diff options
Diffstat (limited to 'builtin')
87 files changed, 9022 insertions, 5472 deletions
diff --git a/builtin/add.c b/builtin/add.c index 56a4e0af6b..87446cf92a 100644 --- a/builtin/add.c +++ b/builtin/add.c @@ -13,6 +13,7 @@ #include "diff.h" #include "diffcore.h" #include "revision.h" +#include "bulk-checkin.h" static const char * const builtin_add_usage[] = { "git add [options] [--] <filepattern>...", @@ -21,12 +22,32 @@ static const char * const builtin_add_usage[] = { static int patch_interactive, add_interactive, edit_interactive; static int take_worktree_changes; -struct update_callback_data -{ +struct update_callback_data { int flags; int add_errors; }; +static int fix_unmerged_status(struct diff_filepair *p, + struct update_callback_data *data) +{ + if (p->status != DIFF_STATUS_UNMERGED) + return p->status; + if (!(data->flags & ADD_CACHE_IGNORE_REMOVAL) && !p->two->mode) + /* + * This is not an explicit add request, and the + * path is missing from the working tree (deleted) + */ + return DIFF_STATUS_DELETED; + else + /* + * Either an explicit add request, or path exists + * in the working tree. An attempt to explicitly + * add a path that does not exist in the working tree + * will be caught as an error by the caller immediately. + */ + return DIFF_STATUS_MODIFIED; +} + static void update_callback(struct diff_queue_struct *q, struct diff_options *opt, void *cbdata) { @@ -36,35 +57,14 @@ static void update_callback(struct diff_queue_struct *q, for (i = 0; i < q->nr; i++) { struct diff_filepair *p = q->queue[i]; const char *path = p->one->path; - switch (p->status) { + switch (fix_unmerged_status(p, data)) { 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. - */ + die(_("unexpected diff status %c"), p->status); 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"); + die(_("updating files failed")); data->add_errors++; } break; @@ -74,7 +74,7 @@ static void update_callback(struct diff_queue_struct *q, 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); + printf(_("remove '%s'\n"), path); break; } } @@ -86,12 +86,13 @@ int add_files_to_cache(const char *prefix, const char **pathspec, int flags) struct rev_info rev; init_revisions(&rev, prefix); setup_revisions(0, NULL, &rev, NULL); - rev.prune_data = pathspec; + init_pathspec(&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; + rev.max_count = 0; /* do not compare unmerged paths with stage #2 */ run_diff_files(&rev, DIFF_RACY_IS_MODIFIED); return !!data.add_errors; } @@ -172,7 +173,7 @@ static void treat_gitlinks(const char **pathspec) /* strip trailing slash */ pathspec[j] = xstrndup(ce->name, len); else - die ("Path '%s' is in submodule '%.*s'", + die (_("Path '%s' is in submodule '%.*s'"), pathspec[j], len, ce->name); } } @@ -188,10 +189,10 @@ static void refresh(int verbose, const char **pathspec) /* nothing */; seen = xcalloc(specs, 1); refresh_index(&the_index, verbose ? REFRESH_IN_PORCELAIN : REFRESH_QUIET, - pathspec, seen, "Unstaged changes after refreshing the index:"); + 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]); + die(_("pathspec '%s' did not match any files"), pathspec[i]); } free(seen); } @@ -205,7 +206,7 @@ static const char **validate_pathspec(int argc, const char **argv, 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); + die(_("'%s' is beyond a symbolic link"), *p + len); } } } @@ -242,7 +243,7 @@ int run_add_interactive(const char *revision, const char *patch_mode, return status; } -int interactive_add(int argc, const char **argv, const char *prefix) +int interactive_add(int argc, const char **argv, const char *prefix, int patch) { const char **pathspec = NULL; @@ -253,7 +254,7 @@ int interactive_add(int argc, const char **argv, const char *prefix) } return run_add_interactive(NULL, - patch_interactive ? "--patch" : NULL, + patch ? "--patch" : NULL, pathspec); } @@ -272,33 +273,34 @@ static int edit_patch(int argc, const char **argv, const char *prefix) git_config(git_diff_basic_config, NULL); /* no "diff" UI options */ if (read_cache() < 0) - die ("Could not read the index"); + 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; + DIFF_OPT_SET(&rev.diffopt, IGNORE_DIRTY_SUBMODULES); out = open(file, O_CREAT | O_WRONLY, 0644); if (out < 0) - die ("Could not open '%s' for writing.", file); + 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"); + die (_("Could not write patch")); launch_editor(file, NULL, NULL); if (stat(file, &st)) - die_errno("Could not stat '%s'", file); + die_errno(_("Could not stat '%s'"), file); if (!st.st_size) - die("Empty patch. Aborted."); + 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); + die (_("Could not apply '%s'"), file); unlink(file); return 0; @@ -307,22 +309,22 @@ static int edit_patch(int argc, const char **argv, const char *prefix) static struct lock_file lock_file; static const char ignore_error[] = -"The following paths are ignored by one of your .gitignore files:\n"; +N_("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, ignore_missing = 0; static struct option builtin_add_options[] = { - OPT__DRY_RUN(&show_only), - OPT__VERBOSE(&verbose), + OPT__DRY_RUN(&show_only, "dry run"), + OPT__VERBOSE(&verbose, "be verbose"), OPT_GROUP(""), OPT_BOOLEAN('i', "interactive", &add_interactive, "interactive picking"), - OPT_BOOLEAN('p', "patch", &patch_interactive, "interactive patching"), + OPT_BOOLEAN('p', "patch", &patch_interactive, "select hunks interactively"), OPT_BOOLEAN('e', "edit", &edit_interactive, "edit current diff and apply"), - OPT_BOOLEAN('f', "force", &ignored_too, "allow adding otherwise ignored files"), + OPT__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('A', "all", &addremove, "add changes from all tracked and untracked 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_BOOLEAN( 0 , "ignore-missing", &ignore_missing, "check if - even missing - files are ignored in dry run"), @@ -331,7 +333,8 @@ static struct option builtin_add_options[] = { static int add_config(const char *var, const char *value, void *cb) { - if (!strcasecmp(var, "add.ignore-errors")) { + if (!strcmp(var, "add.ignoreerrors") || + !strcmp(var, "add.ignore-errors")) { ignore_add_errors = git_config_bool(var, value); return 0; } @@ -343,17 +346,17 @@ static int add_files(struct dir_struct *dir, int flags) int i, exit_status = 0; if (dir->ignored_nr) { - fprintf(stderr, ignore_error); + 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"); + 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"); + die(_("adding files failed")); exit_status = 1; } return exit_status; @@ -377,7 +380,7 @@ int cmd_add(int argc, const char **argv, const char *prefix) if (patch_interactive) add_interactive = 1; if (add_interactive) - exit(interactive_add(argc - 1, argv + 1, prefix)); + exit(interactive_add(argc - 1, argv + 1, prefix, patch_interactive)); if (edit_interactive) return(edit_patch(argc, argv, prefix)); @@ -385,9 +388,9 @@ int cmd_add(int argc, const char **argv, const char *prefix) argv++; if (addremove && take_worktree_changes) - die("-A and -u are mutually incompatible"); + die(_("-A and -u are mutually incompatible")); if (!show_only && ignore_missing) - die("Option --ignore-missing can only be used together with --dry-run"); + die(_("Option --ignore-missing can only be used together with --dry-run")); if ((addremove || take_worktree_changes) && !argc) { static const char *here[2] = { ".", NULL }; argc = 1; @@ -407,14 +410,14 @@ int cmd_add(int argc, const char **argv, const char *prefix) ? 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"); + 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"); + die(_("index file corrupt")); treat_gitlinks(pathspec); if (add_new_files) { @@ -440,32 +443,41 @@ int cmd_add(int argc, const char **argv, const char *prefix) if (pathspec) { int i; + struct path_exclude_check check; + + path_exclude_check_init(&check, &dir); if (!seen) seen = find_used_pathspec(pathspec); for (i = 0; pathspec[i]; i++) { if (!seen[i] && pathspec[i][0] && !file_exists(pathspec[i])) { if (ignore_missing) { - if (excluded(&dir, pathspec[i], DT_UNKNOWN)) + int dtype = DT_UNKNOWN; + if (path_excluded(&check, pathspec[i], -1, &dtype)) dir_add_ignored(&dir, pathspec[i], strlen(pathspec[i])); } else - die("pathspec '%s' did not match any files", + die(_("pathspec '%s' did not match any files"), pathspec[i]); } } free(seen); + path_exclude_check_clear(&check); } + plug_bulk_checkin(); + exit_status |= add_files_to_cache(prefix, pathspec, flags); if (add_new_files) exit_status |= add_files(&dir, flags); + unplug_bulk_checkin(); + 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"); + die(_("Unable to write new index file")); } return exit_status; diff --git a/builtin/apply.c b/builtin/apply.c index 23c18c573b..b4428ea34f 100644 --- a/builtin/apply.c +++ b/builtin/apply.c @@ -14,6 +14,7 @@ #include "builtin.h" #include "string-list.h" #include "dir.h" +#include "diff.h" #include "parse-options.h" /* @@ -43,12 +44,13 @@ static int apply = 1; static int apply_in_reverse; static int apply_with_reject; static int apply_verbosely; +static int allow_overlap; 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>...]", + N_("git apply [options] [<patch>...]"), NULL }; @@ -101,7 +103,7 @@ static void parse_whitespace_option(const char *option) ws_error_action = correct_ws_error; return; } - die("unrecognized whitespace option '%s'", option); + die(_("unrecognized whitespace option '%s'"), option); } static void parse_ignorewhitespace_option(const char *option) @@ -116,7 +118,7 @@ static void parse_ignorewhitespace_option(const char *option) ws_ignore_action = ignore_ws_change; return; } - die("unrecognized whitespace ignore option '%s'", option); + die(_("unrecognized whitespace ignore option '%s'"), option); } static void set_default_whitespace_mode(const char *whitespace_option) @@ -150,9 +152,14 @@ struct fragment { unsigned long leading, trailing; unsigned long oldpos, oldlines; unsigned long newpos, newlines; + /* + * 'patch' is usually borrowed from buf in apply_patch(), + * but some codepaths store an allocated buffer. + */ const char *patch; + unsigned free_patch:1, + rejected:1; int size; - int rejected; int linenr; struct fragment *next; }; @@ -194,6 +201,36 @@ struct patch { struct patch *next; }; +static void free_fragment_list(struct fragment *list) +{ + while (list) { + struct fragment *next = list->next; + if (list->free_patch) + free((char *)list->patch); + free(list); + list = next; + } +} + +static void free_patch(struct patch *patch) +{ + free_fragment_list(patch->fragments); + free(patch->def_name); + free(patch->old_name); + free(patch->new_name); + free(patch->result); + free(patch); +} + +static void free_patch_list(struct patch *list) +{ + while (list) { + struct patch *next = list->next; + free_patch(list); + list = 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 @@ -204,6 +241,7 @@ struct line { unsigned hash : 24; unsigned flag : 8; #define LINE_COMMON 1 +#define LINE_PATCHED 2 }; /* @@ -248,9 +286,6 @@ static int fuzzy_matchlines(const char *s1, size_t n1, 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--; @@ -302,6 +337,11 @@ static void add_line_info(struct image *img, const char *bol, size_t len, unsign img->nr++; } +/* + * "buf" has the file contents to be patched (read from various sources). + * attach it to "image" and add line-based index to it. + * "image" now owns the "buf". + */ static void prepare_image(struct image *image, char *buf, size_t len, int prepare_linetable) { @@ -335,25 +375,27 @@ static void clear_image(struct image *image) image->len = 0; } -static void say_patch_name(FILE *output, const char *pre, - struct patch *patch, const char *post) +/* fmt must contain _one_ %s and no other substitution */ +static void say_patch_name(FILE *output, const char *fmt, struct patch *patch) { - fputs(pre, output); + struct strbuf sb = STRBUF_INIT; + 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); + quote_c_style(patch->old_name, &sb, NULL, 0); + strbuf_addstr(&sb, " => "); + quote_c_style(patch->new_name, &sb, NULL, 0); } else { const char *n = patch->new_name; if (!n) n = patch->old_name; - quote_c_style(n, NULL, output, 0); + quote_c_style(n, &sb, NULL, 0); } - fputs(post, output); + fprintf(output, fmt, sb.buf); + fputc('\n', output); + strbuf_release(&sb); } -#define CHUNKSIZE (8192) #define SLOP (16) static void read_patch_file(struct strbuf *sb, int fd) @@ -416,7 +458,7 @@ static char *squash_slash(char *name) return name; } -static char *find_name_gnu(const char *line, char *def, int p_value) +static char *find_name_gnu(const char *line, const char *def, int p_value) { struct strbuf name = STRBUF_INIT; char *cp; @@ -439,17 +481,13 @@ static char *find_name_gnu(const char *line, char *def, int p_value) 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)); } -static size_t tz_len(const char *line, size_t len) +static size_t sane_tz_len(const char *line, size_t len) { const char *tz, *p; @@ -467,6 +505,24 @@ static size_t tz_len(const char *line, size_t len) return line + len - tz; } +static size_t tz_with_colon_len(const char *line, size_t len) +{ + const char *tz, *p; + + if (len < strlen(" +08:00") || line[len - strlen(":00")] != ':') + return 0; + tz = line + len - strlen(" +08:00"); + + if (tz[0] != ' ' || (tz[1] != '+' && tz[1] != '-')) + return 0; + p = tz + 2; + if (!isdigit(*p++) || !isdigit(*p++) || *p++ != ':' || + !isdigit(*p++) || !isdigit(*p++)) + return 0; + + return line + len - tz; +} + static size_t date_len(const char *line, size_t len) { const char *date, *p; @@ -561,7 +617,9 @@ static size_t diff_timestamp_len(const char *line, size_t len) if (!isdigit(end[-1])) return 0; - n = tz_len(line, end - line); + n = sane_tz_len(line, end - line); + if (!n) + n = tz_with_colon_len(line, end - line); end -= n; n = short_time_len(line, end - line); @@ -588,8 +646,13 @@ static size_t diff_timestamp_len(const char *line, size_t len) return line + len - end; } -static char *find_name_common(const char *line, char *def, int p_value, - const char *end, int terminate) +static char *null_strdup(const char *s) +{ + return s ? xstrdup(s) : NULL; +} + +static char *find_name_common(const char *line, const char *def, + int p_value, const char *end, int terminate) { int len; const char *start = NULL; @@ -610,10 +673,10 @@ static char *find_name_common(const char *line, char *def, int p_value, start = line; } if (!start) - return squash_slash(def); + return squash_slash(null_strdup(def)); len = line - start; if (!len) - return squash_slash(def); + return squash_slash(null_strdup(def)); /* * Generally we prefer the shorter name, especially @@ -624,8 +687,7 @@ static char *find_name_common(const char *line, char *def, int p_value, if (def) { int deflen = strlen(def); if (deflen < len && !strncmp(start, def, deflen)) - return squash_slash(def); - free(def); + return squash_slash(xstrdup(def)); } if (root) { @@ -733,8 +795,8 @@ static int has_epoch_timestamp(const char *nameline) " " "[0-2][0-9]:[0-5][0-9]:00(\\.0+)?" " " - "([-+][0-2][0-9][0-5][0-9])\n"; - const char *timestamp = NULL, *cp; + "([-+][0-2][0-9]:?[0-5][0-9])\n"; + const char *timestamp = NULL, *cp, *colon; static regex_t *stamp; regmatch_t m[10]; int zoneoffset; @@ -750,7 +812,7 @@ static int has_epoch_timestamp(const char *nameline) if (!stamp) { stamp = xmalloc(sizeof(*stamp)); if (regcomp(stamp, stamp_regexp, REG_EXTENDED)) { - warning("Cannot prepare timestamp regexp %s", + warning(_("Cannot prepare timestamp regexp %s"), stamp_regexp); return 0; } @@ -759,13 +821,16 @@ static int has_epoch_timestamp(const char *nameline) status = regexec(stamp, timestamp, ARRAY_SIZE(m), m, 0); if (status) { if (status != REG_NOMATCH) - warning("regexec returned %d for input: %s", + 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); + zoneoffset = strtol(timestamp + m[3].rm_so + 1, (char **) &colon, 10); + if (*colon == ':') + zoneoffset = zoneoffset * 60 + strtol(colon + 1, NULL, 10); + else + zoneoffset = (zoneoffset / 100) * 60 + (zoneoffset % 100); if (timestamp[m[3].rm_so] == '-') zoneoffset = -zoneoffset; @@ -819,8 +884,10 @@ static void parse_traditional_patch(const char *first, const char *second, struc name = find_name_traditional(first, NULL, p_value); patch->old_name = name; } else { - name = find_name_traditional(first, NULL, p_value); - name = find_name_traditional(second, name, p_value); + char *first_name; + first_name = find_name_traditional(first, NULL, p_value); + name = find_name_traditional(second, first_name, p_value); + free(first_name); if (has_epoch_timestamp(first)) { patch->is_new = 1; patch->is_delete = 0; @@ -830,11 +897,12 @@ static void parse_traditional_patch(const char *first, const char *second, struc patch->is_delete = 1; patch->old_name = name; } else { - patch->old_name = patch->new_name = name; + patch->old_name = name; + patch->new_name = xstrdup(name); } } if (!name) - die("unable to find filename in patch at line %d", linenr); + die(_("unable to find filename in patch at line %d"), linenr); } static int gitdiff_hdrend(const char *line, struct patch *patch) @@ -851,7 +919,10 @@ static int gitdiff_hdrend(const char *line, struct patch *patch) * 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) +#define DIFF_OLD_NAME 0 +#define DIFF_NEW_NAME 1 + +static char *gitdiff_verify_name(const char *line, int isnull, char *orig_name, int side) { if (!orig_name && !isnull) return find_name(line, NULL, p_value, TERM_TAB); @@ -863,30 +934,40 @@ static char *gitdiff_verify_name(const char *line, int isnull, char *orig_name, name = orig_name; len = strlen(name); if (isnull) - die("git apply: bad git-diff - expected /dev/null, got %s on line %d", name, linenr); + 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); + die((side == DIFF_NEW_NAME) ? + _("git apply: bad git-diff - inconsistent new filename on line %d") : + _("git apply: bad git-diff - inconsistent old filename on line %d"), 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); + 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"); + char *orig = patch->old_name; + patch->old_name = gitdiff_verify_name(line, patch->is_new, patch->old_name, + DIFF_OLD_NAME); + if (orig != patch->old_name) + free(orig); 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"); + char *orig = patch->new_name; + patch->new_name = gitdiff_verify_name(line, patch->is_delete, patch->new_name, + DIFF_NEW_NAME); + if (orig != patch->new_name) + free(orig); return 0; } @@ -905,42 +986,48 @@ static int gitdiff_newmode(const char *line, struct patch *patch) static int gitdiff_delete(const char *line, struct patch *patch) { patch->is_delete = 1; - patch->old_name = patch->def_name; + free(patch->old_name); + patch->old_name = null_strdup(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; + free(patch->new_name); + patch->new_name = null_strdup(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); + free(patch->old_name); + patch->old_name = find_name(line, NULL, p_value ? p_value - 1 : 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); + free(patch->new_name); + patch->new_name = find_name(line, NULL, p_value ? p_value - 1 : 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); + free(patch->old_name); + patch->old_name = find_name(line, NULL, p_value ? p_value - 1 : 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); + free(patch->new_name); + patch->new_name = find_name(line, NULL, p_value ? p_value - 1 : 0, 0); return 0; } @@ -1021,11 +1108,11 @@ static const char *stop_at_slash(const char *line, int llen) * 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) +static char *git_header_name(const char *line, int llen) { const char *name; const char *second = NULL; - size_t len; + size_t len, line_len; line += strlen("diff --git "); llen -= strlen("diff --git "); @@ -1125,6 +1212,10 @@ static char *git_header_name(char *line, int llen) * Accept a name only if it shows up twice, exactly the same * form. */ + second = strchr(name, '\n'); + if (!second) + return NULL; + line_len = second - name; for (len = 0 ; ; len++) { switch (name[len]) { default: @@ -1132,15 +1223,11 @@ static char *git_header_name(char *line, int llen) 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)) { + second = stop_at_slash(name + len, line_len - len); + if (!second) + return NULL; + second++; + if (second[len] == '\n' && !strncmp(name, second, len)) { return xmemdupz(name, len); } } @@ -1148,7 +1235,7 @@ static char *git_header_name(char *line, int llen) } /* 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) +static int parse_git_header(const char *line, int len, unsigned int size, struct patch *patch) { unsigned long offset; @@ -1264,7 +1351,7 @@ static int parse_range(const char *line, int len, int offset, const char *expect return offset + ex; } -static void recount_diff(char *line, int size, struct fragment *fragment) +static void recount_diff(const char *line, int size, struct fragment *fragment) { int oldlines = 0, newlines = 0, ret = 0; @@ -1304,7 +1391,7 @@ static void recount_diff(char *line, int size, struct fragment *fragment) break; } if (ret) { - warning("recount: unexpected line: %.*s", + warning(_("recount: unexpected line: %.*s"), (int)linelen(line, size), line); return; } @@ -1318,7 +1405,7 @@ static void recount_diff(char *line, int size, struct fragment *fragment) * 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) +static int parse_fragment_header(const char *line, int len, struct fragment *fragment) { int offset; @@ -1332,7 +1419,7 @@ static int parse_fragment_header(char *line, int len, struct fragment *fragment) return offset; } -static int find_header(char *line, unsigned long size, int *hdrsize, struct patch *patch) +static int find_header(const char *line, unsigned long size, int *hdrsize, struct patch *patch) { unsigned long offset, len; @@ -1361,7 +1448,7 @@ static int find_header(char *line, unsigned long size, int *hdrsize, struct patc struct fragment dummy; if (parse_fragment_header(line, len, &dummy) < 0) continue; - die("patch fragment without header at line %d: %.*s", + die(_("patch fragment without header at line %d: %.*s"), linenr, (int)len-1, line); } @@ -1378,10 +1465,18 @@ static int find_header(char *line, unsigned long size, int *hdrsize, struct patc 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; + die(Q_("git diff header lacks filename information when removing " + "%d leading pathname component (line %d)", + "git diff header lacks filename information when removing " + "%d leading pathname components (line %d)", + p_value), + p_value, linenr); + patch->old_name = xstrdup(patch->def_name); + patch->new_name = xstrdup(patch->def_name); } + if (!patch->is_delete && !patch->new_name) + die("git diff header lacks filename information " + "(line %d)", linenr); patch->is_toplevel_relative = 1; *hdrsize = git_hdr_len; return offset; @@ -1440,7 +1535,7 @@ static void check_whitespace(const char *line, int len, unsigned ws_rule) * 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, +static int parse_fragment(const char *line, unsigned long size, struct patch *patch, struct fragment *fragment) { int added, deleted; @@ -1530,13 +1625,21 @@ static int parse_fragment(char *line, unsigned long size, patch->lines_deleted += deleted; if (0 < patch->is_new && oldlines) - return error("new file depends on old contents"); + return error(_("new file depends on old contents")); if (0 < patch->is_delete && newlines) - return error("deleted file still has contents"); + return error(_("deleted file still has contents")); return offset; } -static int parse_single_patch(char *line, unsigned long size, struct patch *patch) +/* + * We have seen "diff --git a/... b/..." header (or a traditional patch + * header). Read hunks that belong to this patch into fragments and hang + * them to the given patch structure. + * + * The (fragment->patch, fragment->size) pair points into the memory given + * by the caller, not a copy, when we return. + */ +static int parse_single_patch(const char *line, unsigned long size, struct patch *patch) { unsigned long offset = 0; unsigned long oldlines = 0, newlines = 0, context = 0; @@ -1550,7 +1653,7 @@ static int parse_single_patch(char *line, unsigned long size, struct patch *patc fragment->linenr = linenr; len = parse_fragment(line, size, patch, fragment); if (len <= 0) - die("corrupt patch at line %d", linenr); + die(_("corrupt patch at line %d"), linenr); fragment->patch = line; fragment->size = len; oldlines += fragment->oldlines; @@ -1586,12 +1689,14 @@ static int parse_single_patch(char *line, unsigned long size, struct patch *patc patch->is_delete = 0; if (0 < patch->is_new && oldlines) - die("new file %s depends on old contents", patch->new_name); + 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); + 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); + fprintf_ln(stderr, + _("** warning: " + "file %s becomes empty but is not deleted"), + patch->new_name); return offset; } @@ -1609,7 +1714,7 @@ static inline int metadata_changes(struct patch *patch) static char *inflate_it(const void *data, unsigned long size, unsigned long inflated_size) { - z_stream stream; + git_zstream stream; void *out; int st; @@ -1629,6 +1734,11 @@ static char *inflate_it(const void *data, unsigned long size, return out; } +/* + * Read a binary hunk and return a new fragment; fragment->patch + * points at an allocated memory that the caller must free, so + * it is marked as "->free_patch = 1". + */ static struct fragment *parse_binary_hunk(char **buf_p, unsigned long *sz_p, int *status_p, @@ -1716,6 +1826,7 @@ static struct fragment *parse_binary_hunk(char **buf_p, frag = xcalloc(1, sizeof(*frag)); frag->patch = inflate_it(data, hunk_size, origlen); + frag->free_patch = 1; if (!frag->patch) goto corrupt; free(data); @@ -1729,7 +1840,7 @@ static struct fragment *parse_binary_hunk(char **buf_p, corrupt: free(data); *status_p = -1; - error("corrupt binary patch at line %d: %.*s", + error(_("corrupt binary patch at line %d: %.*s"), linenr-1, llen-1, buffer); return NULL; } @@ -1758,7 +1869,7 @@ static int parse_binary(char *buffer, unsigned long size, struct patch *patch) 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); + return error(_("unrecognized binary patch at line %d"), linenr-1); if (status) /* otherwise we already gave an error message */ return status; @@ -1781,6 +1892,13 @@ static int parse_binary(char *buffer, unsigned long size, struct patch *patch) return used; } +/* + * Read the patch text in "buffer" taht extends for "size" bytes; stop + * reading after seeing a single patch (i.e. changes to a single file). + * Create fragments (i.e. patch hunks) and hang them to the given patch. + * Return the number of bytes consumed, so that the caller can call us + * again for the next patch. + */ static int parse_chunk(char *buffer, unsigned long size, struct patch *patch) { int hdrsize, patchsize; @@ -1837,7 +1955,7 @@ static int parse_chunk(char *buffer, unsigned long size, struct patch *patch) */ if ((apply || check) && (!patch->is_binary && !metadata_changes(patch))) - die("patch with only garbage at line %d", linenr); + die(_("patch with only garbage at line %d"), linenr); } return offset + hdrsize + patchsize; @@ -1927,11 +2045,11 @@ 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 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); + return error(_("unable to open or read %s"), path); convert_to_git(path, buf->buf, buf->len, buf, 0); return 0; default: @@ -2002,7 +2120,7 @@ static void update_pre_post_images(struct image *preimage, ctx++; } if (preimage->nr <= ctx) - die("oops"); + die(_("oops")); /* and copy it in, while fixing the line length */ len = preimage->line[ctx].len; @@ -2062,7 +2180,8 @@ static int match_fragment(struct image *img, /* Quick hash check */ for (i = 0; i < preimage_limit; i++) - if (preimage->line[i].hash != img->line[try_lno + i].hash) + if ((img->line[try_lno + i].flag & LINE_PATCHED) || + (preimage->line[i].hash != img->line[try_lno + i].hash)) return 0; if (preimage_limit == preimage->nr) { @@ -2340,6 +2459,11 @@ static void remove_last_line(struct image *img) img->len -= img->line[--img->nr].len; } +/* + * The change from "preimage" and "postimage" has been found to + * apply at applied_pos (counts in line numbers) in "img". + * Update "img" to remove "preimage" and replace it with "postimage". + */ static void update_image(struct image *img, int applied_pos, struct image *preimage, @@ -2405,11 +2529,20 @@ static void update_image(struct image *img, memcpy(img->line + applied_pos, postimage->line, postimage->nr * sizeof(*img->line)); + if (!allow_overlap) + for (i = 0; i < postimage->nr; i++) + img->line[applied_pos + i].flag |= LINE_PATCHED; img->nr = nr; } +/* + * Use the patch-hunk text in "frag" to prepare two images (preimage and + * postimage) for the hunk. Find lines that match "preimage" in "img" and + * replace the part of "img" with "postimage" text. + */ static int apply_one_fragment(struct image *img, struct fragment *frag, - int inaccurate_eof, unsigned ws_rule) + int inaccurate_eof, unsigned ws_rule, + int nth_fragment) { int match_beginning, match_end; const char *patch = frag->patch; @@ -2417,6 +2550,8 @@ static int apply_one_fragment(struct image *img, struct fragment *frag, char *old, *oldlines; struct strbuf newlines; int new_blank_lines_at_end = 0; + int found_new_blank_lines_at_end = 0; + int hunk_linenr = frag->linenr; unsigned long leading, trailing; int pos, applied_pos; struct image preimage; @@ -2507,17 +2642,21 @@ static int apply_one_fragment(struct image *img, struct fragment *frag, break; default: if (apply_verbosely) - error("invalid start of line: '%c'", first); + error(_("invalid start of line: '%c'"), first); return -1; } - if (added_blank_line) + if (added_blank_line) { + if (!new_blank_lines_at_end) + found_new_blank_lines_at_end = hunk_linenr; new_blank_lines_at_end++; + } else if (is_blank_context) ; else new_blank_lines_at_end = 0; patch += len; size -= len; + hunk_linenr++; } if (inaccurate_eof && old > oldlines && old[-1] == '\n' && @@ -2599,7 +2738,8 @@ static int apply_one_fragment(struct image *img, struct fragment *frag, 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); + record_ws_error(WS_BLANK_AT_EOF, "+", 1, + found_new_blank_lines_at_end); if (ws_error_action == correct_ws_error) { while (new_blank_lines_at_end--) remove_last_line(&postimage); @@ -2615,19 +2755,30 @@ static int apply_one_fragment(struct image *img, struct fragment *frag, apply = 0; } + if (apply_verbosely && applied_pos != pos) { + int offset = applied_pos - pos; + if (apply_in_reverse) + offset = 0 - offset; + fprintf_ln(stderr, + Q_("Hunk #%d succeeded at %d (offset %d line).", + "Hunk #%d succeeded at %d (offset %d lines).", + offset), + nth_fragment, applied_pos + 1, offset); + } + /* * 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); + fprintf_ln(stderr, _("Context reduced to (%ld/%ld)" + " to apply fragment at %d"), + leading, trailing, applied_pos+1); update_image(img, applied_pos, &preimage, &postimage); } else { if (apply_verbosely) - error("while searching for:\n%.*s", + error(_("while searching for:\n%.*s"), (int)(old - oldlines), oldlines); } @@ -2645,6 +2796,12 @@ static int apply_binary_fragment(struct image *img, struct patch *patch) unsigned long len; void *dst; + if (!fragment) + return error(_("missing binary patch data for '%s'"), + patch->new_name ? + patch->new_name : + patch->old_name); + /* Binary patch is irreversible without the optional second hunk */ if (apply_in_reverse) { if (!fragment->next) @@ -2675,6 +2832,12 @@ static int apply_binary_fragment(struct image *img, struct patch *patch) return -1; } +/* + * Replace "img" with the result of applying the binary patch. + * The binary patch data itself in patch->fragment is still kept + * but the preimage prepared by the caller in "img" is freed here + * or in the helper function apply_binary_fragment() this calls. + */ static int apply_binary(struct image *img, struct patch *patch) { const char *name = patch->old_name ? patch->old_name : patch->new_name; @@ -2737,13 +2900,13 @@ static int apply_binary(struct image *img, struct patch *patch) * in the patch->fragments->{patch,size}. */ if (apply_binary_fragment(img, patch)) - return error("binary patch does not apply to '%s'", + 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)", + return error(_("binary patch to '%s' creates incorrect result (expecting %s, got %s)"), name, patch->new_sha1_prefix, sha1_to_hex(sha1)); } @@ -2756,13 +2919,15 @@ static int apply_fragments(struct image *img, struct patch *patch) const char *name = patch->old_name ? patch->old_name : patch->new_name; unsigned ws_rule = patch->ws_rule; unsigned inaccurate_eof = patch->inaccurate_eof; + int nth = 0; 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); + nth++; + if (apply_one_fragment(img, frag, inaccurate_eof, ws_rule, nth)) { + error(_("patch failed: %s:%ld"), name, frag->oldpos); if (!apply_with_reject) return -1; frag->rejected = 1; @@ -2877,14 +3042,14 @@ static int apply_data(struct patch *patch, struct stat *st, struct cache_entry * 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", + return error(_("patch %s has been renamed/deleted"), patch->old_name); } - /* We have a patched copy in memory use that */ + /* 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); + return error(_("read of %s failed"), patch->old_name); } else if (patch->old_name) { if (S_ISGITLINK(patch->old_mode)) { if (ce) { @@ -2893,12 +3058,15 @@ static int apply_data(struct patch *patch, struct stat *st, struct cache_entry * /* * There is no way to apply subproject * patch without looking at the index. + * NEEDSWORK: shouldn't this be flagged + * as an error??? */ + free_fragment_list(patch->fragments); patch->fragments = NULL; } } else { if (read_old_data(st, patch->old_name, &buf)) - return error("read of %s failed", patch->old_name); + return error(_("read of %s failed"), patch->old_name); } } @@ -2913,7 +3081,7 @@ static int apply_data(struct patch *patch, struct stat *st, struct cache_entry * free(image.line_allocated); if (0 < patch->is_delete && patch->resultsize) - return error("removal patch leaves file contents"); + return error(_("removal patch leaves file contents")); return 0; } @@ -2934,7 +3102,7 @@ static int check_to_create_blob(const char *new_name, int ok_if_exists) if (has_symlink_leading_path(new_name, strlen(new_name))) return 0; - return error("%s: already exists in working directory", new_name); + return error(_("%s: already exists in working directory"), new_name); } else if ((errno != ENOENT) && (errno != ENOTDIR)) return error("%s: %s", new_name, strerror(errno)); @@ -2972,12 +3140,12 @@ static int check_preimage(struct patch *patch, struct cache_entry **ce, struct s 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); + 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)); + return error(_("%s: %s"), old_name, strerror(errno)); } if (to_be_deleted(tpatch)) @@ -2988,7 +3156,7 @@ static int check_preimage(struct patch *patch, struct cache_entry **ce, struct s if (pos < 0) { if (patch->is_new < 0) goto is_new; - return error("%s: does not exist in index", old_name); + return error(_("%s: does not exist in index"), old_name); } *ce = active_cache[pos]; if (stat_ret < 0) { @@ -3002,13 +3170,13 @@ static int check_preimage(struct patch *patch, struct cache_entry **ce, struct s return -1; } if (!cached && verify_index_match(*ce, st)) - return error("%s: does not match index", old_name); + 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)); + return error(_("%s: %s"), old_name, strerror(errno)); } if (!cached && !tpatch) @@ -3019,9 +3187,9 @@ static int check_preimage(struct patch *patch, struct cache_entry **ce, struct s if (!patch->old_mode) patch->old_mode = st_mode; if ((st_mode ^ patch->old_mode) & S_IFMT) - return error("%s: wrong type", old_name); + return error(_("%s: wrong type"), old_name); if (st_mode != patch->old_mode) - warning("%s has type %o, expected %o", + 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; @@ -3030,10 +3198,15 @@ static int check_preimage(struct patch *patch, struct cache_entry **ce, struct s is_new: patch->is_new = 1; patch->is_delete = 0; + free(patch->old_name); patch->old_name = NULL; return 0; } +/* + * Check and apply the patch in-core; leave the result in patch->result + * for the caller to write it out to the final destination. + */ static int check_patch(struct patch *patch) { struct stat st; @@ -3071,7 +3244,7 @@ static int check_patch(struct patch *patch) if (check_index && cache_name_pos(new_name, strlen(new_name)) >= 0 && !ok_if_exists) - return error("%s: already exists in index", new_name); + return error(_("%s: already exists in index"), new_name); if (!cached) { int err = check_to_create_blob(new_name, ok_if_exists); if (err) @@ -3089,14 +3262,22 @@ static int check_patch(struct patch *patch) 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 ((patch->old_mode ^ patch->new_mode) & S_IFMT) { + if (same) + return error(_("new mode (%o) of %s does not " + "match old mode (%o)"), + patch->new_mode, new_name, + patch->old_mode); + else + return error(_("new mode (%o) of %s does not " + "match old mode (%o) of %s"), + patch->new_mode, new_name, + patch->old_mode, old_name); + } } if (apply_data(patch, &st, ce) < 0) - return error("%s: patch does not apply", name); + return error(_("%s: patch does not apply"), name); patch->rejected = 0; return 0; } @@ -3109,7 +3290,7 @@ static int check_patch_list(struct patch *patch) while (patch) { if (apply_verbosely) say_patch_name(stderr, - "Checking patch ", patch, "...\n"); + _("Checking patch %s..."), patch); err |= check_patch(patch); patch = patch->next; } @@ -3164,7 +3345,7 @@ static void build_fake_ancestor(struct patch *list, const char *filename) ce = make_cache_entry(patch->old_mode, sha1_ptr, name, 0, 0); if (!ce) - die("make_cache_entry failed for path '%s'", name); + 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); } @@ -3187,7 +3368,7 @@ static void stat_patch_list(struct patch *patch) show_stats(patch); } - printf(" %d files changed, %d insertions(+), %d deletions(-)\n", files, adds, dels); + print_stat_summary(stdout, files, adds, dels); } static void numstat_patch_list(struct patch *patch) @@ -3307,7 +3488,7 @@ 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); + die(_("unable to remove %s from index"), patch->old_name); } if (!cached) { if (!remove_or_warn(patch->old_mode, patch->old_name) && rmdir_empty) { @@ -3334,19 +3515,19 @@ static void add_index_file(const char *path, unsigned mode, void *buf, unsigned const char *s = buf; if (get_sha1_hex(s + strlen("Subproject commit "), ce->sha1)) - die("corrupt patch for subproject %s", path); + die(_("corrupt patch for subproject %s"), path); } else { if (!cached) { if (lstat(path, &st) < 0) - die_errno("unable to stat newly created file '%s'", + 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); + 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); + 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) @@ -3379,7 +3560,7 @@ static int try_create_file(const char *path, unsigned int mode, const char *buf, strbuf_release(&nbuf); if (close(fd) < 0) - die_errno("closing file '%s'", path); + die_errno(_("closing file '%s'"), path); return 0; } @@ -3428,7 +3609,7 @@ static void create_one_file(char *path, unsigned mode, const char *buf, unsigned ++nr; } } - die_errno("unable to write file '%s' mode %o", path, mode); + die_errno(_("unable to write file '%s' mode %o"), path, mode); } static void create_file(struct patch *patch) @@ -3473,6 +3654,7 @@ static int write_out_one_reject(struct patch *patch) char namebuf[PATH_MAX]; struct fragment *frag; int cnt = 0; + struct strbuf sb = STRBUF_INIT; for (cnt = 0, frag = patch->fragments; frag; frag = frag->next) { if (!frag->rejected) @@ -3483,7 +3665,7 @@ static int write_out_one_reject(struct patch *patch) if (!cnt) { if (apply_verbosely) say_patch_name(stderr, - "Applied patch ", patch, " cleanly.\n"); + _("Applied patch %s cleanly."), patch); return 0; } @@ -3491,16 +3673,20 @@ static int write_out_one_reject(struct patch *patch) * contents are marked "rejected" at the patch level. */ if (!patch->new_name) - die("internal error"); + die(_("internal error")); /* Say this even without --verbose */ - say_patch_name(stderr, "Applying patch ", patch, " with"); - fprintf(stderr, " %d rejects...\n", cnt); + strbuf_addf(&sb, Q_("Applying patch %%s with %d reject...", + "Applying patch %%s with %d rejects...", + cnt), + cnt); + say_patch_name(stderr, sb.buf, patch); + strbuf_release(&sb); cnt = strlen(patch->new_name); if (ARRAY_SIZE(namebuf) <= cnt + 5) { cnt = ARRAY_SIZE(namebuf) - 5; - warning("truncating .rej filename to %.*s.rej", + warning(_("truncating .rej filename to %.*s.rej"), cnt - 1, patch->new_name); } memcpy(namebuf, patch->new_name, cnt); @@ -3508,7 +3694,7 @@ static int write_out_one_reject(struct patch *patch) rej = fopen(namebuf, "w"); if (!rej) - return error("cannot open %s: %s", namebuf, strerror(errno)); + 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 @@ -3521,10 +3707,10 @@ static int write_out_one_reject(struct patch *patch) frag; cnt++, frag = frag->next) { if (!frag->rejected) { - fprintf(stderr, "Hunk #%d applied cleanly.\n", cnt); + fprintf_ln(stderr, _("Hunk #%d applied cleanly."), cnt); continue; } - fprintf(stderr, "Rejected hunk #%d.\n", cnt); + fprintf_ln(stderr, _("Rejected hunk #%d."), cnt); fprintf(rej, "%.*s", frag->size, frag->patch); if (frag->patch[frag->size-1] != '\n') fputc('\n', rej); @@ -3533,15 +3719,12 @@ static int write_out_one_reject(struct patch *patch) return -1; } -static int write_out_results(struct patch *list, int skipped_patch) +static int write_out_results(struct patch *list) { 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) { @@ -3613,15 +3796,8 @@ 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); - } + prefix_one(&p->new_name); + prefix_one(&p->old_name); } } @@ -3631,12 +3807,10 @@ static void prefix_patches(struct patch *p) static int apply_patch(int fd, const char *filename, int options) { size_t offset; - struct strbuf buf = STRBUF_INIT; + struct strbuf buf = STRBUF_INIT; /* owns the patch text */ 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; @@ -3660,13 +3834,15 @@ static int apply_patch(int fd, const char *filename, int options) listp = &patch->next; } else { - /* perhaps free it a bit better? */ - free(patch); + free_patch(patch); skipped_patch++; } offset += nr; } + if (!list && !skipped_patch) + die(_("unrecognized input")); + if (whitespace_error && (ws_error_action == die_on_ws_error)) apply = 0; @@ -3676,7 +3852,7 @@ static int apply_patch(int fd, const char *filename, int options) if (check_index) { if (read_cache() < 0) - die("unable to read index file"); + die(_("unable to read index file")); } if ((check || apply) && @@ -3684,7 +3860,7 @@ static int apply_patch(int fd, const char *filename, int options) !apply_with_reject) exit(1); - if (apply && write_out_results(list, skipped_patch)) + if (apply && write_out_results(list)) exit(1); if (fake_ancestor) @@ -3699,7 +3875,9 @@ static int apply_patch(int fd, const char *filename, int options) if (summary) summary_patch_list(list); + free_patch_list(list); strbuf_release(&buf); + string_list_clear(&fn_table, 0); return 0; } @@ -3784,74 +3962,71 @@ int cmd_apply(int argc, const char **argv, const char *prefix_) int i; int errs = 0; int is_not_gitdir = !startup_info->have_repository; - 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", + { OPTION_CALLBACK, 0, "exclude", NULL, N_("path"), + N_("don't apply changes matching the given path"), 0, option_parse_exclude }, - { OPTION_CALLBACK, 0, "include", NULL, "path", - "apply changes matching the given path", + { OPTION_CALLBACK, 0, "include", NULL, N_("path"), + N_("apply changes matching the given path"), 0, option_parse_include }, - { OPTION_CALLBACK, 'p', NULL, NULL, "num", - "remove <num> leading slashes from traditional diff paths", + { OPTION_CALLBACK, 'p', NULL, NULL, N_("num"), + N_("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"), + N_("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 }, + N_("instead of applying the patch, output diffstat for the input")), + OPT_NOOP_NOARG(0, "allow-binary-replacement"), + OPT_NOOP_NOARG(0, "binary"), OPT_BOOLEAN(0, "numstat", &numstat, - "shows number of added and deleted lines in decimal notation"), + N_("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"), + N_("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"), + N_("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"), + N_("make sure the patch is applicable to the current index")), OPT_BOOLEAN(0, "cached", &cached, - "apply a patch without touching the working tree"), + N_("apply a patch without touching the working tree")), OPT_BOOLEAN(0, "apply", &force_apply, - "also apply the patch (use with --stat/--summary/--check)"), + N_("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"), + N_("build a temporary index based on embedded index information")), { OPTION_CALLBACK, 'z', NULL, NULL, NULL, - "paths are separated with NUL character", + N_("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", + N_("ensure at least <n> lines of context match")), + { OPTION_CALLBACK, 0, "whitespace", &whitespace_option, N_("action"), + N_("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", + N_("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", + N_("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"), + N_("apply the patch in reverse")), OPT_BOOLEAN(0, "unidiff-zero", &unidiff_zero, - "don't expect at least one line of context"), + N_("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), + N_("leave the rejected hunks in corresponding *.rej files")), + OPT_BOOLEAN(0, "allow-overlap", &allow_overlap, + N_("allow overlapping hunks")), + OPT__VERBOSE(&apply_verbosely, N_("be verbose")), OPT_BIT(0, "inaccurate-eof", &options, - "tolerate incorrectly detected missing new-line at the end of file", + N_("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", + N_("do not trust the line counts in the hunk headers"), RECOUNT), - { OPTION_CALLBACK, 0, "directory", NULL, "root", - "prepend <root> to all filenames", + { OPTION_CALLBACK, 0, "directory", NULL, N_("root"), + N_("prepend <root> to all filenames"), 0, option_parse_directory }, OPT_END() }; @@ -3872,10 +4047,10 @@ int cmd_apply(int argc, const char **argv, const char *prefix_) if (!force_apply && (diffstat || numstat || summary || check || fake_ancestor)) apply = 0; if (check_index && is_not_gitdir) - die("--index outside a repository"); + die(_("--index outside a repository")); if (cached) { if (is_not_gitdir) - die("--cached outside a repository"); + die(_("--cached outside a repository")); check_index = 1; } for (i = 0; i < argc; i++) { @@ -3891,7 +4066,7 @@ int cmd_apply(int argc, const char **argv, const char *prefix_) fd = open(arg, O_RDONLY); if (fd < 0) - die_errno("can't open patch '%s'", arg); + die_errno(_("can't open patch '%s'"), arg); read_stdin = 0; set_default_whitespace_mode(whitespace_option); errs |= apply_patch(fd, arg, options); @@ -3905,32 +4080,32 @@ int cmd_apply(int argc, const char **argv, const char *prefix_) squelch_whitespace_errors < whitespace_error) { int squelched = whitespace_error - squelch_whitespace_errors; - warning("squelched %d " - "whitespace error%s", - squelched, - squelched == 1 ? "" : "s"); + warning(Q_("squelched %d whitespace error", + "squelched %d whitespace errors", + squelched), + squelched); } 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" : ""); + die(Q_("%d line adds whitespace errors.", + "%d lines add whitespace errors.", + whitespace_error), + whitespace_error); 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" : ""); + warning(Q_("%d line adds whitespace errors.", + "%d lines add whitespace errors.", + whitespace_error), + whitespace_error); } if (update_index) { if (write_cache(newfd, active_cache, active_nr) || commit_locked_index(&lock_file)) - die("Unable to write new index file"); + die(_("Unable to write new index file")); } return !!errs; diff --git a/builtin/archive.c b/builtin/archive.c index 6a887f5a9d..931956def9 100644 --- a/builtin/archive.c +++ b/builtin/archive.c @@ -14,17 +14,18 @@ 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); + 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"); + 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) + const char *remote, const char *exec, + const char *name_hint) { char buf[LARGE_PACKET_MAX]; int fd[2], i, len, rv; @@ -33,28 +34,41 @@ static int run_remote_archiver(int argc, const char **argv, _remote = remote_get(remote); if (!_remote->url[0]) - die("git archive: Remote with no URL"); + die(_("git archive: Remote with no URL")); transport = transport_get(_remote, _remote->url[0]); transport_connect(transport, "git-upload-archive", exec, fd); + /* + * Inject a fake --format field at the beginning of the + * arguments, with the format inferred from our output + * filename. This way explicit --format options can override + * it. + */ + if (name_hint) { + const char *format = archive_format_from_filename(name_hint); + if (format) + packet_write(fd[1], "argument --format=%s\n", format); + } 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"); + 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"); + die(_("git archive: NACK %s"), buf + 5); + if (len > 4 && !prefixcmp(buf, "ERR ")) + die(_("remote error: %s"), buf + 4); + die(_("git archive: protocol error")); } len = packet_read_line(fd[0], buf, sizeof(buf)); if (len) - die("git archive: expected a flush"); + 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); @@ -63,17 +77,6 @@ static int run_remote_archiver(int argc, const char **argv, 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 | \ @@ -84,7 +87,6 @@ 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"), @@ -98,32 +100,13 @@ int cmd_archive(int argc, const char **argv, const char *prefix) argc = parse_options(argc, argv, prefix, local_opts, NULL, PARSE_OPT_KEEP_ALL); - if (output) { + 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); + return run_remote_archiver(argc, argv, remote, exec, output); setvbuf(stderr, NULL, _IOLBF, BUFSIZ); - return write_archive(argc, argv, prefix, 1); + return write_archive(argc, argv, prefix, 1, output, 0); } diff --git a/builtin/bisect--helper.c b/builtin/bisect--helper.c index 5b226399e1..8d325a5179 100644 --- a/builtin/bisect--helper.c +++ b/builtin/bisect--helper.c @@ -4,16 +4,19 @@ #include "bisect.h" static const char * const git_bisect_helper_usage[] = { - "git bisect--helper --next-all", + "git bisect--helper --next-all [--no-checkout]", NULL }; int cmd_bisect__helper(int argc, const char **argv, const char *prefix) { int next_all = 0; + int no_checkout = 0; struct option options[] = { OPT_BOOLEAN(0, "next-all", &next_all, "perform 'git bisect next'"), + OPT_BOOLEAN(0, "no-checkout", &no_checkout, + "update BISECT_HEAD instead of checking out the current commit"), OPT_END() }; @@ -24,5 +27,5 @@ int cmd_bisect__helper(int argc, const char **argv, const char *prefix) usage_with_options(git_bisect_helper_usage, options); /* next-all */ - return bisect_next_all(prefix); + return bisect_next_all(prefix, no_checkout); } diff --git a/builtin/blame.c b/builtin/blame.c index 101535448f..24d3dd5292 100644 --- a/builtin/blame.c +++ b/builtin/blame.c @@ -41,6 +41,7 @@ static int reverse; static int blank_boundary; static int incremental; static int xdl_opts; +static int abbrev = -1; static enum date_mode blame_date_mode = DATE_ISO8601; static size_t blame_date_width; @@ -83,15 +84,31 @@ struct origin { struct commit *commit; mmfile_t file; unsigned char blob_sha1[20]; + unsigned mode; char path[FLEX_ARRAY]; }; +static int diff_hunks(mmfile_t *file_a, mmfile_t *file_b, long ctxlen, + xdl_emit_hunk_consume_func_t hunk_func, void *cb_data) +{ + xpparam_t xpp = {0}; + xdemitconf_t xecfg = {0}; + xdemitcb_t ecb = {NULL}; + + xpp.flags = xdl_opts; + xecfg.ctxlen = ctxlen; + xecfg.hunk_func = hunk_func; + ecb.priv = cb_data; + return xdi_diff(file_a, file_b, &xpp, &xecfg, &ecb); +} + /* * Prepare diff_filespec and convert it using diff textconv API * if the textconv driver exists. * Return 1 if the conversion succeeds, 0 otherwise. */ int textconv_object(const char *path, + unsigned mode, const unsigned char *sha1, char **buf, unsigned long *buf_size) @@ -100,7 +117,7 @@ int textconv_object(const char *path, struct userdiff_driver *textconv; df = alloc_filespec(path); - fill_filespec(df, sha1, S_IFREG | 0664); + fill_filespec(df, sha1, mode); textconv = get_textconv(df); if (!textconv) { free_filespec(df); @@ -125,7 +142,7 @@ static void fill_origin_blob(struct diff_options *opt, num_read_blob++; if (DIFF_OPT_TST(opt, ALLOW_TEXTCONV) && - textconv_object(o->path, o->blob_sha1, &file->ptr, &file_size)) + textconv_object(o->path, o->mode, o->blob_sha1, &file->ptr, &file_size)) ; else file->ptr = read_sha1_file(o->blob_sha1, &type, &file_size); @@ -313,21 +330,23 @@ static struct origin *get_origin(struct scoreboard *sb, * 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. + * + * This also fills origin->mode for corresponding tree path. */ -static int fill_blob_sha1(struct origin *origin) +static int fill_blob_sha1_and_mode(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)) + origin->blob_sha1, &origin->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); + origin->mode = S_IFINVALID; return -1; } @@ -360,12 +379,14 @@ static struct origin *find_origin(struct scoreboard *sb, /* * 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, + * scoreboard), it does not know the blob_sha1/mode, * so copy it. Otherwise porigin was in the - * scoreboard and already knows blob_sha1. + * scoreboard and already knows blob_sha1/mode. */ - if (porigin->refcnt == 1) + if (porigin->refcnt == 1) { hashcpy(porigin->blob_sha1, cached->blob_sha1); + porigin->mode = cached->mode; + } return porigin; } /* otherwise it was not very useful; free it */ @@ -400,6 +421,7 @@ static struct origin *find_origin(struct scoreboard *sb, /* The path is the same as parent */ porigin = get_origin(sb, parent, origin->path); hashcpy(porigin->blob_sha1, origin->blob_sha1); + porigin->mode = origin->mode; } else { /* * Since origin->path is a pathspec, if the parent @@ -425,6 +447,7 @@ static struct origin *find_origin(struct scoreboard *sb, case 'M': porigin = get_origin(sb, parent, origin->path); hashcpy(porigin->blob_sha1, p->one->sha1); + porigin->mode = p->one->mode; break; case 'A': case 'T': @@ -444,6 +467,7 @@ static struct origin *find_origin(struct scoreboard *sb, cached = make_origin(porigin->commit, porigin->path); hashcpy(cached->blob_sha1, porigin->blob_sha1); + cached->mode = porigin->mode; parent->util = cached; } return porigin; @@ -486,6 +510,7 @@ static struct origin *find_rename(struct scoreboard *sb, !strcmp(p->two->path, origin->path)) { porigin = get_origin(sb, parent, p->one->path); hashcpy(porigin->blob_sha1, p->one->sha1); + porigin->mode = p->one->mode; break; } } @@ -748,12 +773,14 @@ struct blame_chunk_cb_data { long tlno; }; -static void blame_chunk_cb(void *data, long same, long p_next, long t_next) +static int blame_chunk_cb(long start_a, long count_a, + long start_b, long count_b, void *data) { 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; + blame_chunk(d->sb, d->tlno, d->plno, start_b, d->target, d->parent); + d->plno = start_a + count_a; + d->tlno = start_b + count_b; + return 0; } /* @@ -768,8 +795,7 @@ static int pass_blame_to_parent(struct scoreboard *sb, int last_in_target; mmfile_t file_p, file_o; struct blame_chunk_cb_data d; - xpparam_t xpp; - xdemitconf_t xecfg; + memset(&d, 0, sizeof(d)); d.sb = sb; d.target = target; d.parent = parent; last_in_target = find_last_in_target(sb, target); @@ -780,11 +806,7 @@ static int pass_blame_to_parent(struct scoreboard *sb, fill_origin_blob(&sb->revs->diffopt, 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); + diff_hunks(&file_p, &file_o, 0, blame_chunk_cb, &d); /* 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); @@ -888,12 +910,15 @@ struct handle_split_cb_data { long tlno; }; -static void handle_split_cb(void *data, long same, long p_next, long t_next) +static int handle_split_cb(long start_a, long count_a, + long start_b, long count_b, void *data) { 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; + handle_split(d->sb, d->ent, d->tlno, d->plno, start_b, d->parent, + d->split); + d->plno = start_a + count_a; + d->tlno = start_b + count_b; + return 0; } /* @@ -911,8 +936,7 @@ static void find_copy_in_blob(struct scoreboard *sb, int cnt; mmfile_t file_o; struct handle_split_cb_data d; - xpparam_t xpp; - xdemitconf_t xecfg; + memset(&d, 0, sizeof(d)); d.sb = sb; d.ent = ent; d.parent = parent; d.split = split; /* @@ -932,12 +956,8 @@ static void find_copy_in_blob(struct scoreboard *sb, * 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); + diff_hunks(file_p, &file_o, 1, handle_split_cb, &d); /* remainder, if any, all match the preimage */ handle_split(sb, ent, d.tlno, d.plno, ent->num_lines, parent, split); } @@ -1099,6 +1119,7 @@ static int find_copy_in_parent(struct scoreboard *sb, norigin = get_origin(sb, parent, p->one->path); hashcpy(norigin->blob_sha1, p->one->sha1); + norigin->mode = p->one->mode; fill_origin_blob(&sb->revs->diffopt, norigin, &file_p); if (!file_p.ptr) continue; @@ -1301,8 +1322,7 @@ static void pass_blame(struct scoreboard *sb, struct origin *origin, int opt) /* * Information on commits, used for output. */ -struct commit_info -{ +struct commit_info { const char *author; const char *author_mail; unsigned long author_time; @@ -1367,7 +1387,7 @@ static void get_ac_line(const char *inbuf, const char *what, timepos = tmp; *tmp = 0; - while (person < tmp && *tmp != ' ') + while (person < tmp && !(*tmp == ' ' && tmp[1] == '<')) tmp--; if (tmp <= person) return; @@ -1473,13 +1493,14 @@ static void write_filename_info(const char *path) /* * 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. + * the first time each commit appears in the output (unless the + * user has specifically asked for us to repeat). */ -static int emit_one_suspect_detail(struct origin *suspect) +static int emit_one_suspect_detail(struct origin *suspect, int repeat) { struct commit_info ci; - if (suspect->commit->object.flags & METAINFO_SHOWN) + if (!repeat && (suspect->commit->object.flags & METAINFO_SHOWN)) return 0; suspect->commit->object.flags |= METAINFO_SHOWN; @@ -1518,7 +1539,7 @@ static void found_guilty_entry(struct blame_entry *ent) 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); + emit_one_suspect_detail(suspect, 0); write_filename_info(suspect->path); maybe_flush_or_die(stdout, "stdout"); } @@ -1586,7 +1607,7 @@ static const char *format_time(unsigned long time, const char *tz_str, int tz; if (show_raw_time) { - sprintf(time_buf, "%lu %s", time, tz_str); + snprintf(time_buf, sizeof(time_buf), "%lu %s", time, tz_str); } else { tz = atoi(tz_str); @@ -1606,9 +1627,20 @@ static const char *format_time(unsigned long time, const char *tz_str, #define OUTPUT_SHOW_NUMBER 040 #define OUTPUT_SHOW_SCORE 0100 #define OUTPUT_NO_AUTHOR 0200 +#define OUTPUT_SHOW_EMAIL 0400 +#define OUTPUT_LINE_PORCELAIN 01000 -static void emit_porcelain(struct scoreboard *sb, struct blame_entry *ent) +static void emit_porcelain_details(struct origin *suspect, int repeat) { + if (emit_one_suspect_detail(suspect, repeat) || + (suspect->commit->object.flags & MORE_THAN_ONE_PATH)) + write_filename_info(suspect->path); +} + +static void emit_porcelain(struct scoreboard *sb, struct blame_entry *ent, + int opt) +{ + int repeat = opt & OUTPUT_LINE_PORCELAIN; int cnt; const char *cp; struct origin *suspect = ent->suspect; @@ -1621,17 +1653,18 @@ static void emit_porcelain(struct scoreboard *sb, struct blame_entry *ent) 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); + emit_porcelain_details(suspect, repeat); cp = nth_line(sb, ent->lno); for (cnt = 0; cnt < ent->num_lines; cnt++) { char ch; - if (cnt) + if (cnt) { printf("%s %d %d\n", hex, ent->s_lno + 1 + cnt, ent->lno + 1 + cnt); + if (repeat) + emit_porcelain_details(suspect, 1); + } putchar('\t'); do { ch = *cp++; @@ -1659,7 +1692,7 @@ static void emit_other(struct scoreboard *sb, struct blame_entry *ent, int opt) 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; + int length = (opt & OUTPUT_LONG_OBJECT_NAME) ? 40 : abbrev; if (suspect->commit->object.flags & UNINTERESTING) { if (blank_boundary) @@ -1671,12 +1704,17 @@ static void emit_other(struct scoreboard *sb, struct blame_entry *ent, int opt) } printf("%.*s", length, hex); - if (opt & OUTPUT_ANNOTATE_COMPAT) - printf("\t(%10s\t%10s\t%d)", ci.author, + if (opt & OUTPUT_ANNOTATE_COMPAT) { + const char *name; + if (opt & OUTPUT_SHOW_EMAIL) + name = ci.author_mail; + else + name = ci.author; + printf("\t(%10s\t%10s\t%d)", name, format_time(ci.author_time, ci.author_tz, show_raw_time), ent->lno + 1 + cnt); - else { + } else { if (opt & OUTPUT_SHOW_SCORE) printf(" %*d %02d", max_score_digits, ent->score, @@ -1689,9 +1727,15 @@ static void emit_other(struct scoreboard *sb, struct blame_entry *ent, int opt) ent->s_lno + 1 + cnt); if (!(opt & OUTPUT_NO_AUTHOR)) { - int pad = longest_author - utf8_strwidth(ci.author); + const char *name; + int pad; + if (opt & OUTPUT_SHOW_EMAIL) + name = ci.author_mail; + else + name = ci.author; + pad = longest_author - utf8_strwidth(name); printf(" (%s%*s %10s", - ci.author, pad, "", + name, pad, "", format_time(ci.author_time, ci.author_tz, show_raw_time)); @@ -1733,7 +1777,7 @@ static void output(struct scoreboard *sb, int option) for (ent = sb->ent; ent; ent = ent->next) { if (option & OUTPUT_PORCELAIN) - emit_porcelain(sb, ent); + emit_porcelain(sb, ent, option); else { emit_other(sb, ent, option); } @@ -1794,18 +1838,6 @@ static int read_ancestry(const char *graft_file) } /* - * 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; width++) - i *= 10; - return width; -} - -/* * How many columns do we need to show line numbers, authors, * and filenames? */ @@ -1829,7 +1861,10 @@ static void find_alignment(struct scoreboard *sb, int *option) 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 (*option & OUTPUT_SHOW_EMAIL) + num = utf8_strwidth(ci.author_mail); + else + num = utf8_strwidth(ci.author); if (longest_author < num) longest_author = num; } @@ -1842,9 +1877,9 @@ static void find_alignment(struct scoreboard *sb, int *option) 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); + max_orig_digits = decimal_width(longest_src_lines); + max_digits = decimal_width(longest_dst_lines); + max_score_digits = decimal_width(largest_score); } /* @@ -2012,14 +2047,8 @@ static int git_blame_config(const char *var, const char *value, void *cb) return 0; } - switch (userdiff_config(var, value)) { - case 0: - break; - case -1: + if (userdiff_config(var, value) < 0) return -1; - default: - return 0; - } return git_default_config(var, value, cb); } @@ -2058,6 +2087,7 @@ static struct commit *fake_working_tree_commit(struct diff_options *opt, if (!contents_from || strcmp("-", contents_from)) { struct stat st; const char *read_from; + char *buf_ptr; unsigned long buf_len; if (contents_from) { @@ -2075,8 +2105,8 @@ static struct commit *fake_working_tree_commit(struct diff_options *opt, switch (st.st_mode & S_IFMT) { case S_IFREG: if (DIFF_OPT_TST(opt, ALLOW_TEXTCONV) && - textconv_object(read_from, null_sha1, &buf.buf, &buf_len)) - buf.len = buf_len; + textconv_object(read_from, mode, null_sha1, &buf_ptr, &buf_len)) + strbuf_attach(&buf, buf_ptr, buf_len, buf_len + 1); else if (strbuf_read_file(&buf, read_from, st.st_size) != st.st_size) die_errno("cannot open or read '%s'", read_from); break; @@ -2274,16 +2304,20 @@ int cmd_blame(int argc, const char **argv, const char *prefix) 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(0, "line-porcelain", &output_option, "Show porcelain format with per-line commit information", OUTPUT_PORCELAIN|OUTPUT_LINE_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('e', "show-email", &output_option, "Show author email instead of name (Default: off)", OUTPUT_SHOW_EMAIL), OPT_BIT('w', NULL, &xdl_opts, "Ignore whitespace differences", XDF_IGNORE_WHITESPACE), + OPT_BIT(0, "minimal", &xdl_opts, "Spend extra cycles to find better match", XDF_NEED_MINIMAL), 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__ABBREV(&abbrev), OPT_END() }; @@ -2298,8 +2332,8 @@ int cmd_blame(int argc, const char **argv, const char *prefix) save_commit_buffer = 0; dashdash_pos = 0; - parse_options_start(&ctx, argc, argv, prefix, PARSE_OPT_KEEP_DASHDASH | - PARSE_OPT_KEEP_ARGV0); + parse_options_start(&ctx, argc, argv, prefix, options, + PARSE_OPT_KEEP_DASHDASH | PARSE_OPT_KEEP_ARGV0); for (;;) { switch (parse_options_step(&ctx, options, blame_opt_usage)) { case PARSE_OPT_HELP: @@ -2319,6 +2353,11 @@ int cmd_blame(int argc, const char **argv, const char *prefix) parse_done: argc = parse_options_end(&ctx); + if (abbrev == -1) + abbrev = default_abbrev; + /* one more abbrev length is needed for the boundary commit */ + abbrev++; + if (revs_file && read_ancestry(revs_file)) die_errno("reading graft file '%s' failed", revs_file); @@ -2455,11 +2494,11 @@ parse_done: } else { o = get_origin(&sb, sb.final, path); - if (fill_blob_sha1(o)) + if (fill_blob_sha1_and_mode(o)) die("no such path %s in %s", path, final_commit_name); if (DIFF_OPT_TST(&sb.revs->diffopt, ALLOW_TEXTCONV) && - textconv_object(path, o->blob_sha1, (char **) &sb.final_buf, + textconv_object(path, o->mode, o->blob_sha1, (char **) &sb.final_buf, &sb.final_buf_size)) ; else diff --git a/builtin/branch.c b/builtin/branch.c index 87976f0921..0e060f2e4a 100644 --- a/builtin/branch.c +++ b/builtin/branch.c @@ -15,11 +15,13 @@ #include "branch.h" #include "diff.h" #include "revision.h" +#include "string-list.h" +#include "column.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] [-r] (-d | -D) <branchname>...", "git branch [options] (-m | -M) [<oldbranch>] <newbranch>", NULL }; @@ -53,6 +55,9 @@ static enum merge_filter { } merge_filter; static unsigned char merge_filter_ref[20]; +static struct string_list output = STRING_LIST_INIT_DUP; +static unsigned int colopts; + static int parse_branch_color_slot(const char *var, int ofs) { if (!strcasecmp(var+ofs, "plain")) @@ -70,8 +75,10 @@ static int parse_branch_color_slot(const char *var, int ofs) static int git_branch_config(const char *var, const char *value, void *cb) { + if (!prefixcmp(var, "column.")) + return git_column_config(var, value, "branch", &colopts); if (!strcmp(var, "color.branch")) { - branch_use_color = git_config_colorbool(var, value, -1); + branch_use_color = git_config_colorbool(var, value); return 0; } if (!prefixcmp(var, "color.branch.")) { @@ -88,7 +95,7 @@ static int git_branch_config(const char *var, const char *value, void *cb) static const char *branch_get_color(enum color_branch ix) { - if (branch_use_color > 0) + if (want_color(branch_use_color)) return branch_colors[ix]; return ""; } @@ -104,6 +111,7 @@ static int branch_merged(int kind, const char *name, */ struct commit *reference_rev = NULL; const char *reference_name = NULL; + void *reference_name_to_free = NULL; int merged; if (kind == REF_LOCAL_BRANCH) { @@ -114,8 +122,8 @@ static int branch_merged(int kind, const char *name, branch->merge && branch->merge[0] && branch->merge[0]->dst && - (reference_name = - resolve_ref(branch->merge[0]->dst, sha1, 1, NULL)) != NULL) + (reference_name = reference_name_to_free = + resolve_refdup(branch->merge[0]->dst, sha1, 1, NULL)) != NULL) reference_rev = lookup_commit_reference(sha1); } if (!reference_rev) @@ -133,51 +141,55 @@ static int branch_merged(int kind, const char *name, 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.", + warning(_("deleting branch '%s' that has been merged to\n" + " '%s', but 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.", + warning(_("not deleting branch '%s' that is not yet merged to\n" + " '%s', even though it is merged to HEAD."), name, reference_name); } + free(reference_name_to_free); return merged; } -static int delete_branches(int argc, const char **argv, int force, int kinds) +static int delete_branches(int argc, const char **argv, int force, int kinds, + int quiet) { struct commit *rev, *head_rev = NULL; unsigned char sha1[20]; char *name = NULL; - const char *fmt, *remote; + const char *fmt; int i; int ret = 0; + int remote_branch = 0; struct strbuf bname = STRBUF_INIT; switch (kinds) { case REF_REMOTE_BRANCH: fmt = "refs/remotes/%s"; - remote = "remote "; + /* For subsequent UI messages */ + remote_branch = 1; + force = 1; break; case REF_LOCAL_BRANCH: fmt = "refs/heads/%s"; - remote = ""; break; default: - die("cannot use -a with -d"); + 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"); + 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); + error(_("Cannot delete the branch '%s' " + "which you are currently on."), bname.buf); ret = 1; continue; } @@ -185,40 +197,46 @@ static int delete_branches(int argc, const char **argv, int force, int kinds) free(name); name = xstrdup(mkpath(fmt, bname.buf)); - if (!resolve_ref(name, sha1, 1, NULL)) { - error("%sbranch '%s' not found.", - remote, bname.buf); + if (read_ref(name, sha1)) { + error(remote_branch + ? _("remote branch '%s' not found.") + : _("branch '%s' not found."), bname.buf); ret = 1; continue; } rev = lookup_commit_reference(sha1); if (!rev) { - error("Couldn't look up commit object for '%s'", name); + 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" + 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); + "run 'git branch -D %s'."), bname.buf, bname.buf); ret = 1; continue; } if (delete_ref(name, sha1, 0)) { - error("Error deleting %sbranch '%s'", remote, + error(remote_branch + ? _("Error deleting remote branch '%s'") + : _("Error deleting branch '%s'"), 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)); + if (!quiet) + printf(remote_branch + ? _("Deleted remote branch %s (was %s).\n") + : _("Deleted branch %s (was %s).\n"), + 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"); + warning(_("Update of config-file failed")); strbuf_release(&buf); } } @@ -249,7 +267,7 @@ static char *resolve_symref(const char *src, const char *prefix) int flag; const char *dst, *cp; - dst = resolve_ref(src, sha1, 0, &flag); + dst = resolve_ref_unsafe(src, sha1, 0, &flag); if (!(dst && (flag & REF_ISSYMREF))) return NULL; if (prefix && (cp = skip_prefix(dst, prefix))) @@ -259,9 +277,22 @@ static char *resolve_symref(const char *src, const char *prefix) struct append_ref_cb { struct ref_list *ref_list; + const char **pattern; int ret; }; +static int match_patterns(const char **pattern, const char *refname) +{ + if (!*pattern) + return 1; /* no pattern always matches */ + while (*pattern) { + if (!fnmatch(*pattern, refname, 0)) + return 1; + pattern++; + } + return 0; +} + static int append_ref(const char *refname, const unsigned char *sha1, int flags, void *cb_data) { struct append_ref_cb *cb = (struct append_ref_cb *)(cb_data); @@ -296,11 +327,14 @@ static int append_ref(const char *refname, const unsigned char *sha1, int flags, if ((kind & ref_list->kinds) == 0) return 0; + if (!match_patterns(cb->pattern, refname)) + 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) { - cb->ret = error("branch '%s' does not point at a commit", refname); + cb->ret = error(_("branch '%s' does not point at a commit"), refname); return 0; } @@ -313,12 +347,7 @@ static int append_ref(const char *refname, const unsigned char *sha1, int flags, (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)); - } + ALLOC_GROW(ref_list->list, ref_list->index + 1, ref_list->alloc); /* Record the new item */ newitem = &(ref_list->list[ref_list->index++]); @@ -362,6 +391,7 @@ static void fill_tracking_info(struct strbuf *stat, const char *branch_name, int show_upstream_ref) { int ours, theirs; + char *ref = NULL; struct branch *branch = branch_get(branch_name); if (!stat_tracking_info(branch, &ours, &theirs)) { @@ -372,16 +402,29 @@ static void fill_tracking_info(struct strbuf *stat, const char *branch_name, 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); + ref = shorten_unambiguous_ref(branch->merge[0]->dst, 0); + if (!ours) { + if (ref) + strbuf_addf(stat, _("[%s: behind %d]"), ref, theirs); + else + strbuf_addf(stat, _("[behind %d]"), theirs); + + } else if (!theirs) { + if (ref) + strbuf_addf(stat, _("[%s: ahead %d]"), ref, ours); + else + strbuf_addf(stat, _("[ahead %d]"), ours); + } else { + if (ref) + strbuf_addf(stat, _("[%s: ahead %d, behind %d]"), + ref, ours, theirs); + else + strbuf_addf(stat, _("[ahead %d, behind %d]"), + ours, theirs); + } + strbuf_addch(stat, ' '); + free(ref); } static int matches_merge_filter(struct commit *commit) @@ -395,6 +438,28 @@ static int matches_merge_filter(struct commit *commit) return (is_merged == (merge_filter == SHOW_MERGED)); } +static void add_verbose_info(struct strbuf *out, struct ref_item *item, + int verbose, int abbrev) +{ + struct strbuf subject = STRBUF_INIT, stat = STRBUF_INIT; + const char *sub = " **** invalid ref ****"; + struct commit *commit = item->commit; + + if (commit && !parse_commit(commit)) { + pp_commit_easy(CMIT_FMT_ONELINE, commit, &subject); + 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); +} + static void print_ref_item(struct ref_item *item, int maxwidth, int verbose, int abbrev, int current, char *prefix) { @@ -435,28 +500,15 @@ static void print_ref_item(struct ref_item *item, int maxwidth, int verbose, 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); + else if (verbose) + /* " f7c0c00 [ahead 58, behind 197] vcs-svn: drop obj_pool.h" */ + add_verbose_info(&out, item, verbose, abbrev); + if (column_active(colopts)) { + assert(!verbose && "--column and --verbose are incompatible"); + string_list_append(&output, out.buf); + } else { + printf("%s\n", out.buf); } - printf("%s\n", out.buf); strbuf_release(&name); strbuf_release(&out); } @@ -480,7 +532,7 @@ static void show_detached(struct ref_list *ref_list) if (head_commit && is_descendant_of(head_commit, ref_list->with_commit)) { struct ref_item item; - item.name = xstrdup("(no branch)"); + item.name = xstrdup(_("(no branch)")); item.len = strlen(item.name); item.kind = REF_LOCAL_BRANCH; item.dest = NULL; @@ -492,7 +544,7 @@ static void show_detached(struct ref_list *ref_list) } } -static int print_ref_list(int kinds, int detached, int verbose, int abbrev, struct commit_list *with_commit) +static int print_ref_list(int kinds, int detached, int verbose, int abbrev, struct commit_list *with_commit, const char **pattern) { int i; struct append_ref_cb cb; @@ -506,11 +558,16 @@ static int print_ref_list(int kinds, int detached, int verbose, int abbrev, stru if (merge_filter != NO_FILTER) init_revisions(&ref_list.revs, NULL); cb.ref_list = &ref_list; + cb.pattern = pattern; cb.ret = 0; for_each_rawref(append_ref, &cb); if (merge_filter != NO_FILTER) { struct commit *filter; filter = lookup_commit_reference_gently(merge_filter_ref, 0); + if (!filter) + die("object '%s' does not point to a commit", + sha1_to_hex(merge_filter_ref)); + filter->object.flags |= UNINTERESTING; add_pending_object(&ref_list.revs, (struct object *) filter, ""); @@ -523,7 +580,7 @@ static int print_ref_list(int kinds, int detached, int verbose, int abbrev, stru qsort(ref_list.list, ref_list.index, sizeof(struct ref_item), ref_cmp); detached = (detached && (kinds & REF_LOCAL_BRANCH)); - if (detached) + if (detached && match_patterns(pattern, "HEAD")) show_detached(&ref_list); for (i = 0; i < ref_list.index; i++) { @@ -540,7 +597,7 @@ static int print_ref_list(int kinds, int detached, int verbose, int abbrev, stru free_ref_list(&ref_list); if (cb.ret) - error("some refs could not be read"); + error(_("some refs could not be read")); return cb.ret; } @@ -548,50 +605,52 @@ static int print_ref_list(int kinds, int detached, int verbose, int abbrev, stru 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; + int clobber_head_ok; if (!oldname) - die("cannot rename the current branch while not on any."); + 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)) + if (ref_exists(oldref.buf)) recovery = 1; else - die("Invalid branch name: '%s'", oldname); + die(_("Invalid branch name: '%s'"), oldname); } - if (strbuf_check_branch_ref(&newref, newname)) - die("Invalid branch name: '%s'", newname); + /* + * A command like "git branch -M currentbranch currentbranch" cannot + * cause the worktree to become inconsistent with HEAD, so allow it. + */ + clobber_head_ok = !strcmp(oldname, newname); - if (resolve_ref(newref.buf, sha1, 1, NULL) && !force) - die("A branch named '%s' already exists.", newref.buf + 11); + validate_new_branchname(newname, &newref, force, clobber_head_ok); 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"); + die(_("Branch rename failed")); strbuf_release(&logmsg); if (recovery) - warning("Renamed a misnamed branch '%s' away", oldref.buf + 11); + 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); + 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"); + die(_("Branch is renamed, but update of config-file failed")); strbuf_release(&oldsection); strbuf_release(&newsection); } @@ -606,28 +665,69 @@ static int opt_parse_merge_filter(const struct option *opt, const char *arg, int if (!arg) arg = "HEAD"; if (get_sha1(arg, merge_filter_ref)) - die("malformed object name %s", arg); + die(_("malformed object name %s"), arg); return 0; } +static const char edit_description[] = "BRANCH_DESCRIPTION"; + +static int edit_branch_description(const char *branch_name) +{ + FILE *fp; + int status; + struct strbuf buf = STRBUF_INIT; + struct strbuf name = STRBUF_INIT; + + read_branch_desc(&buf, branch_name); + if (!buf.len || buf.buf[buf.len-1] != '\n') + strbuf_addch(&buf, '\n'); + strbuf_addf(&buf, + "# Please edit the description for the branch\n" + "# %s\n" + "# Lines starting with '#' will be stripped.\n", + branch_name); + fp = fopen(git_path(edit_description), "w"); + if ((fwrite(buf.buf, 1, buf.len, fp) < buf.len) || fclose(fp)) { + strbuf_release(&buf); + return error(_("could not write branch description template: %s"), + strerror(errno)); + } + strbuf_reset(&buf); + if (launch_editor(git_path(edit_description), &buf, NULL)) { + strbuf_release(&buf); + return -1; + } + stripspace(&buf, 1); + + strbuf_addf(&name, "branch.%s.description", branch_name); + status = git_config_set(name.buf, buf.buf); + strbuf_release(&name); + strbuf_release(&buf); + + return status; +} + 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; + int delete = 0, rename = 0, force_create = 0, list = 0; + int verbose = 0, abbrev = -1, detached = 0; + int reflog = 0, edit_description = 0; + int quiet = 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__VERBOSE(&verbose, + "show hash and subject, give twice for upstream branch"), + OPT__QUIET(&quiet, "suppress informational messages"), 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__COLOR(&branch_use_color, "use colored output"), - OPT_SET_INT('r', NULL, &kinds, "act on remote-tracking branches", + OPT_SET_INT('r', "remotes", &kinds, "act on remote-tracking branches", REF_REMOTE_BRANCH), { OPTION_CALLBACK, 0, "contains", &with_commit, "commit", @@ -644,14 +744,17 @@ int cmd_branch(int argc, const char **argv, const char *prefix) OPT__ABBREV(&abbrev), OPT_GROUP("Specific git-branch actions:"), - OPT_SET_INT('a', NULL, &kinds, "list both remote-tracking and local branches", + OPT_SET_INT('a', "all", &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', "delete", &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', "move", &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)"), + OPT_BOOLEAN(0, "list", &list, "list branch names"), + OPT_BOOLEAN('l', "create-reflog", &reflog, "create the branch's reflog"), + OPT_BOOLEAN(0, "edit-description", &edit_description, + "edit the description for the branch"), + OPT__FORCE(&force_create, "force creation (when already exists)"), { OPTION_CALLBACK, 0, "no-merged", &merge_filter_ref, "commit", "print only not merged branches", @@ -664,47 +767,96 @@ int cmd_branch(int argc, const char **argv, const char *prefix) PARSE_OPT_LASTARG_DEFAULT | PARSE_OPT_NONEG, opt_parse_merge_filter, (intptr_t) "HEAD", }, + OPT_COLUMN(0, "column", &colopts, "list branches in columns"), OPT_END(), }; - git_config(git_branch_config, NULL); + if (argc == 2 && !strcmp(argv[1], "-h")) + usage_with_options(builtin_branch_usage, options); - if (branch_use_color == -1) - branch_use_color = git_use_color_default; + git_config(git_branch_config, NULL); track = git_branch_track; - head = resolve_ref("HEAD", head_sha1, 0, NULL); + head = resolve_refdup("HEAD", head_sha1, 0, NULL); if (!head) - die("Failed to resolve HEAD as a valid ref."); - head = xstrdup(head); + die(_("Failed to resolve HEAD as a valid ref.")); if (!strcmp(head, "HEAD")) { detached = 1; } else { if (prefixcmp(head, "refs/heads/")) - die("HEAD not found below 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) + + if (!delete && !rename && !edit_description && argc == 0) + list = 1; + + if (!!delete + !!rename + !!force_create + !!list > 1) usage_with_options(builtin_branch_usage, options); + if (abbrev == -1) + abbrev = DEFAULT_ABBREV; + finalize_colopts(&colopts, -1); + if (verbose) { + if (explicitly_enable_column(colopts)) + die(_("--column and --verbose are incompatible")); + colopts = 0; + } + if (delete) - return delete_branches(argc, argv, delete > 1, kinds); - else if (argc == 0) - return 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) { + return delete_branches(argc, argv, delete > 1, kinds, quiet); + else if (list) { + int ret = print_ref_list(kinds, detached, verbose, abbrev, + with_commit, argv); + print_columns(&output, colopts, NULL); + string_list_clear(&output, 0); + return ret; + } + else if (edit_description) { + const char *branch_name; + struct strbuf branch_ref = STRBUF_INIT; + + if (detached) + die("Cannot give description to detached HEAD"); + if (!argc) + branch_name = head; + else if (argc == 1) + branch_name = argv[0]; + else + usage_with_options(builtin_branch_usage, options); + + strbuf_addf(&branch_ref, "refs/heads/%s", branch_name); + if (!ref_exists(branch_ref.buf)) { + strbuf_release(&branch_ref); + + if (!argc) + return error("No commit on branch '%s' yet.", + branch_name); + else + return error("No such branch '%s'.", branch_name); + } + strbuf_release(&branch_ref); + + if (edit_branch_description(branch_name)) + return 1; + } else if (rename) { + if (argc == 1) + rename_branch(head, argv[0], rename > 1); + else if (argc == 2) + rename_branch(argv[0], argv[1], rename > 1); + else + usage_with_options(builtin_branch_usage, options); + } else if (argc > 0 && argc <= 2) { if (kinds != REF_LOCAL_BRANCH) - die("-a and -r options to 'git branch' do not make sense with a branch name"); + 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); + force_create, reflog, 0, quiet, track); } else usage_with_options(builtin_branch_usage, options); diff --git a/builtin/bundle.c b/builtin/bundle.c index 9b87fb9ac2..92a8a6026a 100644 --- a/builtin/bundle.c +++ b/builtin/bundle.c @@ -44,7 +44,7 @@ int cmd_bundle(int argc, const char **argv, const char *prefix) close(bundle_fd); if (verify_bundle(&header, 1)) return 1; - fprintf(stderr, "%s is okay\n", bundle_file); + fprintf(stderr, _("%s is okay\n"), bundle_file); return 0; } if (!strcmp(cmd, "list-heads")) { @@ -53,12 +53,12 @@ int cmd_bundle(int argc, const char **argv, const char *prefix) } if (!strcmp(cmd, "create")) { if (!startup_info->have_repository) - die("Need a repository to create a bundle."); + die(_("Need a repository to create a bundle.")); return !!create_bundle(&header, bundle_file, argc, argv); } else if (!strcmp(cmd, "unbundle")) { if (!startup_info->have_repository) - die("Need a repository to unbundle."); - return !!unbundle(&header, bundle_fd) || + die(_("Need a repository to unbundle.")); + return !!unbundle(&header, bundle_fd, 0) || list_bundle_refs(&header, argc, argv); } else usage(builtin_bundle_usage); diff --git a/builtin/cat-file.c b/builtin/cat-file.c index 76ec3fec92..36a9104433 100644 --- a/builtin/cat-file.c +++ b/builtin/cat-file.c @@ -11,6 +11,7 @@ #include "parse-options.h" #include "diff.h" #include "userdiff.h" +#include "streaming.h" #define BATCH 1 #define BATCH_CHECK 2 @@ -127,6 +128,8 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name) return cmd_ls_tree(2, ls_args, NULL); } + if (type == OBJ_BLOB) + return stream_blob_to_fd(1, sha1, NULL, 0); buf = read_sha1_file(sha1, &type, &size); if (!buf) die("Cannot read object %s", obj_name); @@ -143,12 +146,34 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name) die("git cat-file --textconv %s: <object> must be <sha1:path>", obj_name); - if (!textconv_object(obj_context.path, sha1, &buf, &size)) + if (!textconv_object(obj_context.path, obj_context.mode, sha1, &buf, &size)) die("git cat-file --textconv: unable to run textconv on %s", obj_name); break; case 0: + if (type_from_string(exp_type) == OBJ_BLOB) { + unsigned char blob_sha1[20]; + if (sha1_object_info(sha1, NULL) == OBJ_TAG) { + enum object_type type; + unsigned long size; + char *buffer = read_sha1_file(sha1, &type, &size); + if (memcmp(buffer, "object ", 7) || + get_sha1_hex(buffer + 7, blob_sha1)) + die("%s not a valid tag", sha1_to_hex(sha1)); + free(buffer); + } else + hashcpy(blob_sha1, sha1); + + if (sha1_object_info(blob_sha1, NULL) == OBJ_BLOB) + return stream_blob_to_fd(1, blob_sha1, NULL, 0); + /* + * we attempted to dereference a tag to a blob + * and failed; there may be new dereference + * mechanisms this code is not aware of. + * fall-back to the usual case. + */ + } buf = read_object_with_reference(sha1, exp_type, &size, NULL); break; @@ -187,6 +212,8 @@ static int batch_one_object(const char *obj_name, int print_contents) if (type <= 0) { printf("%s missing\n", obj_name); fflush(stdout); + if (print_contents == BATCH) + free(contents); return 0; } @@ -224,14 +251,8 @@ static const char * const cat_file_usage[] = { static int git_cat_file_config(const char *var, const char *value, void *cb) { - switch (userdiff_config(var, value)) { - case 0: - break; - case -1: + if (userdiff_config(var, value) < 0) return -1; - default: - return 0; - } return git_default_config(var, value, cb); } diff --git a/builtin/check-attr.c b/builtin/check-attr.c index 3016d29caa..44c421eb0f 100644 --- a/builtin/check-attr.c +++ b/builtin/check-attr.c @@ -4,28 +4,30 @@ #include "quote.h" #include "parse-options.h" +static int all_attrs; +static int cached_attrs; static int stdin_paths; static const char * const check_attr_usage[] = { -"git check-attr attr... [--] pathname...", -"git check-attr --stdin attr... < <list-of-paths>", +"git check-attr [-a | --all | attr...] [--] pathname...", +"git check-attr --stdin [-a | --all | attr...] < <list-of-paths>", NULL }; static int null_term_line; static const struct option check_attr_options[] = { + OPT_BOOLEAN('a', "all", &all_attrs, "report all attributes set on file"), + OPT_BOOLEAN(0, "cached", &cached_attrs, "use .gitattributes only from the index"), 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) +static void output_attr(int cnt, struct git_attr_check *check, + 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; @@ -37,12 +39,30 @@ static void check_attr(int cnt, struct git_attr_check *check, value = "unspecified"; quote_c_style(file, NULL, stdout, 0); - printf(": %s: %s\n", name[j], value); + printf(": %s: %s\n", git_attr_name(check[j].attr), value); } } -static void check_attr_stdin_paths(int cnt, struct git_attr_check *check, - const char** name) +static void check_attr(const char *prefix, int cnt, + struct git_attr_check *check, const char *file) +{ + char *full_path = + prefix_path(prefix, prefix ? strlen(prefix) : 0, file); + if (check != NULL) { + if (git_check_attr(full_path, cnt, check)) + die("git_check_attr died"); + output_attr(cnt, check, file); + } else { + if (git_all_attrs(full_path, &cnt, &check)) + die("git_all_attrs died"); + output_attr(cnt, check, file); + free(check); + } + free(full_path); +} + +static void check_attr_stdin_paths(const char *prefix, int cnt, + struct git_attr_check *check) { struct strbuf buf, nbuf; int line_termination = null_term_line ? 0 : '\n'; @@ -56,67 +76,99 @@ static void check_attr_stdin_paths(int cnt, struct git_attr_check *check, die("line is badly quoted"); strbuf_swap(&buf, &nbuf); } - check_attr(cnt, check, name, buf.buf); + check_attr(prefix, cnt, check, buf.buf); maybe_flush_or_die(stdout, "attribute to stdout"); } strbuf_release(&buf); strbuf_release(&nbuf); } +static NORETURN void error_with_usage(const char *msg) +{ + error("%s", msg); + usage_with_options(check_attr_usage, check_attr_options); +} + 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; + int cnt, i, doubledash, filei; + + git_config(git_default_config, 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"); } + if (cached_attrs) + git_attr_set_direction(GIT_ATTR_INDEX, NULL); + 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 + /* Process --all and/or attribute arguments: */ + if (all_attrs) { + if (doubledash >= 1) + error_with_usage("Attributes and --all both specified"); + + cnt = 0; + filei = doubledash + 1; + } else if (doubledash == 0) { + error_with_usage("No attribute specified"); + } else if (doubledash < 0) { + if (!argc) + error_with_usage("No attribute specified"); + + if (stdin_paths) { + /* Treat all arguments as attribute names. */ + cnt = argc; + filei = argc; + } else { + /* Treat exactly one argument as an attribute name. */ + cnt = 1; + filei = 1; + } + } 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); + filei = doubledash + 1; } - 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; + /* Check file argument(s): */ + if (stdin_paths) { + if (filei < argc) + error_with_usage("Can't specify files with --stdin"); + } else { + if (filei >= argc) + error_with_usage("No file specified"); + } + + if (all_attrs) { + check = NULL; + } else { + 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); + check_attr_stdin_paths(prefix, cnt, check); else { - for (i = doubledash; i < argc; i++) - check_attr(cnt, check, argv, argv[i]); + for (i = filei; i < argc; i++) + check_attr(prefix, cnt, check, 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 index ae3f28115a..28a7320271 100644 --- a/builtin/check-ref-format.c +++ b/builtin/check-ref-format.c @@ -8,29 +8,32 @@ #include "strbuf.h" static const char builtin_check_ref_format_usage[] = -"git check-ref-format [--print] <refname>\n" +"git check-ref-format [--normalize] [options] <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. + * Return a copy of refname but with leading slashes removed and runs + * of adjacent slashes replaced with single slashes. * * 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) +static char *collapse_slashes(const char *refname) { + char *ret = xmalloc(strlen(refname) + 1); char ch; - char prev = '\0'; + char prev = '/'; + char *cp = ret; - while ((ch = *src++) != '\0') { + while ((ch = *refname++) != '\0') { if (prev == '/' && ch == prev) continue; - *dst++ = ch; + *cp++ = ch; prev = ch; } - *dst = '\0'; + *cp = '\0'; + return ret; } static int check_ref_format_branch(const char *arg) @@ -45,27 +48,41 @@ static int check_ref_format_branch(const char *arg) return 0; } -static int check_ref_format_print(const char *arg) -{ - char *refname = xmalloc(strlen(arg) + 1); - - if (check_ref_format(arg)) - return 1; - collapse_slashes(refname, arg); - printf("%s\n", refname); - return 0; -} - int cmd_check_ref_format(int argc, const char **argv, const char *prefix) { + int i; + int normalize = 0; + int flags = 0; + const char *refname; + if (argc == 2 && !strcmp(argv[1], "-h")) usage(builtin_check_ref_format_usage); if (argc == 3 && !strcmp(argv[1], "--branch")) return check_ref_format_branch(argv[2]); - if (argc == 3 && !strcmp(argv[1], "--print")) - return check_ref_format_print(argv[2]); - if (argc != 2) + + for (i = 1; i < argc && argv[i][0] == '-'; i++) { + if (!strcmp(argv[i], "--normalize") || !strcmp(argv[i], "--print")) + normalize = 1; + else if (!strcmp(argv[i], "--allow-onelevel")) + flags |= REFNAME_ALLOW_ONELEVEL; + else if (!strcmp(argv[i], "--no-allow-onelevel")) + flags &= ~REFNAME_ALLOW_ONELEVEL; + else if (!strcmp(argv[i], "--refspec-pattern")) + flags |= REFNAME_REFSPEC_PATTERN; + else + usage(builtin_check_ref_format_usage); + } + if (! (i == argc - 1)) usage(builtin_check_ref_format_usage); - return !!check_ref_format(argv[1]); + + refname = argv[i]; + if (normalize) + refname = collapse_slashes(refname); + if (check_refname_format(refname, flags)) + return 1; + if (normalize) + printf("%s\n", refname); + + return 0; } diff --git a/builtin/checkout-index.c b/builtin/checkout-index.c index 65cbee0552..c16d82b7de 100644 --- a/builtin/checkout-index.c +++ b/builtin/checkout-index.c @@ -3,38 +3,6 @@ * * 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" @@ -217,9 +185,9 @@ int cmd_checkout_index(int argc, const char **argv, const char *prefix) 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__FORCE(&force, "forces overwrite of existing files"), + OPT__QUIET(&quiet, + "no warning for existing files and files not in index"), OPT_BOOLEAN('n', "no-create", ¬_new, "don't checkout new files"), { OPTION_CALLBACK, 'u', "index", &newfd, NULL, @@ -241,6 +209,9 @@ int cmd_checkout_index(int argc, const char **argv, const char *prefix) OPT_END() }; + if (argc == 2 && !strcmp(argv[1], "-h")) + usage_with_options(builtin_checkout_index_usage, + builtin_checkout_index_options); git_config(git_default_config, NULL); state.base_dir = ""; prefix_length = prefix ? strlen(prefix) : 0; diff --git a/builtin/checkout.c b/builtin/checkout.c index 9240fafb2a..e8c1b1f189 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -19,6 +19,7 @@ #include "ll-merge.h" #include "resolve-undo.h" #include "submodule.h" +#include "argv-array.h" static const char * const checkout_usage[] = { "git checkout [options] <branch>", @@ -30,8 +31,10 @@ struct checkout_opts { int quiet; int merge; int force; + int force_detach; int writeout_stage; int writeout_error; + int overwrite_ignore; /* not set by parse_options */ int branch_exists; @@ -70,7 +73,7 @@ static int update_some(const unsigned char *sha1, const char *base, int baselen, 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_flags = create_ce_flags(len, 0) | CE_UPDATE; ce->ce_mode = create_ce_mode(mode); add_cache_entry(ce, ADD_CACHE_OK_TO_ADD | ADD_CACHE_OK_TO_REPLACE); return 0; @@ -78,7 +81,10 @@ static int update_some(const unsigned char *sha1, const char *base, int baselen, static int read_tree_some(struct tree *tree, const char **pathspec) { - read_tree_recursive(tree, "", 0, 0, pathspec, update_some, NULL); + struct pathspec ps; + init_pathspec(&ps, pathspec); + read_tree_recursive(tree, "", 0, 0, &ps, update_some, NULL); + free_pathspec(&ps); /* update the index with the given tree's info * for all args, expanding wildcards, and exit @@ -103,21 +109,27 @@ static int check_stage(int stage, struct cache_entry *ce, int pos) return 0; pos++; } - return error("path '%s' does not have %s version", - ce->name, - (stage == 2) ? "our" : "their"); + if (stage == 2) + return error(_("path '%s' does not have our version"), ce->name); + else + return error(_("path '%s' does not have their version"), ce->name); } -static int check_all_stages(struct cache_entry *ce, int pos) +static int check_stages(unsigned 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); + unsigned seen = 0; + const char *name = ce->name; + + while (pos < active_nr) { + ce = active_cache[pos]; + if (strcmp(name, ce->name)) + break; + seen |= (1 << ce_stage(ce)); + pos++; + } + if ((stages & seen) != stages) + return error(_("path '%s' does not have all necessary versions"), + name); return 0; } @@ -130,9 +142,10 @@ static int checkout_stage(int stage, struct cache_entry *ce, int pos, return checkout_entry(active_cache[pos], state, NULL); pos++; } - return error("path '%s' does not have %s version", - ce->name, - (stage == 2) ? "our" : "their"); + if (stage == 2) + return error(_("path '%s' does not have our version"), ce->name); + else + return error(_("path '%s' does not have their version"), ce->name); } static int checkout_merged(int pos, struct checkout *state) @@ -143,18 +156,27 @@ static int checkout_merged(int pos, struct checkout *state) int status; unsigned char sha1[20]; mmbuffer_t result_buf; + unsigned char threeway[3][20]; + unsigned mode = 0; + + memset(threeway, 0, sizeof(threeway)); + while (pos < active_nr) { + int stage; + stage = ce_stage(ce); + if (!stage || strcmp(path, ce->name)) + break; + hashcpy(threeway[stage - 1], ce->sha1); + if (stage == 2) + mode = create_ce_mode(ce->ce_mode); + pos++; + ce = active_cache[pos]; + } + if (is_null_sha1(threeway[1]) || is_null_sha1(threeway[2])) + return error(_("path '%s' does not have necessary versions"), path); - 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); - - read_mmblob(&ancestor, active_cache[pos]->sha1); - read_mmblob(&ours, active_cache[pos+1]->sha1); - read_mmblob(&theirs, active_cache[pos+2]->sha1); + read_mmblob(&ancestor, threeway[0]); + read_mmblob(&ours, threeway[1]); + read_mmblob(&theirs, threeway[2]); /* * NEEDSWORK: re-create conflicts from merges with @@ -167,7 +189,7 @@ static int checkout_merged(int pos, struct checkout *state) free(theirs.ptr); if (status < 0 || !result_buf.ptr) { free(result_buf.ptr); - return error("path '%s': cannot merge", path); + return error(_("path '%s': cannot merge"), path); } /* @@ -184,18 +206,16 @@ static int checkout_merged(int pos, struct checkout *state) */ 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); + die(_("Unable to add merge result for '%s'"), path); + ce = make_cache_entry(mode, sha1, path, 2, 0); if (!ce) - die("make_cache_entry failed for path '%s'", path); + 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) + const char *prefix, struct checkout_opts *opts) { int pos; struct checkout state; @@ -211,7 +231,7 @@ static int checkout_paths(struct tree *source_tree, const char **pathspec, newfd = hold_locked_index(lock_file, 1); if (read_cache_preload(pathspec) < 0) - return error("corrupt index file"); + return error(_("corrupt index file")); if (source_tree) read_tree_some(source_tree, pathspec); @@ -222,10 +242,12 @@ static int checkout_paths(struct tree *source_tree, const char **pathspec, for (pos = 0; pos < active_nr; pos++) { struct cache_entry *ce = active_cache[pos]; + if (source_tree && !(ce->ce_flags & CE_UPDATE)) + continue; match_pathspec(pathspec, ce->name, ce_namelen(ce), 0, ps_matched); } - if (report_path_error(ps_matched, pathspec, 0)) + if (report_path_error(ps_matched, pathspec, prefix)) return 1; /* "checkout -m path" to recreate conflicted state */ @@ -239,14 +261,14 @@ static int checkout_paths(struct tree *source_tree, const char **pathspec, if (!ce_stage(ce)) continue; if (opts->force) { - warning("path '%s' is unmerged", ce->name); + 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); + errs |= check_stages((1<<2) | (1<<3), ce, pos); } else { errs = 1; - error("path '%s' is unmerged", ce->name); + error(_("path '%s' is unmerged"), ce->name); } pos = skip_same_name(ce, pos) - 1; } @@ -260,6 +282,8 @@ static int checkout_paths(struct tree *source_tree, const char **pathspec, state.refresh_cache = 1; for (pos = 0; pos < active_nr; pos++) { struct cache_entry *ce = active_cache[pos]; + if (source_tree && !(ce->ce_flags & CE_UPDATE)) + continue; if (match_pathspec(pathspec, ce->name, ce_namelen(ce), 0, NULL)) { if (!ce_stage(ce)) { errs |= checkout_entry(ce, &state, NULL); @@ -275,9 +299,9 @@ static int checkout_paths(struct tree *source_tree, const char **pathspec, if (write_cache(newfd, active_cache, active_nr) || commit_locked_index(lock_file)) - die("unable to write new index file"); + die(_("unable to write new index file")); - resolve_ref("HEAD", rev, 0, &flag); + read_ref_full("HEAD", rev, 0, &flag); head = lookup_commit_reference_gently(rev, 1); errs |= post_checkout_hook(head, head, 0); @@ -292,17 +316,16 @@ static void show_local_changes(struct object *head, struct diff_options *opts) rev.diffopt.flags = opts->flags; rev.diffopt.output_format |= DIFF_FORMAT_NAME_STATUS; if (diff_setup_done(&rev.diffopt) < 0) - die("diff_setup_done failed"); + 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) +static void describe_detached_head(const 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); + pp_commit_easy(CMIT_FMT_ONELINE, commit, &sb); fprintf(stderr, "%s %s... %s\n", msg, find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV), sb.buf); strbuf_release(&sb); @@ -320,7 +343,7 @@ static int reset_tree(struct tree *tree, struct checkout_opts *o, int worktree) opts.reset = 1; opts.merge = 1; opts.fn = oneway_merge; - opts.verbose_update = !o->quiet; + opts.verbose_update = !o->quiet && isatty(2); opts.src_index = &the_index; opts.dst_index = &the_index; parse_tree(tree); @@ -366,7 +389,7 @@ static int merge_working_tree(struct checkout_opts *opts, int newfd = hold_locked_index(lock_file, 1); if (read_cache_preload(NULL) < 0) - return error("corrupt index file"); + return error(_("corrupt index file")); resolve_undo_clear(); if (opts->force) { @@ -388,7 +411,7 @@ static int merge_working_tree(struct checkout_opts *opts, refresh_cache(REFRESH_QUIET); if (unmerged_cache()) { - error("you need to resolve your current index first"); + error(_("you need to resolve your current index first")); return 1; } @@ -397,14 +420,16 @@ static int merge_working_tree(struct checkout_opts *opts, topts.update = 1; topts.merge = 1; topts.gently = opts->merge && old->commit; - topts.verbose_update = !opts->quiet; + topts.verbose_update = !opts->quiet && isatty(2); topts.fn = twoway_merge; - topts.dir = xcalloc(1, sizeof(*topts.dir)); - topts.dir->flags |= DIR_SHOW_IGNORED; - topts.dir->exclude_per_dir = ".gitignore"; + if (opts->overwrite_ignore) { + topts.dir = xcalloc(1, sizeof(*topts.dir)); + topts.dir->flags |= DIR_SHOW_IGNORED; + setup_standard_excludes(topts.dir); + } tree = parse_tree_indirect(old->commit ? old->commit->object.sha1 : - (unsigned char *)EMPTY_TREE_SHA1_BIN); + 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); @@ -470,7 +495,7 @@ static int merge_working_tree(struct checkout_opts *opts, if (write_cache(newfd, active_cache, active_nr) || commit_locked_index(lock_file)) - die("unable to write new index file"); + die(_("unable to write new index file")); if (!opts->force && !opts->quiet) show_local_changes(&new->commit->object, &opts->diff_options); @@ -489,20 +514,6 @@ static void report_tracking(struct branch_info *new) 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) @@ -519,7 +530,7 @@ static void update_refs_for_switch(struct checkout_opts *opts, temp = log_all_ref_updates; log_all_ref_updates = 1; if (log_ref_setup(ref_name, log_file, sizeof(log_file))) { - fprintf(stderr, "Can not do reflog for '%s'\n", + fprintf(stderr, _("Can not do reflog for '%s'\n"), opts->new_orphan_branch); log_all_ref_updates = temp; return; @@ -530,7 +541,10 @@ static void update_refs_for_switch(struct checkout_opts *opts, else create_branch(old->name, opts->new_branch, new->name, opts->new_branch_force ? 1 : 0, - opts->new_branch_log, opts->track); + opts->new_branch_log, + opts->new_branch_force ? 1 : 0, + opts->quiet, + opts->track); new->name = opts->new_branch; setup_branch_path(new); } @@ -541,19 +555,35 @@ static void update_refs_for_switch(struct checkout_opts *opts, strbuf_addf(&msg, "checkout: moving from %s to %s", old_desc ? old_desc : "(invalid)", new->name); - if (new->path) { + if (!strcmp(new->name, "HEAD") && !new->path && !opts->force_detach) { + /* Nothing to do. */ + } else if (opts->force_detach || !new->path) { /* No longer on any branch. */ + 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(new->name); + describe_detached_head(_("HEAD is now at"), new->commit); + } + } else if (new->path) { /* Switch branches. */ 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 if (opts->new_branch) - fprintf(stderr, "Switched to%s branch '%s'\n", - opts->branch_exists ? " and reset" : " a new", - new->name); - else - fprintf(stderr, "Switched to branch '%s'\n", + if (old->path && !strcmp(new->path, old->path)) { + if (opts->new_branch_force) + fprintf(stderr, _("Reset branch '%s'\n"), + new->name); + else + fprintf(stderr, _("Already on '%s'\n"), + new->name); + } else if (opts->new_branch) { + if (opts->branch_exists) + fprintf(stderr, _("Switched to and reset branch '%s'\n"), new->name); + else + fprintf(stderr, _("Switched to a new branch '%s'\n"), new->name); + } else { + fprintf(stderr, _("Switched to branch '%s'\n"), new->name); + } } if (old->path && old->name) { char log_file[PATH_MAX], ref_file[PATH_MAX]; @@ -563,29 +593,123 @@ static void update_refs_for_switch(struct checkout_opts *opts, if (!file_exists(ref_file) && file_exists(log_file)) remove_path(log_file); } - } 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"))) + if (!opts->quiet && + (new->path || (!opts->force_detach && !strcmp(new->name, "HEAD")))) report_tracking(new); } +static int add_pending_uninteresting_ref(const char *refname, + const unsigned char *sha1, + int flags, void *cb_data) +{ + add_pending_sha1(cb_data, refname, sha1, flags | UNINTERESTING); + return 0; +} + +static void describe_one_orphan(struct strbuf *sb, struct commit *commit) +{ + parse_commit(commit); + strbuf_addstr(sb, " "); + strbuf_addstr(sb, + find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV)); + strbuf_addch(sb, ' '); + pp_commit_easy(CMIT_FMT_ONELINE, commit, sb); + strbuf_addch(sb, '\n'); +} + +#define ORPHAN_CUTOFF 4 +static void suggest_reattach(struct commit *commit, struct rev_info *revs) +{ + struct commit *c, *last = NULL; + struct strbuf sb = STRBUF_INIT; + int lost = 0; + while ((c = get_revision(revs)) != NULL) { + if (lost < ORPHAN_CUTOFF) + describe_one_orphan(&sb, c); + last = c; + lost++; + } + if (ORPHAN_CUTOFF < lost) { + int more = lost - ORPHAN_CUTOFF; + if (more == 1) + describe_one_orphan(&sb, last); + else + strbuf_addf(&sb, _(" ... and %d more.\n"), more); + } + + fprintf(stderr, + Q_( + /* The singular version */ + "Warning: you are leaving %d commit behind, " + "not connected to\n" + "any of your branches:\n\n" + "%s\n", + /* The plural version */ + "Warning: you are leaving %d commits behind, " + "not connected to\n" + "any of your branches:\n\n" + "%s\n", + /* Give ngettext() the count */ + lost), + lost, + sb.buf); + strbuf_release(&sb); + + if (advice_detached_head) + fprintf(stderr, + _( + "If you want to keep them by creating a new branch, " + "this may be a good time\nto do so with:\n\n" + " git branch new_branch_name %s\n\n"), + sha1_to_hex(commit->object.sha1)); +} + +/* + * We are about to leave commit that was at the tip of a detached + * HEAD. If it is not reachable from any ref, this is the last chance + * for the user to do so without resorting to reflog. + */ +static void orphaned_commit_warning(struct commit *old, struct commit *new) +{ + struct rev_info revs; + struct object *object = &old->object; + struct object_array refs; + + init_revisions(&revs, NULL); + setup_revisions(0, NULL, &revs, NULL); + + object->flags &= ~UNINTERESTING; + add_pending_object(&revs, object, sha1_to_hex(object->sha1)); + + for_each_ref(add_pending_uninteresting_ref, &revs); + add_pending_sha1(&revs, "HEAD", new->object.sha1, UNINTERESTING); + + refs = revs.pending; + revs.leak_pending = 1; + + if (prepare_revision_walk(&revs)) + die(_("internal error in revision walk")); + if (!(old->object.flags & UNINTERESTING)) + suggest_reattach(old, &revs); + else + describe_detached_head(_("Previous HEAD position was"), old); + + clear_commit_marks_for_object_array(&refs, ALL_REV_FLAGS); + free(refs.objects); +} + static int switch_branches(struct checkout_opts *opts, struct branch_info *new) { int ret = 0; struct branch_info old; + void *path_to_free; unsigned char rev[20]; int flag; memset(&old, 0, sizeof(old)); - old.path = resolve_ref("HEAD", rev, 0, &flag); + old.path = path_to_free = resolve_refdup("HEAD", rev, 0, &flag); old.commit = lookup_commit_reference_gently(rev, 1); if (!(flag & REF_ISSYMREF)) old.path = NULL; @@ -597,25 +721,23 @@ static int switch_branches(struct checkout_opts *opts, struct branch_info *new) new->name = "HEAD"; new->commit = old.commit; if (!new->commit) - die("You are on a branch yet to be born"); + die(_("You are on a branch yet to be born")); parse_commit(new->commit); } ret = merge_working_tree(opts, &old, new); - if (ret) + if (ret) { + free(path_to_free); 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); + orphaned_commit_warning(old.commit, new->commit); update_refs_for_switch(opts, &old, new); ret = post_checkout_hook(old.commit, new->commit, 1); + free(path_to_free); return ret || opts->writeout_error; } @@ -675,23 +797,147 @@ static const char *unique_tracking_name(const char *name) return NULL; } +static int parse_branchname_arg(int argc, const char **argv, + int dwim_new_local_branch_ok, + struct branch_info *new, + struct tree **source_tree, + unsigned char rev[20], + const char **new_branch) +{ + int argcount = 0; + unsigned char branch_rev[20]; + const char *arg; + int has_dash_dash; + + /* + * 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) + return 0; + + if (!strcmp(argv[0], "--")) /* case (2) */ + return 1; + + 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 (dwim_new_local_branch_ok && + !check_filename(NULL, arg) && + argc == 1) { + const char *remote = unique_tracking_name(arg); + if (!remote || get_sha1(remote, rev)) + return argcount; + *new_branch = arg; + arg = remote; + /* DWIMmed to create local branch */ + } else { + return argcount; + } + } + + /* we can't end up being in (2) anymore, eat the argument */ + argcount++; + argv++; + argc--; + + new->name = arg; + setup_branch_path(new); + + if (!check_refname_format(new->path, 0) && + !read_ref(new->path, branch_rev)) + hashcpy(rev, branch_rev); + else + new->path = NULL; /* not an existing branch */ + + new->commit = lookup_commit_reference_gently(rev, 1); + if (!new->commit) { + /* not a commit */ + *source_tree = parse_tree_indirect(rev); + } else { + parse_commit(new->commit); + *source_tree = new->commit->tree; + } + + 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 { + argcount++; + argv++; + argc--; + } + + return argcount; +} + +static int switch_unborn_to_new_branch(struct checkout_opts *opts) +{ + int status; + struct strbuf branch_ref = STRBUF_INIT; + + strbuf_addf(&branch_ref, "refs/heads/%s", opts->new_branch); + status = create_symref("HEAD", branch_ref.buf, "checkout -b"); + strbuf_release(&branch_ref); + return status; +} + 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__QUIET(&opts.quiet, "suppress progress reporting"), OPT_STRING('b', NULL, &opts.new_branch, "branch", "create and checkout a new branch"), OPT_STRING('B', NULL, &opts.new_branch_force, "branch", "create/reset and checkout a branch"), OPT_BOOLEAN('l', NULL, &opts.new_branch_log, "create reflog for new branch"), + OPT_BOOLEAN(0, "detach", &opts.force_detach, "detach the HEAD at named commit"), OPT_SET_INT('t', "track", &opts.track, "set upstream info for new branch", BRANCH_TRACK_EXPLICIT), OPT_STRING(0, "orphan", &opts.new_orphan_branch, "new branch", "new unparented branch"), @@ -699,8 +945,9 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) 2), OPT_SET_INT('3', "theirs", &opts.writeout_stage, "checkout their version for unmerged files", 3), - OPT_BOOLEAN('f', "force", &opts.force, "force checkout (throw away local modifications)"), + OPT__FORCE(&opts.force, "force checkout (throw away local modifications)"), OPT_BOOLEAN('m', "merge", &opts.merge, "perform a 3-way merge with the new branch"), + OPT_BOOLEAN(0, "overwrite-ignore", &opts.overwrite_ignore, "update ignored files (default)"), OPT_STRING(0, "conflict", &conflict_style, "style", "conflict style (merge or diff3)"), OPT_BOOLEAN('p', "patch", &patch_mode, "select hunks interactively"), @@ -709,10 +956,10 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) PARSE_OPT_NOARG | PARSE_OPT_HIDDEN }, OPT_END(), }; - int has_dash_dash; memset(&opts, 0, sizeof(opts)); memset(&new, 0, sizeof(new)); + opts.overwrite_ignore = 1; gitmodules_config(); git_config(git_checkout_config, &opts); @@ -724,36 +971,42 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) /* we can assume from now on new_branch = !new_branch_force */ if (opts.new_branch && opts.new_branch_force) - die("-B cannot be used with -b"); + die(_("-B cannot be used with -b")); /* copy -B over to -b, so that we can just check the latter */ if (opts.new_branch_force) opts.new_branch = opts.new_branch_force; 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"); + || opts.new_branch_log || opts.merge || opts.force + || opts.force_detach)) + die (_("--patch is incompatible with all other options")); + + if (opts.force_detach && (opts.new_branch || opts.new_orphan_branch)) + die(_("--detach cannot be used with -b/-B/--orphan")); + if (opts.force_detach && 0 < opts.track) + die(_("--detach cannot be used with -t")); /* --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"); + 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"); + die (_("Missing branch name; try -b")); opts.new_branch = argv0 + 1; } if (opts.new_orphan_branch) { if (opts.new_branch) - die("--orphan and -b|-B are mutually exclusive"); + die(_("--orphan and -b|-B are mutually exclusive")); if (opts.track > 0) - die("--orphan cannot be used with -t"); + die(_("--orphan cannot be used with -t")); opts.new_branch = opts.new_orphan_branch; } @@ -763,108 +1016,33 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) } if (opts.force && opts.merge) - die("git checkout: -f and -m are incompatible"); + 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. + * Extract branch name from command line arguments, so + * all that is left is pathspecs. * - * 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. + * Handle * - * 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. + * 1) git checkout <tree> -- [<paths>] + * 2) git checkout -- [<paths>] + * 3) git checkout <something> [<paths>] * + * including "last branch" syntax and DWIM-ery for names of + * remote branches, erroring out for invalid or ambiguous cases. */ 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--; - } + int dwim_ok = + !patch_mode && + dwim_new_local_branch && + opts.track == BRANCH_TRACK_UNSPECIFIED && + !opts.new_branch; + int n = parse_branchname_arg(argc, argv, dwim_ok, + &new, &source_tree, rev, &opts.new_branch); + argv += n; + argc -= n; } -no_reference: - if (opts.track == BRANCH_TRACK_UNSPECIFIED) opts.track = git_branch_track; @@ -872,7 +1050,7 @@ no_reference: const char **pathspec = get_pathspec(prefix, argv); if (!pathspec) - die("invalid path specification"); + die(_("invalid path specification")); if (patch_mode) return interactive_checkout(new.name, pathspec, &opts); @@ -880,16 +1058,19 @@ no_reference: /* 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]); + 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."); + die(_("git checkout: updating paths is incompatible with switching branches.")); } } + if (opts.force_detach) + die(_("git checkout: --detach does not take a path argument")); + 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."); + die(_("git checkout: --ours/--theirs, --force and --merge are incompatible when\nchecking out of the index.")); - return checkout_paths(source_tree, pathspec, &opts); + return checkout_paths(source_tree, pathspec, prefix, &opts); } if (patch_mode) @@ -897,23 +1078,27 @@ no_reference: 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)) { - opts.branch_exists = 1; - if (!opts.new_branch_force) - die("git checkout: branch %s already exists", - opts.new_branch); - } + + opts.branch_exists = validate_new_branchname(opts.new_branch, &buf, + !!opts.new_branch_force, + !!opts.new_branch_force); + strbuf_release(&buf); } if (new.name && !new.commit) { - die("Cannot switch branch to a non-commit."); + die(_("Cannot switch branch to a non-commit.")); } if (opts.writeout_stage) - die("--ours/--theirs is incompatible with switching branches."); + die(_("--ours/--theirs is incompatible with switching branches.")); + if (!new.commit && opts.new_branch) { + unsigned char rev[20]; + int flag; + + if (!read_ref_full("HEAD", rev, 0, &flag) && + (flag & REF_ISSYMREF) && is_null_sha1(rev)) + return switch_unborn_to_new_branch(&opts); + } return switch_branches(&opts, &new); } diff --git a/builtin/clean.c b/builtin/clean.c index fb24030751..0c7b3d0f4c 100644 --- a/builtin/clean.c +++ b/builtin/clean.c @@ -48,13 +48,13 @@ int cmd_clean(int argc, const char **argv, const char *prefix) const char *qname; char *seen = NULL; struct option options[] = { - OPT__QUIET(&quiet), - OPT__DRY_RUN(&show_only), - OPT_BOOLEAN('f', "force", &force, "force"), + OPT__QUIET(&quiet, "do not print names of files removed"), + OPT__DRY_RUN(&show_only, "dry run"), + OPT__FORCE(&force, "force"), OPT_BOOLEAN('d', NULL, &remove_directories, "remove whole directories"), { OPTION_CALLBACK, 'e', "exclude", &exclude_list, "pattern", - "exclude <pattern>", PARSE_OPT_NONEG, exclude_cb }, + "add <pattern> to ignore rules", PARSE_OPT_NONEG, exclude_cb }, OPT_BOOLEAN('x', NULL, &ignored, "remove ignored files, too"), OPT_BOOLEAN('X', NULL, &ignored_only, "remove only ignored files"), @@ -75,11 +75,16 @@ int cmd_clean(int argc, const char **argv, const char *prefix) 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"); + die(_("-x and -X cannot be used together")); + + if (!show_only && !force) { + if (config_set) + die(_("clean.requireForce set to true and neither -n nor -f given; " + "refusing to clean")); + else + die(_("clean.requireForce defaults to true and neither -n nor -f given; " + "refusing to clean")); + } if (force > 1) rm_flags = 0; @@ -87,13 +92,14 @@ int cmd_clean(int argc, const char **argv, const char *prefix) dir.flags |= DIR_SHOW_OTHER_DIRECTORIES; if (read_cache() < 0) - die("index file corrupt"); + die(_("index file corrupt")); if (!ignored) setup_standard_excludes(&dir); for (i = 0; i < exclude_list.nr; i++) - add_exclude(exclude_list.items[i].string, "", 0, dir.exclude_list); + add_exclude(exclude_list.items[i].string, "", 0, + &dir.exclude_list[EXC_CMDL]); pathspec = get_pathspec(prefix, argv); @@ -146,20 +152,20 @@ int cmd_clean(int argc, const char **argv, const char *prefix) qname = quote_path_relative(directory.buf, directory.len, &buf, prefix); if (show_only && (remove_directories || (matches == MATCHED_EXACTLY))) { - printf("Would remove %s\n", qname); + printf(_("Would remove %s\n"), qname); } else if (remove_directories || (matches == MATCHED_EXACTLY)) { if (!quiet) - printf("Removing %s\n", qname); + printf(_("Removing %s\n"), qname); if (remove_dir_recursively(&directory, rm_flags) != 0) { - warning("failed to remove %s", qname); + warning(_("failed to remove %s"), qname); errors++; } } else if (show_only) { - printf("Would not remove %s\n", qname); + printf(_("Would not remove %s\n"), qname); } else { - printf("Not removing %s\n", qname); + printf(_("Not removing %s\n"), qname); } strbuf_reset(&directory); } else { @@ -167,13 +173,13 @@ int cmd_clean(int argc, const char **argv, const char *prefix) continue; qname = quote_path_relative(ent->name, -1, &buf, prefix); if (show_only) { - printf("Would remove %s\n", qname); + printf(_("Would remove %s\n"), qname); continue; } else if (!quiet) { - printf("Removing %s\n", qname); + printf(_("Removing %s\n"), qname); } if (unlink(ent->name) != 0) { - warning("failed to remove %s", qname); + warning(_("failed to remove %s"), qname); errors++; } } diff --git a/builtin/clone.c b/builtin/clone.c index 19ed64041d..7f3b9823ce 100644 --- a/builtin/clone.c +++ b/builtin/clone.c @@ -8,7 +8,7 @@ * Clone a repository into a different directory that does not yet exist. */ -#include "cache.h" +#include "builtin.h" #include "parse-options.h" #include "fetch-pack.h" #include "refs.h" @@ -37,19 +37,31 @@ static const char * const builtin_clone_usage[] = { NULL }; -static int 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 int option_no_checkout, option_bare, option_mirror, option_single_branch = -1; +static int option_local = -1, option_no_hardlinks, option_shared, option_recursive; +static char *option_template, *option_depth; static char *option_origin = NULL; static char *option_branch = NULL; +static const char *real_git_dir; static char *option_upload_pack = "git-upload-pack"; static int option_verbosity; -static int option_progress; +static int option_progress = -1; +static struct string_list option_config; +static struct string_list option_reference; + +static int opt_parse_reference(const struct option *opt, const char *arg, int unset) +{ + struct string_list *option_reference = opt->value; + if (!arg) + return -1; + string_list_append(option_reference, arg); + return 0; +} static struct option builtin_clone_options[] = { OPT__VERBOSITY(&option_verbosity), - OPT_BOOLEAN(0, "progress", &option_progress, - "force progress reporting"), + OPT_BOOL(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"), @@ -58,27 +70,34 @@ static struct option builtin_clone_options[] = { 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_BOOL('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_BOOLEAN(0, "recurse-submodules", &option_recursive, + "initialize submodules in the clone"), + OPT_STRING(0, "template", &option_template, "template-directory", + "directory from which templates will be used"), + OPT_CALLBACK(0 , "reference", &option_reference, "repo", + "reference repository", &opt_parse_reference), + OPT_STRING('o', "origin", &option_origin, "name", + "use <name> 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_BOOL(0, "single-branch", &option_single_branch, + "clone only one branch, HEAD or --branch"), + OPT_STRING(0, "separate-git-dir", &real_git_dir, "gitdir", + "separate git dir from working tree"), + OPT_STRING_LIST('c', "config", &option_config, "key=value", + "set config inside the new repository"), OPT_END() }; @@ -88,7 +107,7 @@ static const char *argv_submodule[] = { static char *get_repo_path(const char *repo, int *is_bundle) { - static char *suffix[] = { "/.git", ".git", "" }; + static char *suffix[] = { "/.git", "", ".git/.git", ".git" }; static char *bundle_suffix[] = { ".bundle", "" }; struct stat st; int i; @@ -96,9 +115,26 @@ static char *get_repo_path(const char *repo, int *is_bundle) for (i = 0; i < ARRAY_SIZE(suffix); i++) { const char *path; path = mkpath("%s%s", repo, suffix[i]); - if (is_directory(path)) { + if (stat(path, &st)) + continue; + if (S_ISDIR(st.st_mode) && is_git_directory(path)) { *is_bundle = 0; - return xstrdup(make_nonrelative_path(path)); + return xstrdup(absolute_path(path)); + } else if (S_ISREG(st.st_mode) && st.st_size > 8) { + /* Is it a "gitfile"? */ + char signature[8]; + int len, fd = open(path, O_RDONLY); + if (fd < 0) + continue; + len = read_in_full(fd, signature, 8); + close(fd); + if (len != 8 || strncmp(signature, "gitdir: ", 8)) + continue; + path = read_gitfile(path); + if (path) { + *is_bundle = 0; + return xstrdup(absolute_path(path)); + } } } @@ -107,7 +143,7 @@ static char *get_repo_path(const char *repo, int *is_bundle) 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 xstrdup(absolute_path(path)); } } @@ -192,39 +228,69 @@ static void strip_trailing_slashes(char *dir) *end = '\0'; } -static void setup_reference(const char *repo) +static int add_one_reference(struct string_list_item *item, void *cb_data) { - 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); + char *ref_git; + struct strbuf alternate = STRBUF_INIT; + + /* Beware: real_path() and mkpath() return static buffer */ + ref_git = xstrdup(real_path(item->string)); + if (is_directory(mkpath("%s/.git/objects", ref_git))) { + char *ref_git_git = xstrdup(mkpath("%s/.git", ref_git)); + free(ref_git); + ref_git = ref_git_git; + } else if (!is_directory(mkpath("%s/objects", ref_git))) + die(_("reference repository '%s' is not a local directory."), + item->string); + + strbuf_addf(&alternate, "%s/objects", ref_git); + add_to_alternates_file(alternate.buf); + strbuf_release(&alternate); + free(ref_git); + return 0; +} - 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); +static void setup_reference(void) +{ + for_each_string_list(&option_reference, add_one_reference, NULL); +} - transport_disconnect(transport); +static void copy_alternates(struct strbuf *src, struct strbuf *dst, + const char *src_repo) +{ + /* + * Read from the source objects/info/alternates file + * and copy the entries to corresponding file in the + * destination repository with add_to_alternates_file(). + * Both src and dst have "$path/objects/info/alternates". + * + * Instead of copying bit-for-bit from the original, + * we need to append to existing one so that the already + * created entry via "clone -s" is not lost, and also + * to turn entries with paths relative to the original + * absolute, so that they can be used in the new repository. + */ + FILE *in = fopen(src->buf, "r"); + struct strbuf line = STRBUF_INIT; - free(ref_git_copy); + while (strbuf_getline(&line, in, '\n') != EOF) { + char *abs_path, abs_buf[PATH_MAX]; + if (!line.len || line.buf[0] == '#') + continue; + if (is_absolute_path(line.buf)) { + add_to_alternates_file(line.buf); + continue; + } + abs_path = mkpath("%s/objects/%s", src_repo, line.buf); + normalize_path_copy(abs_buf, abs_path); + add_to_alternates_file(abs_buf); + } + strbuf_release(&line); + fclose(in); } -static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest) +static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest, + const char *src_repo, int src_baselen) { struct dirent *de; struct stat buf; @@ -233,15 +299,15 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest) dir = opendir(src->buf); if (!dir) - die_errno("failed to open '%s'", src->buf); + 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); + die_errno(_("failed to create directory '%s'"), dest->buf); else if (stat(dest->buf, &buf)) - die_errno("failed to stat '%s'", dest->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); + die(_("%s exists and is not a directory"), dest->buf); } strbuf_addch(src, '/'); @@ -255,56 +321,56 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest) strbuf_setlen(dest, dest_len); strbuf_addstr(dest, de->d_name); if (stat(src->buf, &buf)) { - warning ("failed to stat %s\n", src->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); + copy_or_link_directory(src, dest, + src_repo, src_baselen); + continue; + } + + /* Files that cannot be copied bit-for-bit... */ + if (!strcmp(src->buf + src_baselen, "/info/alternates")) { + copy_alternates(src, dest, src_repo); continue; } if (unlink(dest->buf) && errno != ENOENT) - die_errno("failed to unlink '%s'", dest->buf); + 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); + if (option_local > 0) + 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); + 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) +static void 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 { + if (option_shared) { + struct strbuf alt = STRBUF_INIT; + strbuf_addf(&alt, "%s/objects", src_repo); + add_to_alternates_file(alt.buf); + strbuf_release(&alt); + } else { + struct strbuf src = STRBUF_INIT; + struct strbuf dest = STRBUF_INIT; strbuf_addf(&src, "%s/objects", src_repo); strbuf_addf(&dest, "%s/objects", dest_repo); - copy_or_link_directory(&src, &dest); + copy_or_link_directory(&src, &dest, src_repo, src.len); 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); if (0 <= option_verbosity) - printf("done.\n"); - return ret; + printf(_("done.\n")); } static const char *junk_work_tree; @@ -335,14 +401,54 @@ static void remove_junk_on_signal(int signo) raise(signo); } +static struct ref *find_remote_branch(const struct ref *refs, const char *branch) +{ + struct ref *ref; + struct strbuf head = STRBUF_INIT; + strbuf_addstr(&head, "refs/heads/"); + strbuf_addstr(&head, branch); + ref = find_ref_by_name(refs, head.buf); + strbuf_release(&head); + + if (ref) + return ref; + + strbuf_addstr(&head, "refs/tags/"); + strbuf_addstr(&head, branch); + ref = find_ref_by_name(refs, head.buf); + strbuf_release(&head); + + return ref; +} + static struct ref *wanted_peer_refs(const struct ref *refs, struct refspec *refspec) { - struct ref *local_refs = NULL; - struct ref **tail = &local_refs; + struct ref *head = copy_ref(find_ref_by_name(refs, "HEAD")); + struct ref *local_refs = head; + struct ref **tail = head ? &head->next : &local_refs; - get_fetch_map(refs, refspec, &tail, 0); - if (!option_mirror) + if (option_single_branch) { + struct ref *remote_head = NULL; + + if (!option_branch) + remote_head = guess_remote_head(head, refs, 0); + else + remote_head = find_remote_branch(refs, option_branch); + + if (!remote_head && option_branch) + warning(_("Could not find remote branch %s to clone."), + option_branch); + else { + get_fetch_map(remote_head, refspec, &tail, 0); + + /* if --branch=tag, pull the requested tag explicitly */ + get_fetch_map(remote_head, tag_refspec, &tail, 0); + } + } else + get_fetch_map(refs, refspec, &tail, 0); + + if (!option_mirror && !option_single_branch) get_fetch_map(refs, tag_refspec, &tail, 0); return local_refs; @@ -352,11 +458,153 @@ 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); + for (r = local_refs; r; r = r->next) { + if (!r->peer_ref) + continue; + add_packed_ref(r->peer_ref->name, r->old_sha1); + } pack_refs(PACK_REFS_ALL); - clear_extra_refs(); +} + +static void write_followtags(const struct ref *refs, const char *msg) +{ + const struct ref *ref; + for (ref = refs; ref; ref = ref->next) { + if (prefixcmp(ref->name, "refs/tags/")) + continue; + if (!suffixcmp(ref->name, "^{}")) + continue; + if (!has_sha1_file(ref->old_sha1)) + continue; + update_ref(msg, ref->name, ref->old_sha1, + NULL, 0, DIE_ON_ERR); + } +} + +static void update_remote_refs(const struct ref *refs, + const struct ref *mapped_refs, + const struct ref *remote_head_points_at, + const char *branch_top, + const char *msg) +{ + if (refs) { + write_remote_refs(mapped_refs); + if (option_single_branch) + write_followtags(refs, msg); + } + + if (remote_head_points_at && !option_bare) { + struct strbuf head_ref = STRBUF_INIT; + strbuf_addstr(&head_ref, branch_top); + strbuf_addstr(&head_ref, "HEAD"); + create_symref(head_ref.buf, + remote_head_points_at->peer_ref->name, + msg); + } +} + +static void update_head(const struct ref *our, const struct ref *remote, + const char *msg) +{ + if (our && !prefixcmp(our->name, "refs/heads/")) { + /* Local default branch link */ + create_symref("HEAD", our->name, NULL); + if (!option_bare) { + const char *head = skip_prefix(our->name, "refs/heads/"); + update_ref(msg, "HEAD", our->old_sha1, NULL, 0, DIE_ON_ERR); + install_branch_config(0, head, option_origin, our->name); + } + } else if (our) { + struct commit *c = lookup_commit_reference(our->old_sha1); + /* --branch specifies a non-branch (i.e. tags), detach HEAD */ + update_ref(msg, "HEAD", c->object.sha1, + NULL, REF_NODEREF, DIE_ON_ERR); + } else if (remote) { + /* + * We know remote HEAD points to a non-branch, or + * HEAD points to a branch but we don't know which one. + * Detach HEAD in all these cases. + */ + update_ref(msg, "HEAD", remote->old_sha1, + NULL, REF_NODEREF, DIE_ON_ERR); + } +} + +static int checkout(void) +{ + unsigned char sha1[20]; + char *head; + struct lock_file *lock_file; + struct unpack_trees_options opts; + struct tree *tree; + struct tree_desc t; + int err = 0, fd; + + if (option_no_checkout) + return 0; + + head = resolve_refdup("HEAD", sha1, 1, NULL); + if (!head) { + warning(_("remote HEAD refers to nonexistent ref, " + "unable to checkout.\n")); + return 0; + } + if (!strcmp(head, "HEAD")) { + if (advice_detached_head) + detach_advice(sha1_to_hex(sha1)); + } else { + if (prefixcmp(head, "refs/heads/")) + die(_("HEAD not found below refs/heads!")); + } + free(head); + + /* We need to be in the new work tree for the checkout */ + setup_work_tree(); + + lock_file = xcalloc(1, sizeof(struct lock_file)); + 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_verbosity >= 0); + opts.src_index = &the_index; + opts.dst_index = &the_index; + + tree = parse_tree_indirect(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(sha1), "1", NULL); + + if (!err && option_recursive) + err = run_command_v_opt(argv_submodule, RUN_GIT_CMD); + + return err; +} + +static int write_one_config(const char *key, const char *value, void *data) +{ + return git_config_set_multivar(key, value ? value : "true", "^$", 0); +} + +static void write_config(struct string_list *config) +{ + int i; + + for (i = 0; i < config->nr; i++) { + if (git_config_parse_parameter(config->items[i].string, + write_one_config, NULL) < 0) + die("unable to write parameters to config file"); + } } int cmd_clone(int argc, const char **argv, const char *prefix) @@ -370,34 +618,40 @@ int cmd_clone(int argc, const char **argv, const char *prefix) const struct ref *remote_head_points_at; const struct ref *our_head_points_at; struct ref *mapped_refs; + const struct ref *ref; 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; + const char *src_ref_prefix = "refs/heads/"; + struct remote *remote; + int err = 0, complete_refs_before_fetch = 1; struct refspec *refspec; const char *fetch_pattern; junk_pid = getpid(); + packet_trace_identity("clone"); argc = parse_options(argc, argv, prefix, builtin_clone_options, builtin_clone_usage, 0); if (argc > 2) - usage_msg_opt("Too many arguments.", + 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.", + usage_msg_opt(_("You must specify a repository to clone."), builtin_clone_usage, builtin_clone_options); + if (option_single_branch == -1) + option_single_branch = option_depth ? 1 : 0; + if (option_mirror) option_bare = 1; if (option_bare) { if (option_origin) - die("--bare and --origin %s options are incompatible.", + die(_("--bare and --origin %s options are incompatible."), option_origin); option_no_checkout = 1; } @@ -409,14 +663,14 @@ int cmd_clone(int argc, const char **argv, const char *prefix) path = get_repo_path(repo_name, &is_bundle); if (path) - repo = xstrdup(make_nonrelative_path(repo_name)); + repo = xstrdup(absolute_path(repo_name)); else if (!strchr(repo_name, ':')) - repo = xstrdup(make_absolute_path(repo_name)); + die(_("repository '%s' does not exist"), repo_name); else repo = repo_name; - is_local = path && !is_bundle; + is_local = option_local != 0 && path && !is_bundle; if (is_local && option_depth) - warning("--depth is ignored in local clones; use file:// instead."); + warning(_("--depth is ignored in local clones; use file:// instead.")); if (argc == 2) dir = xstrdup(argv[1]); @@ -426,8 +680,8 @@ int cmd_clone(int argc, const char **argv, const char *prefix) 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); + die(_("destination path '%s' already exists and is not " + "an empty directory."), dir); strbuf_addf(&reflog_msg, "clone: from %s", repo); @@ -436,7 +690,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix) else { work_tree = getenv("GIT_WORK_TREE"); if (work_tree && !stat(work_tree, &buf)) - die("working tree '%s' already exists.", work_tree); + die(_("working tree '%s' already exists."), work_tree); } if (option_bare || work_tree) @@ -449,10 +703,10 @@ int cmd_clone(int argc, const char **argv, const char *prefix) 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'", + 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'.", + die_errno(_("could not create work tree dir '%s'."), work_tree); set_git_work_tree(work_tree); } @@ -463,13 +717,20 @@ int cmd_clone(int argc, const char **argv, const char *prefix) 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)); + die(_("could not create leading directories of '%s'"), git_dir); - if (0 <= option_verbosity) - printf("Cloning into %s%s...\n", - option_bare ? "bare repository " : "", dir); + set_git_dir_init(git_dir, real_git_dir, 0); + if (real_git_dir) + git_dir = real_git_dir; + + if (0 <= option_verbosity) { + if (option_bare) + printf(_("Cloning into bare repository '%s'...\n"), dir); + else + printf(_("Cloning into '%s'...\n"), dir); + } init_db(option_template, INIT_DB_QUIET); + write_config(&option_config); /* * At this point, the config exists, so we do not need the @@ -509,72 +770,77 @@ int cmd_clone(int argc, const char **argv, const char *prefix) git_config_set(key.buf, repo); strbuf_reset(&key); - if (option_reference) - setup_reference(git_dir); + if (option_reference.nr) + setup_reference(); fetch_pattern = value.buf; refspec = parse_fetch_refspec(1, &fetch_pattern); strbuf_reset(&value); - if (is_local) { - refs = clone_local(path, git_dir); - mapped_refs = wanted_peer_refs(refs, refspec); - } else { - struct remote *remote = remote_get(option_origin); - transport = transport_get(remote, remote->url[0]); + remote = remote_get(option_origin); + transport = transport_get(remote, remote->url[0]); + if (!is_local) { if (!transport->get_refs_list || !transport->fetch) - die("Don't know how to clone %s", transport->url); + 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_single_branch) + transport_set_option(transport, TRANS_OPT_FOLLOWTAGS, "1"); transport_set_verbosity(transport, option_verbosity, option_progress); 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); - } } + refs = transport_get_remote_refs(transport); + if (refs) { - clear_extra_refs(); + mapped_refs = wanted_peer_refs(refs, refspec); + /* + * transport_get_remote_refs() may return refs with null sha-1 + * in mapped_refs (see struct transport->get_refs_list + * comment). In that case we need fetch it early because + * remote_head code below relies on it. + * + * for normal clones, transport_get_remote_refs() should + * return reliable ref set, we can delay cloning until after + * remote HEAD check. + */ + for (ref = refs; ref; ref = ref->next) + if (is_null_sha1(ref->old_sha1)) { + complete_refs_before_fetch = 0; + break; + } - write_remote_refs(mapped_refs); + if (!is_local && !complete_refs_before_fetch) + transport_fetch_refs(transport, 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; - } + find_remote_branch(mapped_refs, option_branch); + + if (!our_head_points_at) + die(_("Remote branch %s not found in upstream %s"), + option_branch, option_origin); } else our_head_points_at = remote_head_points_at; } else { - warning("You appear to have cloned an empty repository."); + warning(_("You appear to have cloned an empty repository.")); + mapped_refs = NULL; our_head_points_at = NULL; remote_head_points_at = NULL; remote_head = NULL; @@ -584,84 +850,20 @@ int cmd_clone(int argc, const char **argv, const char *prefix) "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 (is_local) + clone_local(path, git_dir); + else if (refs && complete_refs_before_fetch) + transport_fetch_refs(transport, mapped_refs); - 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; - } + update_remote_refs(refs, mapped_refs, remote_head_points_at, + branch_top.buf, reflog_msg.buf); - if (transport) { - transport_unlock_pack(transport); - transport_disconnect(transport); - } + update_head(our_head_points_at, remote_head, reflog_msg.buf); - 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_verbosity > 0); - 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); - } + transport_unlock_pack(transport); + transport_disconnect(transport); + + err = checkout(); strbuf_release(&reflog_msg); strbuf_release(&branch_top); diff --git a/builtin/column.c b/builtin/column.c new file mode 100644 index 0000000000..5ea798a7ca --- /dev/null +++ b/builtin/column.c @@ -0,0 +1,59 @@ +#include "builtin.h" +#include "cache.h" +#include "strbuf.h" +#include "parse-options.h" +#include "string-list.h" +#include "column.h" + +static const char * const builtin_column_usage[] = { + "git column [options]", + NULL +}; +static unsigned int colopts; + +static int column_config(const char *var, const char *value, void *cb) +{ + return git_column_config(var, value, cb, &colopts); +} + +int cmd_column(int argc, const char **argv, const char *prefix) +{ + struct string_list list = STRING_LIST_INIT_DUP; + struct strbuf sb = STRBUF_INIT; + struct column_options copts; + const char *command = NULL, *real_command = NULL; + struct option options[] = { + OPT_STRING(0, "command", &real_command, "name", "lookup config vars"), + OPT_COLUMN(0, "mode", &colopts, "layout to use"), + OPT_INTEGER(0, "raw-mode", &colopts, "layout to use"), + OPT_INTEGER(0, "width", &copts.width, "Maximum width"), + OPT_STRING(0, "indent", &copts.indent, "string", "Padding space on left border"), + OPT_INTEGER(0, "nl", &copts.nl, "Padding space on right border"), + OPT_INTEGER(0, "padding", &copts.padding, "Padding space between columns"), + OPT_END() + }; + + /* This one is special and must be the first one */ + if (argc > 1 && !prefixcmp(argv[1], "--command=")) { + command = argv[1] + 10; + git_config(column_config, (void *)command); + } else + git_config(column_config, NULL); + + memset(&copts, 0, sizeof(copts)); + copts.width = term_columns(); + copts.padding = 1; + argc = parse_options(argc, argv, "", options, builtin_column_usage, 0); + if (argc) + usage_with_options(builtin_column_usage, options); + if (real_command || command) { + if (!real_command || !command || strcmp(real_command, command)) + die(_("--command must be the first argument")); + } + finalize_colopts(&colopts, -1); + while (!strbuf_getline(&sb, stdin, '\n')) + string_list_append(&list, sb.buf); + + print_columns(&list, colopts, &copts); + return 0; +} diff --git a/builtin/commit-tree.c b/builtin/commit-tree.c index d083795e26..164b655df9 100644 --- a/builtin/commit-tree.c +++ b/builtin/commit-tree.c @@ -8,8 +8,9 @@ #include "tree.h" #include "builtin.h" #include "utf8.h" +#include "gpg-interface.h" -static const char commit_tree_usage[] = "git commit-tree <sha1> [(-p <sha1>)...] < changelog"; +static const char commit_tree_usage[] = "git commit-tree [(-p <sha1>)...] [-S<signer>] [-m <message>] [-F <file>] <sha1> <changelog"; static void new_parent(struct commit *parent, struct commit_list **parents_p) { @@ -25,38 +26,98 @@ static void new_parent(struct commit *parent, struct commit_list **parents_p) commit_list_insert(parent, parents_p); } +static int commit_tree_config(const char *var, const char *value, void *cb) +{ + int status = git_gpg_config(var, value, NULL); + if (status) + return status; + return git_default_config(var, value, cb); +} + int cmd_commit_tree(int argc, const char **argv, const char *prefix) { - int i; + int i, got_tree = 0; struct commit_list *parents = NULL; unsigned char tree_sha1[20]; unsigned char commit_sha1[20]; struct strbuf buffer = STRBUF_INIT; + const char *sign_commit = NULL; - git_config(git_default_config, NULL); + git_config(commit_tree_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); - assert_sha1_type(sha1, OBJ_COMMIT); - new_parent(lookup_commit(sha1), &parents); + for (i = 1; i < argc; i++) { + const char *arg = argv[i]; + if (!strcmp(arg, "-p")) { + unsigned char sha1[20]; + if (argc <= ++i) + usage(commit_tree_usage); + if (get_sha1(argv[i], sha1)) + die("Not a valid object name %s", argv[i]); + assert_sha1_type(sha1, OBJ_COMMIT); + new_parent(lookup_commit(sha1), &parents); + continue; + } + + if (!memcmp(arg, "-S", 2)) { + sign_commit = arg + 2; + continue; + } + + if (!strcmp(arg, "-m")) { + if (argc <= ++i) + usage(commit_tree_usage); + if (buffer.len) + strbuf_addch(&buffer, '\n'); + strbuf_addstr(&buffer, argv[i]); + strbuf_complete_line(&buffer); + continue; + } + + if (!strcmp(arg, "-F")) { + int fd; + + if (argc <= ++i) + usage(commit_tree_usage); + if (buffer.len) + strbuf_addch(&buffer, '\n'); + if (!strcmp(argv[i], "-")) + fd = 0; + else { + fd = open(argv[i], O_RDONLY); + if (fd < 0) + die_errno("git commit-tree: failed to open '%s'", + argv[i]); + } + if (strbuf_read(&buffer, fd, 0) < 0) + die_errno("git commit-tree: failed to read '%s'", + argv[i]); + if (fd && close(fd)) + die_errno("git commit-tree: failed to close '%s'", + argv[i]); + strbuf_complete_line(&buffer); + continue; + } + + if (get_sha1(arg, tree_sha1)) + die("Not a valid object name %s", arg); + if (got_tree) + die("Cannot give more than one trees"); + got_tree = 1; } - if (strbuf_read(&buffer, 0, 0) < 0) - die_errno("git commit-tree: failed to read"); + if (!buffer.len) { + 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)) { + if (commit_tree(&buffer, tree_sha1, parents, commit_sha1, + NULL, sign_commit)) { strbuf_release(&buffer); return 1; } diff --git a/builtin/commit.c b/builtin/commit.c index 66fdd22024..f43eaafb3b 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -26,6 +26,8 @@ #include "unpack-trees.h" #include "quote.h" #include "submodule.h" +#include "gpg-interface.h" +#include "column.h" static const char * const builtin_commit_usage[] = { "git commit [options] [--] <filepattern>...", @@ -38,25 +40,31 @@ static const char * const builtin_status_usage[] = { }; static const char implicit_ident_advice[] = -"Your name and email address were configured automatically based\n" +N_("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" +"After doing this, you may fix the identity used for this commit with:\n" "\n" -" git commit --amend --author='Your Name <you@example.com>'\n"; +" git commit --amend --reset-author\n"); static const char empty_amend_advice[] = -"You asked to amend the most recent commit, but doing so would make\n" +N_("You asked to amend the most recent commit, but doing so would make\n" "it empty. You can repeat your command with --allow-empty, or you can\n" -"remove the commit entirely with \"git reset HEAD^\".\n"; +"remove the commit entirely with \"git reset HEAD^\".\n"); -static unsigned char head_sha1[20]; +static const char empty_cherry_pick_advice[] = +N_("The previous cherry-pick is now empty, possibly due to conflict resolution.\n" +"If you wish to commit it anyway, use:\n" +"\n" +" git commit --allow-empty\n" +"\n" +"Otherwise, please use 'git reset'\n"); -static char *use_message_buffer; +static const 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 */ @@ -68,12 +76,20 @@ static enum { static const char *logfile, *force_author; static const char *template_file; +/* + * The _message variables are commit names from which to take + * the commit message and/or authorship. + */ +static const char *author_message, *author_message_buffer; 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 char *fixup_message, *squash_message; +static int all, also, interactive, patch_interactive, only, amend, signoff; +static int edit_flag = -1; /* unspecified */ static int quiet, verbose, no_verify, allow_empty, dry_run, renew_authorship; static int no_post_rewrite, allow_empty_message; static char *untracked_files_arg, *force_date, *ignore_submodule_arg; +static char *sign_commit; + /* * The default commit message cleanup mode will remove the lines * beginning with # (shell comments) and leading and trailing @@ -88,18 +104,17 @@ static enum { } cleanup_mode; static char *cleanup_arg; -static int use_editor = 1, initial_commit, in_merge, include_status = 1; +static enum commit_whence whence; +static int use_editor = 1, include_status = 1; static int show_ignored_in_status; static const char *only_include_assumed; -static struct strbuf message; +static struct strbuf message = STRBUF_INIT; -static int null_termination; static enum { STATUS_FORMAT_LONG, STATUS_FORMAT_SHORT, STATUS_FORMAT_PORCELAIN } status_format = STATUS_FORMAT_LONG; -static int status_show_branch; static int opt_parse_m(const struct option *opt, const char *arg, int unset) { @@ -113,53 +128,17 @@ static int opt_parse_m(const struct option *opt, const char *arg, int unset) 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_BOOLEAN(0, "branch", &status_show_branch, "show branch information"), - 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"), - OPT_BOOLEAN(0, "no-post-rewrite", &no_post_rewrite, "bypass post-rewrite hook"), - { 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" }, - /* end commit contents options */ - - { OPTION_BOOLEAN, 0, "allow-empty", &allow_empty, NULL, - "ok to record an empty change", - PARSE_OPT_NOARG | PARSE_OPT_HIDDEN }, - { OPTION_BOOLEAN, 0, "allow-empty-message", &allow_empty_message, NULL, - "ok to record a change with an empty message", - PARSE_OPT_NOARG | PARSE_OPT_HIDDEN }, - - OPT_END() -}; +static void determine_whence(struct wt_status *s) +{ + if (file_exists(git_path("MERGE_HEAD"))) + whence = FROM_MERGE; + else if (file_exists(git_path("CHERRY_PICK_HEAD"))) + whence = FROM_CHERRY_PICK; + else + whence = FROM_COMMIT; + if (s) + s->whence = whence; +} static void rollback_index_files(void) { @@ -209,8 +188,11 @@ static int list_paths(struct string_list *list, const char *with_tree, ; m = xcalloc(1, i); - if (with_tree) - overlay_tree_on_cache(with_tree, prefix); + if (with_tree) { + char *max_prefix = common_prefix(pattern); + overlay_tree_on_cache(with_tree, max_prefix ? max_prefix : prefix); + free(max_prefix); + } for (i = 0; i < active_nr; i++) { struct cache_entry *ce = active_cache[i]; @@ -225,7 +207,7 @@ static int list_paths(struct string_list *list, const char *with_tree, item->util = item; /* better a valid pointer than a fake one */ } - return report_path_error(m, pattern, prefix ? strlen(prefix) : 0); + return report_path_error(m, pattern, prefix); } static void add_remove_files(struct string_list *list) @@ -241,19 +223,19 @@ static void add_remove_files(struct string_list *list) if (!lstat(p->string, &st)) { if (add_to_cache(p->string, &st, 0)) - die("updating files failed"); + die(_("updating files failed")); } else remove_file_from_cache(p->string); } } -static void create_base_index(void) +static void create_base_index(const struct commit *current_head) { struct tree *tree; struct unpack_trees_options opts; struct tree_desc t; - if (initial_commit) { + if (!current_head) { discard_cache(); return; } @@ -266,9 +248,9 @@ static void create_base_index(void) opts.dst_index = &the_index; opts.fn = oneway_merge; - tree = parse_tree_indirect(head_sha1); + tree = parse_tree_indirect(current_head->object.sha1); if (!tree) - die("failed to unpack HEAD tree object"); + die(_("failed to unpack HEAD tree object")); parse_tree(tree); init_tree_desc(&t, tree->buffer, tree->size); if (unpack_trees(1, &t, &opts)) @@ -285,29 +267,50 @@ static void refresh_cache_or_die(int refresh_flags) die_resolve_conflict("commit"); } -static char *prepare_index(int argc, const char **argv, const char *prefix, int is_status) +static char *prepare_index(int argc, const char **argv, const char *prefix, + const struct commit *current_head, int is_status) { int fd; struct string_list partial; const char **pathspec = NULL; + char *old_index_env = 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"); + die(_("index file corrupt")); + + if (interactive) { + fd = hold_locked_index(&index_lock, 1); + + refresh_cache_or_die(refresh_flags); + + if (write_cache(fd, active_cache, active_nr) || + close_lock_file(&index_lock)) + die(_("unable to create temporary index")); + + old_index_env = getenv(INDEX_ENVIRONMENT); + setenv(INDEX_ENVIRONMENT, index_lock.filename, 1); + + if (interactive_add(argc, argv, prefix, patch_interactive) != 0) + die(_("interactive add failed")); + + if (old_index_env && *old_index_env) + setenv(INDEX_ENVIRONMENT, old_index_env, 1); + else + unsetenv(INDEX_ENVIRONMENT); + + discard_cache(); + read_cache_from(index_lock.filename); + + commit_style = COMMIT_NORMAL; + return index_lock.filename; + } /* * Non partial, non as-is commit. @@ -325,9 +328,10 @@ static char *prepare_index(int argc, const char **argv, const char *prefix, int fd = hold_locked_index(&index_lock, 1); add_files_to_cache(also ? prefix : NULL, pathspec, 0); refresh_cache_or_die(refresh_flags); + update_main_cache_tree(WRITE_TREE_SILENT); if (write_cache(fd, active_cache, active_nr) || close_lock_file(&index_lock)) - die("unable to write new_index file"); + die(_("unable to write new_index file")); commit_style = COMMIT_NORMAL; return index_lock.filename; } @@ -345,9 +349,10 @@ static char *prepare_index(int argc, const char **argv, const char *prefix, int fd = hold_locked_index(&index_lock, 1); refresh_cache_or_die(refresh_flags); if (active_cache_changed) { + update_main_cache_tree(WRITE_TREE_SILENT); if (write_cache(fd, active_cache, active_nr) || commit_locked_index(&index_lock)) - die("unable to write new_index file"); + die(_("unable to write new_index file")); } else { rollback_lock_file(&index_lock); } @@ -376,37 +381,41 @@ static char *prepare_index(int argc, const char **argv, const char *prefix, int */ commit_style = COMMIT_PARTIAL; - if (in_merge) - die("cannot do a partial commit during a merge."); + if (whence != FROM_COMMIT) { + if (whence == FROM_MERGE) + die(_("cannot do a partial commit during a merge.")); + else if (whence == FROM_CHERRY_PICK) + die(_("cannot do a partial commit during a cherry-pick.")); + } memset(&partial, 0, sizeof(partial)); partial.strdup_strings = 1; - if (list_paths(&partial, initial_commit ? NULL : "HEAD", prefix, pathspec)) + if (list_paths(&partial, !current_head ? NULL : "HEAD", prefix, pathspec)) exit(1); discard_cache(); if (read_cache() < 0) - die("cannot read the index"); + 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"); + 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(); + create_base_index(current_head); 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"); + die(_("unable to write temporary index file")); discard_cache(); read_cache_from(false_lock.filename); @@ -436,10 +445,10 @@ static int run_status(FILE *fp, const char *index_file, const char *prefix, int switch (status_format) { case STATUS_FORMAT_SHORT: - wt_shortstatus_print(s, null_termination, status_show_branch); + wt_shortstatus_print(s); break; case STATUS_FORMAT_PORCELAIN: - wt_porcelain_print(s, null_termination); + wt_porcelain_print(s); break; case STATUS_FORMAT_LONG: wt_status_print(s); @@ -449,36 +458,45 @@ static int run_status(FILE *fp, const char *index_file, const char *prefix, int return s->commitable; } -static int is_a_merge(const unsigned char *sha1) +static int is_a_merge(const struct commit *current_head) { - struct commit *commit = lookup_commit(sha1); - if (!commit || parse_commit(commit)) - die("could not parse HEAD commit"); - return !!(commit->parents && commit->parents->next); + return !!(current_head->parents && current_head->parents->next); } static const char sign_off_header[] = "Signed-off-by: "; -static void determine_author_info(void) +static void export_one(const char *var, const char *s, const char *e, int hack) +{ + struct strbuf buf = STRBUF_INIT; + if (hack) + strbuf_addch(&buf, hack); + strbuf_addf(&buf, "%.*s", (int)(e - s), s); + setenv(var, buf.buf, 1); + strbuf_release(&buf); +} + +static void determine_author_info(struct strbuf *author_ident) { char *name, *email, *date; + struct ident_split author; name = getenv("GIT_AUTHOR_NAME"); email = getenv("GIT_AUTHOR_EMAIL"); date = getenv("GIT_AUTHOR_DATE"); - if (use_message && !renew_authorship) { + if (author_message) { const char *a, *lb, *rb, *eol; + size_t len; - a = strstr(use_message_buffer, "\nauthor "); + a = strstr(author_message_buffer, "\nauthor "); if (!a) - die("invalid commit: %s", use_message); + die(_("invalid commit: %s"), author_message); lb = strchrnul(a + strlen("\nauthor "), '<'); rb = strchrnul(lb, '>'); eol = strchrnul(rb, '\n'); if (!*lb || !*rb || !*eol) - die("invalid commit: %s", use_message); + die(_("invalid commit: %s"), author_message); if (lb == a + strlen("\nauthor ")) /* \nauthor <foo@example.com> */ @@ -489,6 +507,11 @@ static void determine_author_info(void) (a + strlen("\nauthor ")))); email = xmemdupz(lb + strlen("<"), rb - (lb + strlen("<"))); date = xmemdupz(rb + strlen("> "), eol - (rb + strlen("> "))); + len = eol - (rb + strlen("> ")); + date = xmalloc(len + 2); + *date = '@'; + memcpy(date + 1, rb + strlen("> "), len); + date[len + 1] = '\0'; } if (force_author) { @@ -496,17 +519,19 @@ static void determine_author_info(void) const char *rb = strchr(force_author, '>'); if (!lb || !rb) - die("malformed --author parameter"); + 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; + strbuf_addstr(author_ident, fmt_ident(name, email, date, IDENT_STRICT)); + if (!split_ident_line(&author, author_ident->buf, author_ident->len)) { + export_one("GIT_AUTHOR_NAME", author.name_begin, author.name_end, 0); + export_one("GIT_AUTHOR_EMAIL", author.mail_begin, author.mail_end, 0); + export_one("GIT_AUTHOR_DATE", author.date_begin, author.tz_end, '@'); + } } static int ends_rfc2822_footer(struct strbuf *sb) @@ -550,68 +575,127 @@ static int ends_rfc2822_footer(struct strbuf *sb) return 1; } +static char *cut_ident_timestamp_part(char *string) +{ + char *ket = strrchr(string, '>'); + if (!ket || ket[1] != ' ') + die(_("Malformed ident string: '%s'"), string); + *++ket = '\0'; + return ket; +} + static int prepare_to_commit(const char *index_file, const char *prefix, - struct wt_status *s) + struct commit *current_head, + struct wt_status *s, + struct strbuf *author_ident) { struct stat statbuf; + struct strbuf committer_ident = STRBUF_INIT; 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; + int clean_message_contents = (cleanup_mode != CLEANUP_NONE); + + /* This checks and barfs if author is badly specified */ + determine_author_info(author_ident); if (!no_verify && run_hook(index_file, "pre-commit", NULL)) return 0; + if (squash_message) { + /* + * Insert the proper subject line before other commit + * message options add their content. + */ + if (use_message && !strcmp(use_message, squash_message)) + strbuf_addstr(&sb, "squash! "); + else { + struct pretty_print_context ctx = {0}; + struct commit *c; + c = lookup_commit_reference_by_name(squash_message); + if (!c) + die(_("could not lookup commit %s"), squash_message); + ctx.output_encoding = get_commit_output_encoding(); + format_commit_message(c, "squash! %s\n\n", &sb, + &ctx); + } + } + 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"); + 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"); + 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'", + 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"); + die(_("commit has empty message")); strbuf_add(&sb, buffer + 2, strlen(buffer + 2)); hook_arg1 = "commit"; hook_arg2 = use_message; + } else if (fixup_message) { + struct pretty_print_context ctx = {0}; + struct commit *commit; + commit = lookup_commit_reference_by_name(fixup_message); + if (!commit) + die(_("could not lookup commit %s"), fixup_message); + ctx.output_encoding = get_commit_output_encoding(); + format_commit_message(commit, "fixup! %s\n\n", + &sb, &ctx); + hook_arg1 = "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"); + 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"); + die_errno(_("could not read SQUASH_MSG")); hook_arg1 = "squash"; - } else if (template_file && !stat(template_file, &statbuf)) { + } else if (template_file) { if (strbuf_read_file(&sb, template_file, 0) < 0) - die_errno("could not read '%s'", template_file); + die_errno(_("could not read '%s'"), template_file); hook_arg1 = "template"; + clean_message_contents = 0; } /* - * This final case does not modify the template message, - * it just sets the argument to the prepare-commit-msg hook. + * The remaining cases don't modify the template message, but + * just set the argument(s) to the prepare-commit-msg hook. */ - else if (in_merge) + else if (whence == FROM_MERGE) hook_arg1 = "merge"; + else if (whence == FROM_CHERRY_PICK) { + hook_arg1 = "commit"; + hook_arg2 = "CHERRY_PICK_HEAD"; + } + + if (squash_message) { + /* + * If squash_commit was used for the commit subject, + * then we're possibly hijacking other commit log options. + * Reset the hook args to tell the real story. + */ + hook_arg1 = "message"; + hook_arg2 = ""; + } - fp = fopen(git_path(commit_editmsg), "w"); - if (fp == NULL) - die_errno("could not open '%s'", git_path(commit_editmsg)); + s->fp = fopen(git_path(commit_editmsg), "w"); + if (s->fp == NULL) + die_errno(_("could not open '%s'"), git_path(commit_editmsg)); - if (cleanup_mode != CLEANUP_NONE) + if (clean_message_contents) stripspace(&sb, 0); if (signoff) { @@ -632,77 +716,81 @@ static int prepare_to_commit(const char *index_file, const char *prefix, strbuf_release(&sob); } - if (fwrite(sb.buf, 1, sb.len, fp) < sb.len) - die_errno("could not write commit template"); + if (fwrite(sb.buf, 1, sb.len, s->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); + strbuf_addstr(&committer_ident, 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."); + char *ai_tmp, *ci_tmp; + if (whence != FROM_COMMIT) + status_printf_ln(s, GIT_COLOR_NORMAL, + whence == FROM_MERGE + ? _("\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" + "It looks like you may be committing a cherry-pick.\n" + "If this is not correct, please remove the file\n" + " %s\n" + "and try again.\n"), + git_path(whence == FROM_MERGE + ? "MERGE_HEAD" + : "CHERRY_PICK_HEAD")); + + fprintf(s->fp, "\n"); if (cleanup_mode == CLEANUP_ALL) - fprintf(fp, - " Lines starting\n" - "# with '#' will be ignored, and an empty" - " message aborts the commit.\n"); + status_printf(s, GIT_COLOR_NORMAL, + _("Please enter the commit message for your changes." + " Lines starting\nwith '#' will be ignored, and an empty" + " message aborts the commit.\n")); else /* CLEANUP_SPACE, that is. */ - fprintf(fp, + status_printf(s, GIT_COLOR_NORMAL, + _("Please enter the commit message for your changes." " Lines starting\n" - "# with '#' will be kept; you may remove them" + "with '#' will be kept; you may remove them" " yourself if you want to.\n" - "# An empty message aborts the commit.\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); + status_printf_ln(s, GIT_COLOR_NORMAL, + "%s", only_include_assumed); + + ai_tmp = cut_ident_timestamp_part(author_ident->buf); + ci_tmp = cut_ident_timestamp_part(committer_ident.buf); + if (strcmp(author_ident->buf, committer_ident.buf)) + status_printf_ln(s, GIT_COLOR_NORMAL, + _("%s" + "Author: %s"), + ident_shown++ ? "" : "\n", + author_ident->buf); if (!user_ident_sufficiently_given()) - fprintf(fp, - "%s" - "# Committer: %s\n", - ident_shown++ ? "" : "#\n", - committer_ident); + status_printf_ln(s, GIT_COLOR_NORMAL, + _("%s" + "Committer: %s"), + ident_shown++ ? "" : "\n", + committer_ident.buf); if (ident_shown) - fprintf(fp, "#\n"); + status_printf_ln(s, GIT_COLOR_NORMAL, ""); saved_color_setting = s->use_color; s->use_color = 0; - commitable = run_status(fp, index_file, prefix, 1, s); + commitable = run_status(s->fp, index_file, prefix, 1, s); s->use_color = saved_color_setting; + + *ai_tmp = ' '; + *ci_tmp = ' '; } else { unsigned char sha1[20]; const char *parent = "HEAD"; if (!active_nr && read_cache() < 0) - die("Cannot read index"); + die(_("Cannot read index")); if (amend) parent = "HEAD^1"; @@ -712,14 +800,22 @@ static int prepare_to_commit(const char *index_file, const char *prefix, else commitable = index_differs_from(parent, 0); } + strbuf_release(&committer_ident); - fclose(fp); + fclose(s->fp); - if (!commitable && !in_merge && !allow_empty && - !(amend && is_a_merge(head_sha1))) { + /* + * Reject an attempt to record a non-merge empty commit without + * explicit --allow-empty. In the cherry-pick case, it may be + * empty due to conflict resolution, which the user should okay. + */ + if (!commitable && whence != FROM_MERGE && !allow_empty && + !(amend && is_a_merge(current_head))) { run_status(stdout, index_file, prefix, 0, s); if (amend) - fputs(empty_amend_advice, stderr); + fputs(_(empty_amend_advice), stderr); + else if (whence == FROM_CHERRY_PICK) + fputs(_(empty_cherry_pick_advice), stderr); return 0; } @@ -730,11 +826,8 @@ static int prepare_to_commit(const char *index_file, const char *prefix, */ 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"); + if (update_main_cache_tree(0)) { + error(_("Error building trees")); return 0; } @@ -749,7 +842,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix, 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"); + _("Please supply the message using either -m or -F option.\n")); exit(1); } } @@ -762,27 +855,10 @@ static int prepare_to_commit(const char *index_file, const char *prefix, 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) +static int rest_is_empty(struct strbuf *sb, int start) { - struct strbuf tmpl = STRBUF_INIT; + int i, eol; 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++) { @@ -805,6 +881,40 @@ static int message_is_empty(struct strbuf *sb) 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) +{ + if (cleanup_mode == CLEANUP_NONE && sb->len) + return 0; + return rest_is_empty(sb, 0); +} + +/* + * See if the user edited the message in the editor or left what + * was in the template intact + */ +static int template_untouched(struct strbuf *sb) +{ + struct strbuf tmpl = STRBUF_INIT; + char *start; + + if (cleanup_mode == CLEANUP_NONE && sb->len) + return 0; + + if (!template_file || strbuf_read_file(&tmpl, template_file, 0) <= 0) + return 0; + + stripspace(&tmpl, cleanup_mode == CLEANUP_ALL); + start = (char *)skip_prefix(sb->buf, tmpl.buf); + if (!start) + start = sb->buf; + strbuf_release(&tmpl); + return rest_is_empty(sb, start - sb->buf); +} + static const char *find_author_by_nickname(const char *name) { struct rev_info revs; @@ -829,7 +939,7 @@ static const char *find_author_by_nickname(const char *name) format_commit_message(commit, "%an <%ae>", &buf, &ctx); return strbuf_detach(&buf, NULL); } - die("No existing author found with '%s'", name); + die(_("No existing author found with '%s'"), name); } @@ -844,103 +954,109 @@ static void handle_untracked_files_arg(struct wt_status *s) 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); + die(_("Invalid untracked files mode '%s'"), untracked_files_arg); +} + +static const char *read_commit_message(const char *name) +{ + const char *out_enc, *out; + struct commit *commit; + + commit = lookup_commit_reference_by_name(name); + if (!commit) + die(_("could not lookup commit %s"), name); + out_enc = get_commit_output_encoding(); + out = logmsg_reencode(commit, out_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 (out == NULL) + out = xstrdup(commit->buffer); + return out; } static int parse_and_validate_options(int argc, const char *argv[], + const struct option *options, const char * const usage[], const char *prefix, + struct commit *current_head, struct wt_status *s) { int f = 0; - argc = parse_options(argc, argv, prefix, builtin_commit_options, usage, - 0); + argc = parse_options(argc, argv, prefix, 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"); + die(_("Using both --reset-author and --author does not make sense")); - if (logfile || message.len || use_message) + if (logfile || message.len || use_message || fixup_message) use_editor = 0; - if (edit_flag) - use_editor = 1; + if (0 <= edit_flag) + use_editor = edit_flag; 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 (amend && !current_head) + die(_("You have nothing to amend.")); + if (amend && whence != FROM_COMMIT) { + if (whence == FROM_MERGE) + die(_("You are in the middle of a merge -- cannot amend.")); + else if (whence == FROM_CHERRY_PICK) + die(_("You are in the middle of a cherry-pick -- cannot amend.")); + } + if (fixup_message && squash_message) + die(_("Options --squash and --fixup cannot be used together")); if (use_message) f++; if (edit_message) f++; + if (fixup_message) + f++; if (logfile) f++; if (f > 1) - die("Only one of -c/-C/-F can be used."); + die(_("Only one of -c/-C/-F/--fixup can be used.")); if (message.len && f > 0) - die("Option -m cannot be combined with -c/-C/-F."); + die((_("Option -m cannot be combined with -c/-C/-F/--fixup."))); + if (f || message.len) + template_file = NULL; if (edit_message) use_message = edit_message; - if (amend && !use_message) + if (amend && !use_message && !fixup_message) use_message = "HEAD"; - if (!use_message && renew_authorship) - die("--reset-author can be used only with -C, -c or --amend."); + if (!use_message && whence != FROM_CHERRY_PICK && 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; + use_message_buffer = read_commit_message(use_message); + if (!renew_authorship) { + author_message = use_message; + author_message_buffer = use_message_buffer; } - 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 (whence == FROM_CHERRY_PICK && !renew_authorship) { + author_message = "CHERRY_PICK_HEAD"; + author_message_buffer = read_commit_message(author_message); } + if (patch_interactive) + interactive = 1; + if (!!also + !!only + !!all + !!interactive > 1) - die("Only one of --include/--only/--all/--interactive can be used."); + die(_("Only one of --include/--only/--all/--interactive/--patch can be used.")); if (argc == 0 && (also || (only && !amend))) - die("No paths with --include/--only does not make sense."); + 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."; + 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..."; + 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")) @@ -950,16 +1066,14 @@ static int parse_and_validate_options(int argc, const char *argv[], else if (!strcmp(cleanup_arg, "strip")) cleanup_mode = CLEANUP_ALL; else - die("Invalid cleanup mode %s", cleanup_arg); + 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."); + die(_("Paths with -a does not make sense.")); - if (null_termination && status_format == STATUS_FORMAT_LONG) + if (s->null_termination && status_format == STATUS_FORMAT_LONG) status_format = STATUS_FORMAT_PORCELAIN; if (status_format != STATUS_FORMAT_LONG) dry_run = 1; @@ -968,12 +1082,12 @@ static int parse_and_validate_options(int argc, const char *argv[], } static int dry_run_commit(int argc, const char **argv, const char *prefix, - struct wt_status *s) + const struct commit *current_head, struct wt_status *s) { int commitable; const char *index_file; - index_file = prepare_index(argc, argv, prefix, 1); + index_file = prepare_index(argc, argv, prefix, current_head, 1); commitable = run_status(stdout, index_file, prefix, 0, s); rollback_index_files(); @@ -984,6 +1098,8 @@ static int parse_status_slot(const char *var, int offset) { if (!strcasecmp(var+offset, "header")) return WT_STATUS_HEADER; + if (!strcasecmp(var+offset, "branch")) + return WT_STATUS_ONBRANCH; if (!strcasecmp(var+offset, "updated") || !strcasecmp(var+offset, "added")) return WT_STATUS_UPDATED; @@ -1002,6 +1118,8 @@ static int git_status_config(const char *k, const char *v, void *cb) { struct wt_status *s = cb; + if (!prefixcmp(k, "column.")) + return git_column_config(k, v, "status", &s->colopts); if (!strcmp(k, "status.submodulesummary")) { int is_bool; s->submodule_summary = git_config_bool_or_int(k, v, &is_bool); @@ -1010,7 +1128,7 @@ static int git_status_config(const char *k, const char *v, void *cb) return 0; } if (!strcmp(k, "status.color") || !strcmp(k, "color.status")) { - s->use_color = git_config_colorbool(k, v, -1); + s->use_color = git_config_colorbool(k, v); return 0; } if (!prefixcmp(k, "status.color.") || !prefixcmp(k, "color.status.")) { @@ -1036,7 +1154,7 @@ static int git_status_config(const char *k, const char *v, void *cb) else if (!strcmp(v, "all")) s->show_untracked_files = SHOW_ALL_UNTRACKED_FILES; else - return error("Invalid untracked files mode '%s'", v); + return error(_("Invalid untracked files mode '%s'"), v); return 0; } return git_diff_ui_config(k, v, NULL); @@ -1044,19 +1162,19 @@ static int git_status_config(const char *k, const char *v, void *cb) int cmd_status(int argc, const char **argv, const char *prefix) { - struct wt_status s; + static struct wt_status s; int fd; unsigned char sha1[20]; static struct option builtin_status_options[] = { - OPT__VERBOSE(&verbose), + OPT__VERBOSE(&verbose, "be verbose"), OPT_SET_INT('s', "short", &status_format, "show status concisely", STATUS_FORMAT_SHORT), - OPT_BOOLEAN('b', "branch", &status_show_branch, + OPT_BOOLEAN('b', "branch", &s.show_branch, "show branch information"), OPT_SET_INT(0, "porcelain", &status_format, - "show porcelain output format", + "machine-readable output", STATUS_FORMAT_PORCELAIN), - OPT_BOOLEAN('z', "null", &null_termination, + OPT_BOOLEAN('z', "null", &s.null_termination, "terminate entries with NUL"), { OPTION_STRING, 'u', "untracked-files", &untracked_files_arg, "mode", @@ -1067,19 +1185,25 @@ int cmd_status(int argc, const char **argv, const char *prefix) { OPTION_STRING, 0, "ignore-submodules", &ignore_submodule_arg, "when", "ignore changes to submodules, optional when: all, dirty, untracked. (Default: all)", PARSE_OPT_OPTARG, NULL, (intptr_t)"all" }, + OPT_COLUMN(0, "column", &s.colopts, "list untracked files in columns"), OPT_END(), }; - if (null_termination && status_format == STATUS_FORMAT_LONG) - status_format = STATUS_FORMAT_PORCELAIN; + if (argc == 2 && !strcmp(argv[1], "-h")) + usage_with_options(builtin_status_usage, builtin_status_options); wt_status_prepare(&s); gitmodules_config(); git_config(git_status_config, &s); - in_merge = file_exists(git_path("MERGE_HEAD")); + determine_whence(&s); argc = parse_options(argc, argv, prefix, builtin_status_options, builtin_status_usage, 0); + finalize_colopts(&s.colopts, -1); + + if (s.null_termination && status_format == STATUS_FORMAT_LONG) + status_format = STATUS_FORMAT_PORCELAIN; + handle_untracked_files_arg(&s); if (show_ignored_in_status) s.show_ignored_files = 1; @@ -1090,32 +1214,22 @@ int cmd_status(int argc, const char **argv, const char *prefix) refresh_index(&the_index, REFRESH_QUIET|REFRESH_UNMERGED, s.pathspec, NULL, NULL); fd = hold_locked_index(&index_lock, 0); - if (0 <= fd) { - if (active_cache_changed && - !write_cache(fd, active_cache, active_nr)) - commit_locked_index(&index_lock); - else - rollback_lock_file(&index_lock); - } + if (0 <= fd) + update_index_if_able(&the_index, &index_lock); s.is_initial = get_sha1(s.reference, sha1) ? 1 : 0; - s.in_merge = in_merge; s.ignore_submodule_arg = ignore_submodule_arg; 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, status_show_branch); + wt_shortstatus_print(&s); break; case STATUS_FORMAT_PORCELAIN: - wt_porcelain_print(&s, null_termination); + wt_porcelain_print(&s); break; case STATUS_FORMAT_LONG: s.verbose = verbose; @@ -1126,22 +1240,23 @@ int cmd_status(int argc, const char **argv, const char *prefix) return 0; } -static void print_summary(const char *prefix, const unsigned char *sha1) +static void print_summary(const char *prefix, const unsigned char *sha1, + int initial_commit) { 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); + const char *head; 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"); + die(_("couldn't look up newly created commit")); if (!commit || parse_commit(commit)) - die("could not parse newly created commit"); + die(_("could not parse newly created commit")); strbuf_addstr(&format, "format:%h] %s"); @@ -1156,7 +1271,7 @@ static void print_summary(const char *prefix, const unsigned char *sha1) strbuf_addbuf_percentquote(&format, &committer_ident); if (advice_implicit_identity) { strbuf_addch(&format, '\n'); - strbuf_addstr(&format, implicit_ident_advice); + strbuf_addstr(&format, _(implicit_ident_advice)); } } strbuf_release(&author_ident); @@ -1174,17 +1289,17 @@ static void print_summary(const char *prefix, const unsigned char *sha1) 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); + head = resolve_ref_unsafe("HEAD", junk_sha1, 0, NULL); printf("[%s%s ", !prefixcmp(head, "refs/heads/") ? head + 11 : !strcmp(head, "HEAD") ? - "detached HEAD" : + _("detached HEAD") : head, - initial_commit ? " (root-commit)" : ""); + initial_commit ? _(" (root-commit)") : ""); if (!log_tree_commit(&rev, commit)) { rev.always_show_header = 1; @@ -1198,6 +1313,7 @@ static void print_summary(const char *prefix, const unsigned char *sha1) static int git_commit_config(const char *k, const char *v, void *cb) { struct wt_status *s = cb; + int status; if (!strcmp(k, "commit.template")) return git_config_pathname(&template_file, k, v); @@ -1206,6 +1322,9 @@ static int git_commit_config(const char *k, const char *v, void *cb) return 0; } + status = git_gpg_config(k, v, NULL); + if (status) + return status; return git_status_config(k, v, s); } @@ -1245,78 +1364,138 @@ static int run_rewrite_hook(const unsigned char *oldsha1, int cmd_commit(int argc, const char **argv, const char *prefix) { + static struct wt_status s; + static struct option builtin_commit_options[] = { + OPT__QUIET(&quiet, "suppress summary after successful commit"), + OPT__VERBOSE(&verbose, "show diff in commit message template"), + + OPT_GROUP("Commit message options"), + OPT_FILENAME('F', "file", &logfile, "read message 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", "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_STRING(0, "fixup", &fixup_message, "commit", "use autosquash formatted message to fixup specified commit"), + OPT_STRING(0, "squash", &squash_message, "commit", "use autosquash formatted message to squash 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_BOOL('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"), + { OPTION_STRING, 'S', "gpg-sign", &sign_commit, "key id", + "GPG sign commit", PARSE_OPT_OPTARG, NULL, (intptr_t) "" }, + /* 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('p', "patch", &patch_interactive, "interactively add changes"), + 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_BOOLEAN(0, "branch", &s.show_branch, "show branch information"), + OPT_SET_INT(0, "porcelain", &status_format, + "machine-readable output", STATUS_FORMAT_PORCELAIN), + OPT_BOOLEAN('z', "null", &s.null_termination, + "terminate entries with NUL"), + OPT_BOOLEAN(0, "amend", &amend, "amend previous commit"), + OPT_BOOLEAN(0, "no-post-rewrite", &no_post_rewrite, "bypass post-rewrite hook"), + { 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" }, + /* end commit contents options */ + + { OPTION_BOOLEAN, 0, "allow-empty", &allow_empty, NULL, + "ok to record an empty change", + PARSE_OPT_NOARG | PARSE_OPT_HIDDEN }, + { OPTION_BOOLEAN, 0, "allow-empty-message", &allow_empty_message, NULL, + "ok to record a change with an empty message", + PARSE_OPT_NOARG | PARSE_OPT_HIDDEN }, + + OPT_END() + }; + struct strbuf sb = STRBUF_INIT; + struct strbuf author_ident = STRBUF_INIT; const char *index_file, *reflog_msg; char *nl, *p; - unsigned char commit_sha1[20]; + unsigned char 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; + struct commit *current_head = NULL; + struct commit_extra_header *extra = NULL; + + if (argc == 2 && !strcmp(argv[1], "-h")) + usage_with_options(builtin_commit_usage, builtin_commit_options); 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); + determine_whence(&s); + s.colopts = 0; + + if (get_sha1("HEAD", sha1)) + current_head = NULL; + else { + current_head = lookup_commit_or_die(sha1, "HEAD"); + if (!current_head || parse_commit(current_head)) + die(_("could not parse HEAD commit")); } - index_file = prepare_index(argc, argv, prefix, 0); + argc = parse_and_validate_options(argc, argv, builtin_commit_options, + builtin_commit_usage, + prefix, current_head, &s); + if (dry_run) + return dry_run_commit(argc, argv, prefix, current_head, &s); + index_file = prepare_index(argc, argv, prefix, current_head, 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)) { + if (!prepare_to_commit(index_file, prefix, + current_head, &s, &author_ident)) { rollback_index_files(); return 1; } /* Determine parents */ reflog_msg = getenv("GIT_REFLOG_ACTION"); - if (initial_commit) { + if (!current_head) { if (!reflog_msg) reflog_msg = "commit (initial)"; } else if (amend) { struct commit_list *c; - struct commit *commit; if (!reflog_msg) 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) + for (c = current_head->parents; c; c = c->next) pptr = &commit_list_insert(c->item, pptr)->next; - } else if (in_merge) { + } else if (whence == FROM_MERGE) { struct strbuf m = STRBUF_INIT; FILE *fp; if (!reflog_msg) reflog_msg = "commit (merge)"; - pptr = &commit_list_insert(lookup_commit(head_sha1), pptr)->next; + pptr = &commit_list_insert(current_head, pptr)->next; fp = fopen(git_path("MERGE_HEAD"), "r"); if (fp == NULL) - die_errno("could not open '%s' for reading", + 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; + struct commit *parent; + + parent = get_merge_parent(m.buf); + if (!parent) + die(_("Corrupt MERGE_HEAD file (%s)"), m.buf); + pptr = &commit_list_insert(parent, 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"); + die_errno(_("could not read MERGE_MODE")); if (!strcmp(sb.buf, "no-ff")) allow_fast_forward = 0; } @@ -1324,8 +1503,10 @@ int cmd_commit(int argc, const char **argv, const char *prefix) parents = reduce_heads(parents); } else { if (!reflog_msg) - reflog_msg = "commit"; - pptr = &commit_list_insert(lookup_commit(head_sha1), pptr)->next; + reflog_msg = (whence == FROM_CHERRY_PICK) + ? "commit (cherry-pick)" + : "commit"; + pptr = &commit_list_insert(current_head, pptr)->next; } /* Finally, get the commit message */ @@ -1333,7 +1514,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix) 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)); + die(_("could not read commit message: %s"), strerror(saved_errno)); } /* Truncate the message just before the diff, if any. */ @@ -1345,21 +1526,37 @@ int cmd_commit(int argc, const char **argv, const char *prefix) if (cleanup_mode != CLEANUP_NONE) stripspace(&sb, cleanup_mode == CLEANUP_ALL); + if (template_untouched(&sb) && !allow_empty_message) { + rollback_index_files(); + fprintf(stderr, _("Aborting commit; you did not edit the message.\n")); + exit(1); + } if (message_is_empty(&sb) && !allow_empty_message) { rollback_index_files(); - fprintf(stderr, "Aborting commit due to empty commit message.\n"); + 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))) { + if (amend) { + const char *exclude_gpgsig[2] = { "gpgsig", NULL }; + extra = read_commit_extra_headers(current_head, exclude_gpgsig); + } else { + struct commit_extra_header **tail = &extra; + append_merge_tag_headers(parents, &tail); + } + + if (commit_tree_extended(&sb, active_cache_tree->sha1, parents, sha1, + author_ident.buf, sign_commit, extra)) { rollback_index_files(); - die("failed to write commit object"); + die(_("failed to write commit object")); } + strbuf_release(&author_ident); + free_commit_extra_headers(extra); ref_lock = lock_any_ref_for_update("HEAD", - initial_commit ? NULL : head_sha1, + !current_head + ? NULL + : current_head->object.sha1, 0); nl = strchr(sb.buf, '\n'); @@ -1372,22 +1569,24 @@ int cmd_commit(int argc, const char **argv, const char *prefix) if (!ref_lock) { rollback_index_files(); - die("cannot lock HEAD ref"); + die(_("cannot lock HEAD ref")); } - if (write_ref_sha1(ref_lock, commit_sha1, sb.buf) < 0) { + if (write_ref_sha1(ref_lock, sha1, sb.buf) < 0) { rollback_index_files(); - die("cannot update HEAD ref"); + die(_("cannot update HEAD ref")); } + unlink(git_path("CHERRY_PICK_HEAD")); + unlink(git_path("REVERT_HEAD")); 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" + 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."); + "not exceeded, and then \"git reset HEAD\" to recover.")); rerere(0); run_hook(get_index_file(), "post-commit", NULL); @@ -1395,13 +1594,14 @@ int cmd_commit(int argc, const char **argv, const char *prefix) struct notes_rewrite_cfg *cfg; cfg = init_copy_notes_for_rewrite("amend"); if (cfg) { - copy_note_for_rewrite(cfg, head_sha1, commit_sha1); + /* we are amending, so current_head is not NULL */ + copy_note_for_rewrite(cfg, current_head->object.sha1, sha1); finish_copy_notes_for_rewrite(cfg); } - run_rewrite_hook(head_sha1, commit_sha1); + run_rewrite_hook(current_head->object.sha1, sha1); } if (!quiet) - print_summary(prefix, commit_sha1); + print_summary(prefix, sha1, !current_head); return 0; } diff --git a/builtin/config.c b/builtin/config.c index ca4a0db4a7..33c8820af6 100644 --- a/builtin/config.c +++ b/builtin/config.c @@ -25,6 +25,7 @@ static const char *given_config_file; static int actions, types; static const char *get_color_slot, *get_colorbool_slot; static int end_null; +static int respect_includes = -1; #define ACTION_GET (1<<0) #define ACTION_GET_ALL (1<<1) @@ -52,7 +53,7 @@ static struct option builtin_config_options[] = { OPT_BOOLEAN(0, "global", &use_global_config, "use global config file"), OPT_BOOLEAN(0, "system", &use_system_config, "use system config file"), OPT_BOOLEAN(0, "local", &use_local_config, "use repository config file"), - OPT_STRING('f', "file", &given_config_file, "FILE", "use given 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), @@ -74,6 +75,7 @@ static struct option builtin_config_options[] = { 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_BOOL(0, "includes", &respect_includes, "respect include directives on lookup"), OPT_END(), }; @@ -99,6 +101,7 @@ static int show_config(const char *key_, const char *value_, void *cb) const char *vptr = value; int must_free_vptr = 0; int dup_error = 0; + int must_print_delim = 0; if (!use_key_regexp && strcmp(key_, key)) return 0; @@ -109,10 +112,8 @@ static int show_config(const char *key_, const char *value_, void *cb) return 0; if (show_keys) { - if (value_) - printf("%s%c", key_, key_delim); - else - printf("%s", key_); + printf("%s", key_); + must_print_delim = 1; } if (seen && !do_all) dup_error = 1; @@ -130,16 +131,23 @@ static int show_config(const char *key_, const char *value_, void *cb) } else if (types == TYPE_PATH) { git_config_pathname(&vptr, key_, value_); must_free_vptr = 1; + } else if (value_) { + vptr = value_; + } else { + /* Just show the key name */ + vptr = ""; + must_print_delim = 0; } - else - vptr = value_?value_:""; seen++; if (dup_error) { error("More than one value for the key %s: %s", key_, vptr); } - else + else { + if (must_print_delim) + printf("%c", key_delim); 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 @@ -153,32 +161,48 @@ static int show_config(const char *key_, const char *value_, void *cb) 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; + struct config_include_data inc = CONFIG_INCLUDE_INIT; + config_fn_t fn; + void *data; - local = config_exclusive_filename; + local = given_config_file; if (!local) { const char *home = getenv("HOME"); local = repo_config = git_pathdup("config"); - if (git_config_global() && home) + if (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) { + char *tl; + + /* + * NEEDSWORK: this naive pattern lowercasing obviously does not + * work for more complex patterns like "^[^.]*Foo.*bar". + * Perhaps we should deprecate this altogether someday. + */ + + 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); + key_regexp = (regex_t*)xmalloc(sizeof(regex_t)); if (regcomp(key_regexp, key, REG_EXTENDED)) { fprintf(stderr, "Invalid key pattern: %s\n", key_); + free(key); goto free_strings; } + } else { + if (git_config_parse_key(key_, &key, NULL)) + goto free_strings; } if (regex_) { @@ -194,19 +218,28 @@ static int get_value(const char *key_, const char *regex_) } } + fn = show_config; + data = NULL; + if (respect_includes) { + inc.fn = fn; + inc.data = data; + fn = git_config_include; + data = &inc; + } + if (do_all && system_wide) - git_config_from_file(show_config, system_wide, NULL); + git_config_from_file(fn, system_wide, data); if (do_all && global) - git_config_from_file(show_config, global, NULL); + git_config_from_file(fn, global, data); if (do_all) - git_config_from_file(show_config, local, NULL); - git_config_from_parameters(show_config, NULL); + git_config_from_file(fn, local, data); + git_config_from_parameters(fn, data); if (!do_all && !seen) - git_config_from_file(show_config, local, NULL); + git_config_from_file(fn, local, data); if (!do_all && !seen && global) - git_config_from_file(show_config, global, NULL); + git_config_from_file(fn, global, data); if (!do_all && !seen && system_wide) - git_config_from_file(show_config, system_wide, NULL); + git_config_from_file(fn, system_wide, data); free(key); if (regexp) { @@ -282,7 +315,8 @@ static void get_color(const char *def_color) { get_color_found = 0; parsed_color[0] = '\0'; - git_config(git_get_color_config, NULL); + git_config_with_options(git_get_color_config, NULL, + given_config_file, respect_includes); if (!get_color_found && def_color) color_parse(def_color, "command line", parsed_color); @@ -290,24 +324,18 @@ static void get_color(const char *def_color) fputs(parsed_color, stdout); } -static int stdout_is_tty; static int get_colorbool_found; static int get_diff_color_found; +static int get_color_ui_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; - } + if (!strcmp(var, get_colorbool_slot)) + get_colorbool_found = git_config_colorbool(var, value); + else if (!strcmp(var, "diff.color")) + get_diff_color_found = git_config_colorbool(var, value); + else if (!strcmp(var, "color.ui")) + get_color_ui_found = git_config_colorbool(var, value); return 0; } @@ -315,15 +343,18 @@ static int get_colorbool(int print) { get_colorbool_found = -1; get_diff_color_found = -1; - git_config(git_get_colorbool_config, NULL); + git_config_with_options(git_get_colorbool_config, NULL, + given_config_file, respect_includes); 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; + get_colorbool_found = get_color_ui_found; } + get_colorbool_found = want_color(get_colorbool_found); + if (print) { printf("%s\n", get_colorbool_found ? "true" : "false"); return 0; @@ -336,7 +367,7 @@ int cmd_config(int argc, const char **argv, const char *prefix) int nongit = !startup_info->have_repository; char *value; - config_exclusive_filename = getenv(CONFIG_ENVIRONMENT); + given_config_file = getenv(CONFIG_ENVIRONMENT); argc = parse_options(argc, argv, prefix, builtin_config_options, builtin_config_usage, @@ -351,24 +382,26 @@ int cmd_config(int argc, const char **argv, const char *prefix) char *home = getenv("HOME"); if (home) { char *user_config = xstrdup(mkpath("%s/.gitconfig", home)); - config_exclusive_filename = user_config; + given_config_file = user_config; } else { die("$HOME not set"); } } else if (use_system_config) - config_exclusive_filename = git_etc_gitconfig(); + given_config_file = git_etc_gitconfig(); else if (use_local_config) - config_exclusive_filename = git_pathdup("config"); + given_config_file = git_pathdup("config"); 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; + given_config_file = + xstrdup(prefix_filename(prefix, + strlen(prefix), + given_config_file)); } + if (respect_includes == -1) + respect_includes = !given_config_file; + if (end_null) { term = '\0'; delim = '\n'; @@ -405,42 +438,52 @@ int cmd_config(int argc, const char **argv, const char *prefix) if (actions == ACTION_LIST) { check_argc(argc, 0, 0); - if (git_config(show_all_config, NULL) < 0) { - if (config_exclusive_filename) + if (git_config_with_options(show_all_config, NULL, + given_config_file, + respect_includes) < 0) { + if (given_config_file) die_errno("unable to read config file '%s'", - config_exclusive_filename); + given_config_file); else die("error processing config file(s)"); } } else if (actions == ACTION_EDIT) { check_argc(argc, 0, 0); - if (!config_exclusive_filename && nongit) + if (!given_config_file && nongit) die("not in a git directory"); git_config(git_default_config, NULL); - launch_editor(config_exclusive_filename ? - config_exclusive_filename : git_path("config"), + launch_editor(given_config_file ? + given_config_file : git_path("config"), NULL, NULL); } else if (actions == ACTION_SET) { + int ret; check_argc(argc, 2, 2); value = normalize_value(argv[0], argv[1]); - return git_config_set(argv[0], value); + ret = git_config_set_in_file(given_config_file, argv[0], value); + if (ret == CONFIG_NOTHING_SET) + error("cannot overwrite multiple values with a single value\n" + " Use a regexp, --add or --replace-all to change %s.", argv[0]); + return ret; } 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); + return git_config_set_multivar_in_file(given_config_file, + 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); + return git_config_set_multivar_in_file(given_config_file, + 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); + return git_config_set_multivar_in_file(given_config_file, + argv[0], value, argv[2], 1); } else if (actions == ACTION_GET) { check_argc(argc, 1, 2); @@ -461,18 +504,22 @@ int cmd_config(int argc, const char **argv, const char *prefix) else if (actions == ACTION_UNSET) { check_argc(argc, 1, 2); if (argc == 2) - return git_config_set_multivar(argv[0], NULL, argv[1], 0); + return git_config_set_multivar_in_file(given_config_file, + argv[0], NULL, argv[1], 0); else - return git_config_set(argv[0], NULL); + return git_config_set_in_file(given_config_file, + 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); + return git_config_set_multivar_in_file(given_config_file, + 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]); + ret = git_config_rename_section_in_file(given_config_file, + argv[0], argv[1]); if (ret < 0) return ret; if (ret == 0) @@ -481,7 +528,8 @@ int cmd_config(int argc, const char **argv, const char *prefix) else if (actions == ACTION_REMOVE_SECTION) { int ret; check_argc(argc, 1, 1); - ret = git_config_rename_section(argv[0], NULL); + ret = git_config_rename_section_in_file(given_config_file, + argv[0], NULL); if (ret < 0) return ret; if (ret == 0) @@ -492,11 +540,15 @@ int cmd_config(int argc, const char **argv, const char *prefix) } 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); + color_stdout_is_tty = git_config_bool("command line", argv[0]); return get_colorbool(argc != 0); } return 0; } + +int cmd_repo_config(int argc, const char **argv, const char *prefix) +{ + fprintf(stderr, "WARNING: git repo-config is deprecated in favor of git config.\n"); + return cmd_config(argc, argv, prefix); +} diff --git a/builtin/count-objects.c b/builtin/count-objects.c index 2bdd8ebde1..c37cb98c31 100644 --- a/builtin/count-objects.c +++ b/builtin/count-objects.c @@ -79,7 +79,7 @@ int cmd_count_objects(int argc, const char **argv, const char *prefix) unsigned long loose = 0, packed = 0, packed_loose = 0, garbage = 0; off_t loose_size = 0; struct option opts[] = { - OPT__VERBOSE(&verbose), + OPT__VERBOSE(&verbose, "be verbose"), OPT_END(), }; diff --git a/builtin/describe.c b/builtin/describe.c index 43caff2ffe..9f63067f50 100644 --- a/builtin/describe.c +++ b/builtin/describe.c @@ -6,6 +6,7 @@ #include "exec_cmd.h" #include "parse-options.h" #include "diff.h" +#include "hash.h" #define SEEN (1u<<0) #define MAX_TAGS (FLAG_BITS - 1) @@ -20,9 +21,10 @@ 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 abbrev = -1; /* unspecified */ static int max_candidates = 10; -static int found_names; +static struct hash_table names; +static int have_util; static const char *pattern; static int always; static const char *dirty; @@ -34,16 +36,44 @@ static const char *diff_index_args[] = { struct commit_name { + struct commit_name *next; + unsigned char peeled[20]; struct tag *tag; unsigned prio:2; /* annotated tag = 2, tag = 1, head = 0 */ unsigned name_checked:1; unsigned char sha1[20]; - char path[FLEX_ARRAY]; /* more */ + const char *path; }; static const char *prio_names[] = { "head", "lightweight", "annotated", }; +static inline unsigned int hash_sha1(const unsigned char *sha1) +{ + unsigned int hash; + memcpy(&hash, sha1, sizeof(hash)); + return hash; +} + +static inline struct commit_name *find_commit_name(const unsigned char *peeled) +{ + struct commit_name *n = lookup_hash(hash_sha1(peeled), &names); + while (n && !!hashcmp(peeled, n->peeled)) + n = n->next; + return n; +} + +static int set_util(void *chain, void *data) +{ + struct commit_name *n; + for (n = chain; n; n = n->next) { + struct commit *c = lookup_commit_reference_gently(n->peeled, 1); + if (c) + c->util = n; + } + return 0; +} + static int replace_name(struct commit_name *e, int prio, const unsigned char *sha1, @@ -78,31 +108,36 @@ static int replace_name(struct commit_name *e, } static void add_to_known_names(const char *path, - struct commit *commit, + const unsigned char *peeled, int prio, const unsigned char *sha1) { - struct commit_name *e = commit->util; + struct commit_name *e = find_commit_name(peeled); struct tag *tag = NULL; if (replace_name(e, prio, sha1, &tag)) { - size_t len = strlen(path)+1; - free(e); - e = xmalloc(sizeof(struct commit_name) + len); + if (!e) { + void **pos; + e = xmalloc(sizeof(struct commit_name)); + hashcpy(e->peeled, peeled); + pos = insert_hash(hash_sha1(peeled), e, &names); + if (pos) { + e->next = *pos; + *pos = e; + } else { + e->next = NULL; + } + } e->tag = tag; e->prio = prio; e->name_checked = 0; hashcpy(e->sha1, sha1); - memcpy(e->path, path, len); - commit->util = e; + e->path = path; } - 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; @@ -110,16 +145,10 @@ static int get_name(const char *path, const unsigned char *sha1, int flag, void 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); + is_tag = !!hashcmp(sha1, peeled); } else { - commit = lookup_commit_reference_gently(sha1, 1); - object = parse_object(sha1); - if (!commit || !object) - return 0; - is_tag = object->type == OBJ_TAG; + hashcpy(peeled, sha1); + is_tag = 0; } /* If --all, then any refs are used. @@ -142,7 +171,7 @@ static int get_name(const char *path, const unsigned char *sha1, int flag, void if (!prio) return 0; } - add_to_known_names(all ? path + 5 : path + 10, commit, prio, sha1); + add_to_known_names(all ? path + 5 : path + 10, peeled, prio, sha1); return 0; } @@ -189,7 +218,7 @@ static unsigned long finish_depth_computation( struct commit *p = parents->item; parse_commit(p); if (!(p->object.flags & SEEN)) - insert_by_date(p, list); + commit_list_insert_by_date(p, list); p->object.flags |= c->object.flags; parents = parents->next; } @@ -202,13 +231,13 @@ 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)) - die("annotated tag %s not available", n->path); + die(_("annotated tag %s not available"), n->path); } if (n->tag && !n->name_checked) { if (!n->tag->tag) - die("annotated tag %s has no embedded name", n->path); + die(_("annotated tag %s has no embedded name"), 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); + warning(_("tag '%s' is really '%s' here"), n->tag->tag, n->path); n->name_checked = 1; } @@ -235,12 +264,12 @@ static void describe(const char *arg, int last_one) unsigned int unannotated_cnt = 0; if (get_sha1(arg, sha1)) - die("Not a valid object name %s", arg); + 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); + die(_("%s is not a valid '%s' object"), arg, commit_type); - n = cmit->util; + n = find_commit_name(cmit->object.sha1); if (n && (tags || all || n->prio == 2)) { /* * Exact match to an existing ref. @@ -255,9 +284,14 @@ static void describe(const char *arg, int last_one) } if (!max_candidates) - die("no tag exactly matches '%s'", sha1_to_hex(cmit->object.sha1)); + die(_("no tag exactly matches '%s'"), sha1_to_hex(cmit->object.sha1)); if (debug) - fprintf(stderr, "searching to describe %s\n", arg); + fprintf(stderr, _("searching to describe %s\n"), arg); + + if (!have_util) { + for_each_hash(&names, set_util, NULL); + have_util = 1; + } list = NULL; cmit->object.flags = SEEN; @@ -292,7 +326,7 @@ static void describe(const char *arg, int last_one) } if (annotated_cnt && !list) { if (debug) - fprintf(stderr, "finished search at %s\n", + fprintf(stderr, _("finished search at %s\n"), sha1_to_hex(c->object.sha1)); break; } @@ -300,7 +334,7 @@ static void describe(const char *arg, int last_one) struct commit *p = parents->item; parse_commit(p); if (!(p->object.flags & SEEN)) - insert_by_date(p, &list); + commit_list_insert_by_date(p, &list); p->object.flags |= c->object.flags; parents = parents->next; } @@ -316,19 +350,19 @@ static void describe(const char *arg, int last_one) return; } if (unannotated_cnt) - die("No annotated tags can describe '%s'.\n" - "However, there were unannotated tags: try --tags.", + 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.", + 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); + commit_list_insert_by_date(gave_up_on, &list); seen_commits--; } seen_commits += finish_depth_computation(&list, &all_matches[0]); @@ -341,11 +375,11 @@ static void describe(const char *arg, int last_one) prio_names[t->name->prio], t->depth, t->name->path); } - fprintf(stderr, "traversed %lu commits\n", seen_commits); + 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", + _("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)); } @@ -386,7 +420,11 @@ int cmd_describe(int argc, const char **argv, const char *prefix) OPT_END(), }; + git_config(git_default_config, NULL); argc = parse_options(argc, argv, prefix, options, describe_usage, 0); + if (abbrev < 0) + abbrev = DEFAULT_ABBREV; + if (max_candidates < 0) max_candidates = 0; else if (max_candidates > MAX_TAGS) @@ -395,7 +433,7 @@ int cmd_describe(int argc, const char **argv, const char *prefix) save_commit_buffer = 0; if (longformat && abbrev == 0) - die("--long is incompatible with --abbrev=0"); + die(_("--long is incompatible with --abbrev=0")); if (contains) { const char **args = xmalloc((7 + argc) * sizeof(char *)); @@ -418,16 +456,30 @@ int cmd_describe(int argc, const char **argv, const char *prefix) 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."); + init_hash(&names); + for_each_rawref(get_name, NULL); + if (!names.nr && !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; + if (dirty) { + static struct lock_file index_lock; + int fd; + + read_cache_preload(NULL); + refresh_index(&the_index, REFRESH_QUIET|REFRESH_UNMERGED, + NULL, NULL, NULL); + fd = hold_locked_index(&index_lock, 0); + if (0 <= fd) + update_index_if_able(&the_index, &index_lock); + + if (!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"); + die(_("--dirty is incompatible with committishes")); } else { while (argc-- > 0) { describe(*argv++, argc == 0); diff --git a/builtin/diff-files.c b/builtin/diff-files.c index 951c7c8994..46085f862f 100644 --- a/builtin/diff-files.c +++ b/builtin/diff-files.c @@ -61,7 +61,7 @@ int cmd_diff_files(int argc, const char **argv, const char *prefix) (rev.diffopt.output_format & DIFF_FORMAT_PATCH)) rev.combine_merges = rev.dense_combined_merges = 1; - if (read_cache_preload(rev.diffopt.paths) < 0) { + if (read_cache_preload(rev.diffopt.pathspec.raw) < 0) { perror("read_cache_preload"); return -1; } diff --git a/builtin/diff-tree.c b/builtin/diff-tree.c index 0d2a3e9fa2..be6417d166 100644 --- a/builtin/diff-tree.c +++ b/builtin/diff-tree.c @@ -163,6 +163,9 @@ int cmd_diff_tree(int argc, const char **argv, const char *prefix) } if (read_stdin) { + int saved_nrl = 0; + int saved_dcctc = 0; + if (opt->diffopt.detect_rename) opt->diffopt.setup |= (DIFF_SETUP_USE_SIZE_CACHE | DIFF_SETUP_USE_CACHE); @@ -173,9 +176,16 @@ int cmd_diff_tree(int argc, const char **argv, const char *prefix) fputs(line, stdout); fflush(stdout); } - else + else { diff_tree_stdin(line); + if (saved_nrl < opt->diffopt.needed_rename_limit) + saved_nrl = opt->diffopt.needed_rename_limit; + if (opt->diffopt.degraded_cc_to_c) + saved_dcctc = 1; + } } + opt->diffopt.degraded_cc_to_c = saved_dcctc; + opt->diffopt.needed_rename_limit = saved_nrl; } return diff_result_code(&opt->diffopt, 0); diff --git a/builtin/diff.c b/builtin/diff.c index a43d326363..9069dc41be 100644 --- a/builtin/diff.c +++ b/builtin/diff.c @@ -14,6 +14,7 @@ #include "log-tree.h" #include "builtin.h" #include "submodule.h" +#include "sha1-array.h" struct blobinfo { unsigned char sha1[20]; @@ -22,7 +23,7 @@ struct blobinfo { }; static const char builtin_diff_usage[] = -"git diff <options> <rev>{0,2} -- <path>*"; +"git diff [<options>] [<commit> [<commit>]] [--] [<path>...]"; static void stuff_change(struct diff_options *opt, unsigned old_mode, unsigned new_mode, @@ -71,9 +72,9 @@ static int builtin_diff_b_f(struct rev_info *revs, usage(builtin_diff_usage); if (lstat(path, &st)) - die_errno("failed to stat '%s'", path); + 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); + die(_("'%s': not a regular file or symlink"), path); diff_set_mnemonic_prefix(&revs->diffopt, "o/", "w/"); @@ -135,7 +136,7 @@ static int builtin_diff_index(struct rev_info *revs, revs->max_count != -1 || revs->min_age != -1 || revs->max_age != -1) usage(builtin_diff_usage); - if (read_cache_preload(revs->diffopt.paths) < 0) { + if (read_cache_preload(revs->diffopt.pathspec.raw) < 0) { perror("read_cache_preload"); return -1; } @@ -169,7 +170,7 @@ static int builtin_diff_combined(struct rev_info *revs, struct object_array_entry *ent, int ents) { - const unsigned char (*parent)[20]; + struct sha1_array parents = SHA1_ARRAY_INIT; int i; if (argc > 1) @@ -177,11 +178,11 @@ static int builtin_diff_combined(struct rev_info *revs, 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, + for (i = 1; i < ents; i++) + sha1_array_append(&parents, ent[i].item->sha1); + diff_tree_combined(ent[0].item->sha1, &parents, revs->dense_combined_merges, revs); + sha1_array_clear(&parents); return 0; } @@ -197,17 +198,11 @@ static void refresh_index_quietly(void) 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); + update_index_if_able(&the_index, 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] == '-') { @@ -222,7 +217,7 @@ static int builtin_diff_files(struct rev_info *revs, int argc, const char **argv else if (!strcmp(argv[1], "-h")) usage(builtin_diff_usage); else - return error("invalid option: %s", argv[1]); + return error(_("invalid option: %s"), argv[1]); argv++; argc--; } @@ -237,12 +232,11 @@ static int builtin_diff_files(struct rev_info *revs, int argc, const char **argv revs->combine_merges = revs->dense_combined_merges = 1; setup_work_tree(); - if (read_cache_preload(revs->diffopt.paths) < 0) { + if (read_cache_preload(revs->diffopt.pathspec.raw) < 0) { perror("read_cache_preload"); return -1; } - result = run_diff_files(revs, options); - return diff_result_code(&revs->diffopt, result); + return run_diff_files(revs, options); } int cmd_diff(int argc, const char **argv, const char *prefix) @@ -283,9 +277,6 @@ int cmd_diff(int argc, const char **argv, const char *prefix) gitmodules_config(); 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. */ @@ -294,17 +285,21 @@ int cmd_diff(int argc, const char **argv, const char *prefix) /* Otherwise, we are doing the usual "git" diff */ rev.diffopt.skip_stat_unmatch = !!diff_auto_refresh_index; + /* Scale to real terminal size and respect statGraphWidth config */ + rev.diffopt.stat_width = -1; + rev.diffopt.stat_graph_width = -1; + /* 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"); + 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"); + die(_("diff_setup_done failed")); } DIFF_OPT_SET(&rev.diffopt, RECURSIVE); @@ -330,8 +325,11 @@ int cmd_diff(int argc, const char **argv, const char *prefix) 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)"); + if (!rev.pending.nr) { + struct tree *tree; + tree = lookup_tree(EMPTY_TREE_SHA1_BIN); + add_pending_object(&rev, &tree->object, "HEAD"); + } break; } } @@ -346,12 +344,12 @@ int cmd_diff(int argc, const char **argv, const char *prefix) obj = parse_object(obj->sha1); obj = deref_tag(obj, NULL, 0); if (!obj) - die("invalid object '%s' given.", name); + 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'", + die(_("more than %d trees given: '%s'"), (int) ARRAY_SIZE(ent), name); obj->flags |= flags; ent[ents].item = obj; @@ -361,7 +359,7 @@ int cmd_diff(int argc, const char **argv, const char *prefix) } if (obj->type == OBJ_BLOB) { if (2 <= blobs) - die("more than two blobs given: '%s'", name); + die(_("more than two blobs given: '%s'"), name); hashcpy(blob[blobs].sha1, obj->sha1); blob[blobs].name = name; blob[blobs].mode = list->mode; @@ -369,16 +367,12 @@ int cmd_diff(int argc, const char **argv, const char *prefix) continue; } - die("unhandled object '%s' given.", name); + die(_("unhandled object '%s' given."), name); } - if (rev.prune_data) { - const char **pathspec = rev.prune_data; - while (*pathspec) { - if (!path) - path = *pathspec; - paths++; - pathspec++; - } + if (rev.prune_data.nr) { + if (!path) + path = rev.prune_data.items[0].match; + paths += rev.prune_data.nr; } /* diff --git a/builtin/fast-export.c b/builtin/fast-export.c index c8fd46b872..ef7c012094 100644 --- a/builtin/fast-export.c +++ b/builtin/fast-export.c @@ -16,6 +16,7 @@ #include "string-list.h" #include "utf8.h" #include "parse-options.h" +#include "quote.h" static const char *fast_export_usage[] = { "git fast-export [rev-list-opts]", @@ -24,8 +25,9 @@ static const char *fast_export_usage[] = { static int progress; static enum { ABORT, VERBATIM, WARN, STRIP } signed_tag_mode = ABORT; -static enum { ERROR, DROP, REWRITE } tag_of_filtered_mode = ABORT; +static enum { ERROR, DROP, REWRITE } tag_of_filtered_mode = ERROR; static int fake_missing_tagger; +static int use_done_feature; static int no_data; static int full_tree; @@ -49,7 +51,7 @@ 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; + tag_of_filtered_mode = ERROR; else if (!strcmp(arg, "drop")) tag_of_filtered_mode = DROP; else if (!strcmp(arg, "rewrite")) @@ -178,6 +180,15 @@ static int depth_first(const void *a_, const void *b_) return (a->status == 'R') - (b->status == 'R'); } +static void print_path(const char *path) +{ + int need_quote = quote_c_style(path, NULL, NULL, 0); + if (need_quote) + quote_c_style(path, NULL, stdout, 0); + else + printf("%s", path); +} + static void show_filemodify(struct diff_queue_struct *q, struct diff_options *options, void *data) { @@ -195,13 +206,18 @@ static void show_filemodify(struct diff_queue_struct *q, switch (q->queue[i]->status) { case DIFF_STATUS_DELETED: - printf("D %s\n", spec->path); + printf("D "); + print_path(spec->path); + putchar('\n'); break; case DIFF_STATUS_COPIED: case DIFF_STATUS_RENAMED: - printf("%c \"%s\" \"%s\"\n", q->queue[i]->status, - ospec->path, spec->path); + printf("%c ", q->queue[i]->status); + print_path(ospec->path); + putchar(' '); + print_path(spec->path); + putchar('\n'); if (!hashcmp(ospec->sha1, spec->sha1) && ospec->mode == spec->mode) @@ -216,13 +232,15 @@ static void show_filemodify(struct diff_queue_struct *q, * 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); + printf("M %06o %s ", spec->mode, + sha1_to_hex(spec->sha1)); else { struct object *object = lookup_object(spec->sha1); - printf("M %06o :%d %s\n", spec->mode, - get_object_mark(object), spec->path); + printf("M %06o :%d ", spec->mode, + get_object_mark(object)); } + print_path(spec->path); + putchar('\n'); break; default: @@ -592,7 +610,7 @@ static void import_marks(char *input_file) die ("Could not read blob %s", sha1_to_hex(sha1)); if (object->flags & SHOWN) - error("Object %s already has a mark", sha1); + error("Object %s already has a mark", sha1_to_hex(sha1)); mark_object(object, mark); if (last_idnum < mark) @@ -619,17 +637,17 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix) 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", + OPT_STRING(0, "export-marks", &export_filename, "file", "Dump marks to this file"), - OPT_STRING(0, "import-marks", &import_filename, "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"), OPT_BOOLEAN(0, "full-tree", &full_tree, "Output full tree for each commit"), - { OPTION_NEGBIT, 0, "data", &no_data, NULL, - "Skip output of blob data", - PARSE_OPT_NOARG | PARSE_OPT_NEGHELP, NULL, 1 }, + OPT_BOOLEAN(0, "use-done-feature", &use_done_feature, + "Use the done feature to terminate the stream"), + OPT_BOOL(0, "no-data", &no_data, "Skip output of blob data"), OPT_END() }; @@ -648,10 +666,13 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix) if (argc > 1) usage_with_options (fast_export_usage, options); + if (use_done_feature) + printf("feature done\n"); + if (import_filename) import_marks(import_filename); - if (import_filename && revs.prune_data) + if (import_filename && revs.prune_data.nr) full_tree = 1; get_tags_and_duplicates(&revs.pending, &extra_refs); @@ -675,5 +696,8 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix) if (export_filename) export_marks(export_filename); + if (use_done_feature) + printf("done\n"); + return 0; } diff --git a/builtin/fetch-pack.c b/builtin/fetch-pack.c index dbd8b7bcc8..149db88726 100644 --- a/builtin/fetch-pack.c +++ b/builtin/fetch-pack.c @@ -1,4 +1,4 @@ -#include "cache.h" +#include "builtin.h" #include "refs.h" #include "pkt-line.h" #include "commit.h" @@ -9,17 +9,23 @@ #include "fetch-pack.h" #include "remote.h" #include "run-command.h" +#include "transport.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 int no_done; +static int fetch_fsck_objects = -1; +static int transfer_fsck_objects = -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>...]"; +"git fetch-pack [--all] [--stdin] [--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) @@ -47,16 +53,16 @@ static void rev_list_push(struct commit *commit, int mark) if (parse_commit(commit)) return; - insert_by_date(commit, &rev_list); + commit_list_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) +static int rev_list_insert_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data) { - struct object *o = deref_tag(parse_object(sha1), path, 0); + struct object *o = deref_tag(parse_object(sha1), refname, 0); if (o && o->type == OBJ_COMMIT) rev_list_push((struct commit *)o, SEEN); @@ -64,9 +70,9 @@ static int rev_list_insert_ref(const char *path, const unsigned char *sha1, int return 0; } -static int clear_marks(const char *path, const unsigned char *sha1, int flag, void *cb_data) +static int clear_marks(const char *refname, const unsigned char *sha1, int flag, void *cb_data) { - struct object *o = deref_tag(parse_object(sha1), path, 0); + struct object *o = deref_tag(parse_object(sha1), refname, 0); if (o && o->type == OBJ_COMMIT) clear_commit_marks((struct commit *)o, @@ -183,6 +189,36 @@ static void consume_shallow_list(int fd) } } +struct write_shallow_data { + struct strbuf *out; + int use_pack_protocol; + int count; +}; + +static int write_one_shallow(const struct commit_graft *graft, void *cb_data) +{ + struct write_shallow_data *data = cb_data; + const char *hex = sha1_to_hex(graft->sha1); + data->count++; + if (data->use_pack_protocol) + packet_buf_write(data->out, "shallow %s", hex); + else { + strbuf_addstr(data->out, hex); + strbuf_addch(data->out, '\n'); + } + return 0; +} + +static int write_shallow_commits(struct strbuf *out, int use_pack_protocol) +{ + struct write_shallow_data data; + data.out = out; + data.use_pack_protocol = use_pack_protocol; + data.count = 0; + for_each_commit_graft(write_one_shallow, &data); + return data.count; +} + static enum ack_type get_ack(int fd, unsigned char *result_sha1) { static char line[1000]; @@ -217,14 +253,35 @@ static void send_request(int fd, struct strbuf *buf) safe_write(fd, buf->buf, buf->len); } +static void insert_one_alternate_ref(const struct ref *ref, void *unused) +{ + rev_list_insert_ref(NULL, ref->old_sha1, 0, NULL); +} + +#define INITIAL_FLUSH 16 +#define PIPESAFE_FLUSH 32 +#define LARGE_FLUSH 1024 + +static int next_flush(int count) +{ + int flush_limit = args.stateless_rpc ? LARGE_FLUSH : PIPESAFE_FLUSH; + + if (count < flush_limit) + count <<= 1; + else + count += flush_limit; + return count; +} + static int find_common(int fd[2], unsigned char *result_sha1, struct ref *refs) { int fetching; - int count = 0, flushes = 0, retval; + int count = 0, flushes = 0, flush_at = INITIAL_FLUSH, retval; const unsigned char *sha1; unsigned in_vain = 0; int got_continue = 0; + int got_ready = 0; struct strbuf req_buf = STRBUF_INIT; size_t state_len = 0; @@ -235,6 +292,7 @@ static int find_common(int fd[2], unsigned char *result_sha1, marked = 1; for_each_ref(rev_list_insert_ref, NULL); + for_each_alternate_ref(insert_one_alternate_ref, NULL); fetching = 0; for ( ; refs ; refs = refs->next) { @@ -262,6 +320,7 @@ static int find_common(int fd[2], unsigned char *result_sha1, 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 (no_done) strbuf_addstr(&c, " no-done"); 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"); @@ -332,19 +391,20 @@ static int find_common(int fd[2], unsigned char *result_sha1, if (args.verbose) fprintf(stderr, "have %s\n", sha1_to_hex(sha1)); in_vain++; - if (!(31 & ++count)) { + if (flush_at <= ++count) { int ack; packet_buf_flush(&req_buf); send_request(fd[1], &req_buf); strbuf_setlen(&req_buf, state_len); flushes++; + flush_at = next_flush(count); /* * 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) + if (!args.stateless_rpc && count == INITIAL_FLUSH) continue; consume_shallow_list(fd[0]); @@ -364,6 +424,8 @@ static int find_common(int fd[2], unsigned char *result_sha1, case ACK_continue: { struct commit *commit = lookup_commit(result_sha1); + if (!commit) + die("invalid commit %s", sha1_to_hex(result_sha1)); if (args.stateless_rpc && ack == ACK_common && !(commit->object.flags & COMMON)) { @@ -379,6 +441,10 @@ static int find_common(int fd[2], unsigned char *result_sha1, retval = 0; in_vain = 0; got_continue = 1; + if (ack == ACK_ready) { + rev_list = NULL; + got_ready = 1; + } break; } } @@ -392,8 +458,10 @@ static int find_common(int fd[2], unsigned char *result_sha1, } } done: - packet_buf_write(&req_buf, "done\n"); - send_request(fd[1], &req_buf); + if (!got_ready || !no_done) { + packet_buf_write(&req_buf, "done\n"); + send_request(fd[1], &req_buf); + } if (args.verbose) fprintf(stderr, "done\n"); if (retval != 0) { @@ -422,7 +490,7 @@ done: static struct commit_list *complete; -static int mark_complete(const char *path, const unsigned char *sha1, int flag, void *cb_data) +static int mark_complete(const char *refname, const unsigned char *sha1, int flag, void *cb_data) { struct object *o = parse_object(sha1); @@ -435,8 +503,10 @@ static int mark_complete(const char *path, const unsigned char *sha1, int flag, } if (o && o->type == OBJ_COMMIT) { struct commit *commit = (struct commit *)o; - commit->object.flags |= COMPLETE; - insert_by_date(commit, &complete); + if (!(commit->object.flags & COMPLETE)) { + commit->object.flags |= COMPLETE; + commit_list_insert_by_date(commit, &complete); + } } return 0; } @@ -458,6 +528,7 @@ static void filter_refs(struct ref **refs, int nr_match, char **match) struct ref **newtail = &newlist; struct ref *ref, *next; struct ref *fastarray[32]; + int match_pos; if (nr_match && !args.fetch_all) { if (ARRAY_SIZE(fastarray) < nr_match) @@ -470,10 +541,11 @@ static void filter_refs(struct ref **refs, int nr_match, char **match) else return_refs = NULL; + match_pos = 0; for (ref = *refs; ref; ref = next) { next = ref->next; if (!memcmp(ref->name, "refs/", 5) && - check_ref_format(ref->name + 5)) + check_refname_format(ref->name + 5, 0)) ; /* trash */ else if (args.fetch_all && (!args.depth || prefixcmp(ref->name, "refs/tags/") )) { @@ -483,11 +555,21 @@ static void filter_refs(struct ref **refs, int nr_match, char **match) continue; } else { - int order = path_match(ref->name, nr_match, match); - if (order) { - return_refs[order-1] = ref; - continue; /* we will link it later */ + int cmp = -1; + while (match_pos < nr_match) { + cmp = strcmp(ref->name, match[match_pos]); + if (cmp < 0) /* definitely do not have it */ + break; + else if (cmp == 0) { /* definitely have it */ + match[match_pos][0] = '\0'; + return_refs[match_pos] = ref; + break; + } + else /* might have it; keep looking */ + match_pos++; } + if (!cmp) + continue; /* we will link it later */ } free(ref); } @@ -508,6 +590,11 @@ static void filter_refs(struct ref **refs, int nr_match, char **match) *refs = newlist; } +static void mark_alternate_complete(const struct ref *ref, void *unused) +{ + mark_complete(NULL, ref->old_sha1, 0, NULL); +} + static int everything_local(struct ref **refs, int nr_match, char **match) { struct ref *ref; @@ -536,6 +623,7 @@ static int everything_local(struct ref **refs, int nr_match, char **match) if (!args.depth) { for_each_ref(mark_complete, NULL); + for_each_alternate_ref(mark_alternate_complete, NULL); if (cutoff) mark_recent_complete_commits(cutoff); } @@ -658,11 +746,17 @@ static int get_pack(int xd[2], char **pack_lockfile) } else { *av++ = "unpack-objects"; - if (args.quiet) + if (args.quiet || args.no_progress) *av++ = "-q"; } if (*hdr_arg) *av++ = hdr_arg; + if (fetch_fsck_objects >= 0 + ? fetch_fsck_objects + : transfer_fsck_objects >= 0 + ? transfer_fsck_objects + : 0) + *av++ = "--strict"; *av++ = NULL; cmd.in = demux.out; @@ -690,12 +784,20 @@ static struct ref *do_fetch_pack(int fd[2], struct ref *ref = copy_ref_list(orig_ref); unsigned char sha1[20]; + sort_ref_list(&ref, ref_compare_name); + 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; + if (server_supports("no-done")) { + if (args.verbose) + fprintf(stderr, "Server supports no-done\n"); + if (args.stateless_rpc) + no_done = 1; + } } else if (server_supports("multi_ack")) { if (args.verbose) @@ -741,21 +843,12 @@ 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++; - } + if (!nr_heads) + return 0; + + for (src = dst = 1; src < nr_heads; src++) + if (strcmp(heads[src], heads[dst-1])) + heads[dst++] = heads[src]; return dst; } @@ -776,6 +869,16 @@ static int fetch_pack_config(const char *var, const char *value, void *cb) return 0; } + if (!strcmp(var, "fetch.fsckobjects")) { + fetch_fsck_objects = git_config_bool(var, value); + return 0; + } + + if (!strcmp(var, "transfer.fsckobjects")) { + transfer_fsck_objects = git_config_bool(var, value); + return 0; + } + return git_default_config(var, value, cb); } @@ -796,90 +899,127 @@ static void fetch_pack_setup(void) int cmd_fetch_pack(int argc, const char **argv, const char *prefix) { - int i, ret, nr_heads; + int i, ret; struct ref *ref = NULL; - char *dest = NULL, **heads; + const char *dest = NULL; + int alloc_heads = 0, nr_heads = 0; + char **heads = NULL; 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++) { + packet_trace_identity("fetch-pack"); + + for (i = 1; i < argc && *argv[i] == '-'; 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 (!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("--stdin", arg)) { + args.stdin_refs = 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); + } + + if (i < argc) + dest = argv[i++]; + else + usage(fetch_pack_usage); + + /* + * Copy refs from cmdline to growable list, then append any + * refs from the standard input: + */ + ALLOC_GROW(heads, argc - i, alloc_heads); + for (; i < argc; i++) + heads[nr_heads++] = xstrdup(argv[i]); + if (args.stdin_refs) { + if (args.stateless_rpc) { + /* in stateless RPC mode we use pkt-line to read + * from stdin, until we get a flush packet + */ + static char line[1000]; + for (;;) { + int n = packet_read_line(0, line, sizeof(line)); + if (!n) + break; + if (line[n-1] == '\n') + n--; + ALLOC_GROW(heads, nr_heads + 1, alloc_heads); + heads[nr_heads++] = xmemdupz(line, n); } - if (!strcmp("--lock-pack", arg)) { - args.lock_pack = 1; - pack_lockfile_ptr = &pack_lockfile; - continue; + } + else { + /* read from stdin one ref per line, until EOF */ + struct strbuf line = STRBUF_INIT; + while (strbuf_getline(&line, stdin, '\n') != EOF) { + ALLOC_GROW(heads, nr_heads + 1, alloc_heads); + heads[nr_heads++] = strbuf_detach(&line, NULL); } - usage(fetch_pack_usage); + strbuf_release(&line); } - 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, + conn = git_connect(fd, dest, args.uploadpack, args.verbose ? CONNECT_VERBOSE : 0); } - get_remote_heads(fd[0], &ref, 0, NULL, 0, NULL); + get_remote_heads(fd[0], &ref, 0, NULL); ref = fetch_pack(&args, fd, conn, ref, dest, nr_heads, heads, pack_lockfile_ptr); @@ -914,6 +1054,11 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix) return ret; } +static int compare_heads(const void *a, const void *b) +{ + return strcmp(*(const char **)a, *(const char **)b); +} + struct ref *fetch_pack(struct fetch_pack_args *my_args, int fd[], struct child_process *conn, const struct ref *ref, @@ -933,8 +1078,11 @@ struct ref *fetch_pack(struct fetch_pack_args *my_args, st.st_mtime = 0; } - if (heads && nr_heads) + if (heads && nr_heads) { + qsort(heads, nr_heads, sizeof(*heads), compare_heads); nr_heads = remove_duplicates(nr_heads, heads); + } + if (!ref) { packet_flush(fd[1]); die("no matching remote head"); diff --git a/builtin/fetch.c b/builtin/fetch.c index d35f000c03..bb9a0743ff 100644 --- a/builtin/fetch.c +++ b/builtin/fetch.c @@ -12,6 +12,8 @@ #include "parse-options.h" #include "sigchain.h" #include "transport.h" +#include "submodule.h" +#include "connected.h" static const char * const builtin_fetch_usage[] = { "git fetch [<options>] [<repository> [<refspec>...]]", @@ -28,12 +30,28 @@ enum { }; static int all, append, dry_run, force, keep, multiple, prune, update_head_ok, verbosity; -static int progress; +static int progress = -1, recurse_submodules = RECURSE_SUBMODULES_DEFAULT; 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 const char *submodule_prefix = ""; +static const char *recurse_submodules_default; + +static int option_parse_recurse_submodules(const struct option *opt, + const char *arg, int unset) +{ + if (unset) { + recurse_submodules = RECURSE_SUBMODULES_OFF; + } else { + if (arg) + recurse_submodules = parse_fetch_recurse_submodules_arg(opt->long_name, arg); + else + recurse_submodules = RECURSE_SUBMODULES_ON; + } + return 0; +} static struct option builtin_fetch_options[] = { OPT__VERBOSITY(&verbosity), @@ -41,10 +59,9 @@ static struct option builtin_fetch_options[] = { "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", + 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__FORCE(&force, "force overwrite of local branch"), OPT_BOOLEAN('m', "multiple", &multiple, "fetch from multiple remotes"), OPT_SET_INT('t', "tags", &tags, @@ -52,15 +69,23 @@ static struct option builtin_fetch_options[] = { 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"), + "prune remote-tracking branches no longer on remote"), + { OPTION_CALLBACK, 0, "recurse-submodules", NULL, "on-demand", + "control recursive fetching of submodules", + PARSE_OPT_OPTARG, option_parse_recurse_submodules }, 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_BOOLEAN(0, "progress", &progress, "force progress reporting"), - OPT_STRING(0, "depth", &depth, "DEPTH", + OPT_BOOL(0, "progress", &progress, "force progress reporting"), + OPT_STRING(0, "depth", &depth, "depth", "deepen history of shallow clone"), + { OPTION_STRING, 0, "submodule-prefix", &submodule_prefix, "dir", + "prepend this to submodule path output", PARSE_OPT_HIDDEN }, + { OPTION_STRING, 0, "recurse-submodules-default", + &recurse_submodules_default, NULL, + "default mode for recursion", PARSE_OPT_HIDDEN }, OPT_END() }; @@ -98,7 +123,7 @@ static void add_merge_config(struct ref **head, continue; /* - * Not fetched to a tracking branch? We need to fetch + * Not fetched to a remote-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 @@ -172,7 +197,7 @@ static struct ref *get_ref_map(struct transport *transport, } else { ref_map = get_remote_ref(remote_refs, "HEAD"); if (!ref_map) - die("Couldn't find remote ref HEAD"); + die(_("Couldn't find remote ref HEAD")); ref_map->merge = 1; tail = &ref_map->next; } @@ -215,23 +240,24 @@ static int s_update_ref(const char *action, static int update_local_ref(struct ref *ref, const char *remote, - char *display) + const struct ref *remote_ref, + struct strbuf *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)); + 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", TRANSPORT_SUMMARY_WIDTH, - "[up to date]", REFCOL_WIDTH, remote, - pretty_ref); + strbuf_addf(display, "= %-*s %-*s -> %s", + TRANSPORT_SUMMARY_WIDTH, + _("[up to date]"), REFCOL_WIDTH, + remote, pretty_ref); return 0; } @@ -243,9 +269,10 @@ static int update_local_ref(struct ref *ref, * 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)", - TRANSPORT_SUMMARY_WIDTH, "[rejected]", REFCOL_WIDTH, remote, - pretty_ref); + strbuf_addf(display, + _("! %-*s %-*s -> %s (can't fetch in current branch)"), + TRANSPORT_SUMMARY_WIDTH, _("[rejected]"), + REFCOL_WIDTH, remote, pretty_ref); return 1; } @@ -253,9 +280,11 @@ static int update_local_ref(struct ref *ref, !prefixcmp(ref->name, "refs/tags/")) { int r; r = s_update_ref("updating tag", ref, 0); - sprintf(display, "%c %-*s %-*s -> %s%s", r ? '!' : '-', - TRANSPORT_SUMMARY_WIDTH, "[tag update]", REFCOL_WIDTH, remote, - pretty_ref, r ? " (unable to update local ref)" : ""); + strbuf_addf(display, "%c %-*s %-*s -> %s%s", + r ? '!' : '-', + TRANSPORT_SUMMARY_WIDTH, _("[tag update]"), + REFCOL_WIDTH, remote, pretty_ref, + r ? _(" (unable to update local ref)") : ""); return r; } @@ -265,19 +294,32 @@ static int update_local_ref(struct ref *ref, const char *msg; const char *what; int r; - if (!strncmp(ref->name, "refs/tags/", 10)) { + /* + * Nicely describe the new ref we're fetching. + * Base this on the remote's ref name, as it's + * more likely to follow a standard layout. + */ + const char *name = remote_ref ? remote_ref->name : ""; + if (!prefixcmp(name, "refs/tags/")) { msg = "storing tag"; - what = "[new tag]"; - } - else { + what = _("[new tag]"); + } else if (!prefixcmp(name, "refs/heads/")) { msg = "storing head"; - what = "[new branch]"; + what = _("[new branch]"); + } else { + msg = "storing ref"; + what = _("[new ref]"); } + if ((recurse_submodules != RECURSE_SUBMODULES_OFF) && + (recurse_submodules != RECURSE_SUBMODULES_ON)) + check_for_new_submodule_commits(ref->new_sha1); r = s_update_ref(msg, ref, 0); - sprintf(display, "%c %-*s %-*s -> %s%s", r ? '!' : '*', - TRANSPORT_SUMMARY_WIDTH, what, REFCOL_WIDTH, remote, pretty_ref, - r ? " (unable to update local ref)" : ""); + strbuf_addf(display, "%c %-*s %-*s -> %s%s", + r ? '!' : '*', + TRANSPORT_SUMMARY_WIDTH, what, + REFCOL_WIDTH, remote, pretty_ref, + r ? _(" (unable to update local ref)") : ""); return r; } @@ -287,10 +329,15 @@ static int update_local_ref(struct ref *ref, strcpy(quickref, find_unique_abbrev(current->object.sha1, DEFAULT_ABBREV)); strcat(quickref, ".."); strcat(quickref, find_unique_abbrev(ref->new_sha1, DEFAULT_ABBREV)); + if ((recurse_submodules != RECURSE_SUBMODULES_OFF) && + (recurse_submodules != RECURSE_SUBMODULES_ON)) + check_for_new_submodule_commits(ref->new_sha1); r = s_update_ref("fast-forward", ref, 1); - sprintf(display, "%c %-*s %-*s -> %s%s", r ? '!' : ' ', - TRANSPORT_SUMMARY_WIDTH, quickref, REFCOL_WIDTH, remote, - pretty_ref, r ? " (unable to update local ref)" : ""); + strbuf_addf(display, "%c %-*s %-*s -> %s%s", + r ? '!' : ' ', + TRANSPORT_SUMMARY_WIDTH, quickref, + REFCOL_WIDTH, remote, pretty_ref, + r ? _(" (unable to update local ref)") : ""); return r; } else if (force || ref->force) { char quickref[84]; @@ -298,125 +345,165 @@ static int update_local_ref(struct ref *ref, strcpy(quickref, find_unique_abbrev(current->object.sha1, DEFAULT_ABBREV)); strcat(quickref, "..."); strcat(quickref, find_unique_abbrev(ref->new_sha1, DEFAULT_ABBREV)); + if ((recurse_submodules != RECURSE_SUBMODULES_OFF) && + (recurse_submodules != RECURSE_SUBMODULES_ON)) + check_for_new_submodule_commits(ref->new_sha1); r = s_update_ref("forced-update", ref, 1); - sprintf(display, "%c %-*s %-*s -> %s (%s)", r ? '!' : '+', - TRANSPORT_SUMMARY_WIDTH, quickref, REFCOL_WIDTH, remote, - pretty_ref, - r ? "unable to update local ref" : "forced update"); + strbuf_addf(display, "%c %-*s %-*s -> %s (%s)", + r ? '!' : '+', + TRANSPORT_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)", - TRANSPORT_SUMMARY_WIDTH, "[rejected]", REFCOL_WIDTH, remote, - pretty_ref); + strbuf_addf(display, "! %-*s %-*s -> %s %s", + TRANSPORT_SUMMARY_WIDTH, _("[rejected]"), + REFCOL_WIDTH, remote, pretty_ref, + _("(non-fast-forward)")); return 1; } } +static int iterate_ref_map(void *cb_data, unsigned char sha1[20]) +{ + struct ref **rm = cb_data; + struct ref *ref = *rm; + + if (!ref) + return -1; /* end of the list */ + *rm = ref->next; + hashcpy(sha1, ref->old_sha1); + return 0; +} + 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]; + int url_len, i, shown_url = 0, rc = 0; + struct strbuf note = STRBUF_INIT; const char *what, *kind; struct ref *rm; char *url, *filename = dry_run ? "/dev/null" : git_path("FETCH_HEAD"); + int want_merge; fp = fopen(filename, "a"); if (!fp) - return error("cannot open %s: %s\n", filename, strerror(errno)); + 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; - } + rm = ref_map; + if (check_everything_connected(iterate_ref_map, 0, &rm)) { + rc = error(_("%s did not send all necessary objects\n"), url); + goto abort; + } - commit = lookup_commit_reference_gently(rm->old_sha1, 1); - if (!commit) - rm->merge = 0; + /* + * The first pass writes objects to be merged and then the + * second pass writes the rest, in order to allow using + * FETCH_HEAD as a refname to refer to the ref to be merged. + */ + for (want_merge = 1; 0 <= want_merge; want_merge--) { + for (rm = ref_map; rm; rm = rm->next) { + struct ref *ref = NULL; + + commit = lookup_commit_reference_gently(rm->old_sha1, 1); + if (!commit) + rm->merge = 0; + + if (rm->merge != want_merge) + continue; + + 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; + } - 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); - free(ref); - } else - sprintf(note, "* %-*s %-*s -> FETCH_HEAD", - TRANSPORT_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 (!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-tracking 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; + + strbuf_reset(¬e); + if (*what) { + if (*kind) + strbuf_addf(¬e, "%s ", kind); + strbuf_addf(¬e, "'%s' of ", what); + } + fprintf(fp, "%s\t%s\t%s", + sha1_to_hex(rm->old_sha1), + rm->merge ? "" : "not-for-merge", + note.buf); + for (i = 0; i < url_len; ++i) + if ('\n' == url[i]) + fputs("\\n", fp); + else + fputc(url[i], fp); + fputc('\n', fp); + + strbuf_reset(¬e); + if (ref) { + rc |= update_local_ref(ref, what, rm, ¬e); + free(ref); + } else + strbuf_addf(¬e, "* %-*s %-*s -> FETCH_HEAD", + TRANSPORT_SUMMARY_WIDTH, + *kind ? kind : "branch", + REFCOL_WIDTH, + *what ? what : "HEAD"); + if (note.len) { + if (verbosity >= 0 && !shown_url) { + fprintf(stderr, _("From %.*s\n"), + url_len, url); + shown_url = 1; + } + if (verbosity >= 0) + fprintf(stderr, " %s\n", note.buf); } - 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" + error(_("some local refs could not be updated; try running\n" " 'git remote prune %s' to remove any old, conflicting " - "branches", remote_name); + "branches"), remote_name); + + abort: + strbuf_release(¬e); + free(url); + fclose(fp); return rc; } @@ -424,23 +511,10 @@ static int store_updated_refs(const char *raw_url, const char *remote_name, * 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}; + struct ref *rm = ref_map; /* * If we are deepening a shallow clone we already have these @@ -451,47 +525,7 @@ static int quickfetch(struct ref *ref_map) */ 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; + return check_everything_connected(iterate_ref_map, 1, &rm); } static int fetch_refs(struct transport *transport, struct ref *ref_map) @@ -507,21 +541,21 @@ static int fetch_refs(struct transport *transport, struct ref *ref_map) return ret; } -static int prune_refs(struct transport *transport, struct ref *ref_map) +static int prune_refs(struct refspec *refs, int ref_count, struct ref *ref_map) { int result = 0; - struct ref *ref, *stale_refs = get_stale_heads(transport->remote, ref_map); + struct ref *ref, *stale_refs = get_stale_heads(refs, ref_count, ref_map); const char *dangling_msg = dry_run - ? " (%s will become dangling)\n" - : " (%s has become dangling)\n"; + ? _(" (%s will become dangling)") + : _(" (%s has become dangling)"); 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", - TRANSPORT_SUMMARY_WIDTH, "[deleted]", - REFCOL_WIDTH, "(none)", prettify_refname(ref->name)); + TRANSPORT_SUMMARY_WIDTH, _("[deleted]"), + REFCOL_WIDTH, _("(none)"), prettify_refname(ref->name)); warn_dangling_symref(stderr, dangling_msg, ref->name); } } @@ -560,7 +594,7 @@ static void find_non_local_tags(struct transport *transport, for_each_ref(add_existing, &existing_refs); for (ref = transport_get_remote_refs(transport); ref; ref = ref->next) { - if (prefixcmp(ref->name, "refs/tags")) + if (prefixcmp(ref->name, "refs/tags/")) continue; /* @@ -638,8 +672,8 @@ static void check_not_current_branch(struct ref *ref_map) 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); + die(_("Refusing to fetch into current branch %s " + "of non-bare repository"), current_branch->refname); } static int truncate_fetch_head(void) @@ -648,7 +682,7 @@ static int truncate_fetch_head(void) FILE *fp = fopen(filename, "w"); if (!fp) - return error("cannot open %s: %s\n", filename, strerror(errno)); + return error(_("cannot open %s: %s\n"), filename, strerror(errno)); fclose(fp); return 0; } @@ -672,7 +706,7 @@ static int do_fetch(struct transport *transport, } if (!transport->get_refs_list || !transport->fetch) - die("Don't know how to fetch from %s", transport->url); + die(_("Don't know how to fetch from %s"), transport->url); /* if not appending, truncate FETCH_HEAD */ if (!append && !dry_run) { @@ -701,8 +735,31 @@ static int do_fetch(struct transport *transport, free_refs(ref_map); return 1; } - if (prune) - prune_refs(transport, ref_map); + if (prune) { + /* If --tags was specified, pretend the user gave us the canonical tags refspec */ + if (tags == TAGS_SET) { + const char *tags_str = "refs/tags/*:refs/tags/*"; + struct refspec *tags_refspec, *refspec; + + /* Copy the refspec and add the tags to it */ + refspec = xcalloc(ref_count + 1, sizeof(struct refspec)); + tags_refspec = parse_fetch_refspec(1, &tags_str); + memcpy(refspec, refs, ref_count * sizeof(struct refspec)); + memcpy(&refspec[ref_count], tags_refspec, sizeof(struct refspec)); + ref_count++; + + prune_refs(refspec, ref_count, ref_map); + + ref_count--; + /* The rest of the strings belong to fetch_one */ + free_refspec(1, tags_refspec); + free(refspec); + } else if (ref_count) { + prune_refs(refs, ref_count, ref_map); + } else { + prune_refs(transport->remote->fetch, transport->remote->fetch_refspec_nr, ref_map); + } + } free_refs(ref_map); /* if neither --no-tags nor --tags was specified, do automated tag @@ -726,10 +783,10 @@ 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", + 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", + warning(_("Option \"%s\" is ignored for %s\n"), name, transport->url); } @@ -784,28 +841,38 @@ static int add_remote_or_group(const char *name, struct string_list *list) return 1; } -static int fetch_multiple(struct string_list *list) +static void add_options_to_argv(int *argc, const char **argv) { - int i, result = 0; - const char *argv[11] = { "fetch", "--append" }; - int argc = 2; - if (dry_run) - argv[argc++] = "--dry-run"; + argv[(*argc)++] = "--dry-run"; if (prune) - argv[argc++] = "--prune"; + argv[(*argc)++] = "--prune"; if (update_head_ok) - argv[argc++] = "--update-head-ok"; + argv[(*argc)++] = "--update-head-ok"; if (force) - argv[argc++] = "--force"; + argv[(*argc)++] = "--force"; if (keep) - argv[argc++] = "--keep"; + argv[(*argc)++] = "--keep"; + if (recurse_submodules == RECURSE_SUBMODULES_ON) + argv[(*argc)++] = "--recurse-submodules"; + else if (recurse_submodules == RECURSE_SUBMODULES_ON_DEMAND) + argv[(*argc)++] = "--recurse-submodules=on-demand"; if (verbosity >= 2) - argv[argc++] = "-v"; + argv[(*argc)++] = "-v"; if (verbosity >= 1) - argv[argc++] = "-v"; + argv[(*argc)++] = "-v"; else if (verbosity < 0) - argv[argc++] = "-q"; + argv[(*argc)++] = "-q"; + +} + +static int fetch_multiple(struct string_list *list) +{ + int i, result = 0; + const char *argv[12] = { "fetch", "--append" }; + int argc = 2; + + add_options_to_argv(&argc, argv); if (!append && !dry_run) { int errcode = truncate_fetch_head(); @@ -818,9 +885,9 @@ static int fetch_multiple(struct string_list *list) argv[argc] = name; argv[argc + 1] = NULL; if (verbosity >= 0) - printf("Fetching %s\n", name); + printf(_("Fetching %s\n"), name); if (run_command_v_opt(argv, RUN_GIT_CMD)) { - error("Could not fetch %s", name); + error(_("Could not fetch %s"), name); result = 1; } } @@ -832,12 +899,13 @@ static int fetch_one(struct remote *remote, int argc, const char **argv) { int i; static const char **refs = NULL; + struct refspec *refspec; int ref_nr = 0; int exit_code; if (!remote) - die("No remote repository specified. Please, specify either a URL or a\n" - "remote name from which new revisions should be fetched."); + die(_("No remote repository specified. Please, specify either a URL or a\n" + "remote name from which new revisions should be fetched.")); transport = transport_get(remote, NULL); transport_set_verbosity(transport, verbosity, progress); @@ -856,7 +924,7 @@ static int fetch_one(struct remote *remote, int argc, const char **argv) char *ref; i++; if (i >= argc) - die("You need to specify a tag name."); + die(_("You need to specify a tag name.")); ref = xmalloc(strlen(argv[i]) * 2 + 22); strcpy(ref, "refs/tags/"); strcat(ref, argv[i]); @@ -872,8 +940,9 @@ static int fetch_one(struct remote *remote, int argc, const char **argv) sigchain_push_common(unlock_pack_on_signal); atexit(unlock_pack); - exit_code = do_fetch(transport, - parse_fetch_refspec(ref_nr, refs), ref_nr); + refspec = parse_fetch_refspec(ref_nr, refs); + exit_code = do_fetch(transport, refspec, ref_nr); + free_refspec(ref_nr, refspec); transport_disconnect(transport); transport = NULL; return exit_code; @@ -886,6 +955,8 @@ int cmd_fetch(int argc, const char **argv, const char *prefix) struct remote *remote; int result = 0; + packet_trace_identity("fetch"); + /* Record the command line for the reflog */ strbuf_addstr(&default_rla, "fetch"); for (i = 1; i < argc; i++) @@ -894,11 +965,20 @@ int cmd_fetch(int argc, const char **argv, const char *prefix) argc = parse_options(argc, argv, prefix, builtin_fetch_options, builtin_fetch_usage, 0); + if (recurse_submodules != RECURSE_SUBMODULES_OFF) { + if (recurse_submodules_default) { + int arg = parse_fetch_recurse_submodules_arg("--recurse-submodules-default", recurse_submodules_default); + set_config_fetch_recurse_submodules(arg); + } + gitmodules_config(); + git_config(submodule_config, NULL); + } + if (all) { if (argc == 1) - die("fetch --all does not take a repository argument"); + die(_("fetch --all does not take a repository argument")); else if (argc > 1) - die("fetch --all does not make sense with refspecs"); + 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) { @@ -909,7 +989,7 @@ int cmd_fetch(int argc, const char **argv, const char *prefix) /* 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]); + die(_("No such remote or remote group: %s"), argv[i]); result = fetch_multiple(&list); } else { /* Single remote or group */ @@ -917,7 +997,7 @@ int cmd_fetch(int argc, const char **argv, const char *prefix) if (list.nr > 1) { /* More than one remote */ if (argc > 1) - die("Fetching a group and specifying refspecs does not make sense"); + die(_("Fetching a group and specifying refspecs does not make sense")); result = fetch_multiple(&list); } else { /* Zero or one remotes */ @@ -926,6 +1006,16 @@ int cmd_fetch(int argc, const char **argv, const char *prefix) } } + if (!result && (recurse_submodules != RECURSE_SUBMODULES_OFF)) { + const char *options[10]; + int num_options = 0; + add_options_to_argv(&num_options, options); + result = fetch_populated_submodules(num_options, options, + submodule_prefix, + recurse_submodules, + verbosity < 0); + } + /* All names were strdup()ed or strndup()ed */ list.strdup_strings = 1; string_list_clear(&list, 0); diff --git a/builtin/fmt-merge-msg.c b/builtin/fmt-merge-msg.c index 78c77742b6..2c4d435da1 100644 --- a/builtin/fmt-merge-msg.c +++ b/builtin/fmt-merge-msg.c @@ -5,33 +5,46 @@ #include "revision.h" #include "tag.h" #include "string-list.h" +#include "branch.h" +#include "fmt-merge-msg.h" +#include "gpg-interface.h" static const char * const fmt_merge_msg_usage[] = { "git fmt-merge-msg [-m <message>] [--log[=<n>]|--no-log] [--file <file>]", NULL }; -static int shortlog_len; +static int use_branch_desc; -static int fmt_merge_msg_config(const char *key, const char *value, void *cb) +int fmt_merge_msg_config(const char *key, const char *value, void *cb) { if (!strcmp(key, "merge.log") || !strcmp(key, "merge.summary")) { int is_bool; - shortlog_len = git_config_bool_or_int(key, value, &is_bool); - if (!is_bool && shortlog_len < 0) + merge_log_config = git_config_bool_or_int(key, value, &is_bool); + if (!is_bool && merge_log_config < 0) return error("%s: negative length %s", key, value); - if (is_bool && shortlog_len) - shortlog_len = DEFAULT_MERGE_LOG_LEN; + if (is_bool && merge_log_config) + merge_log_config = DEFAULT_MERGE_LOG_LEN; + } else if (!strcmp(key, "merge.branchdesc")) { + use_branch_desc = git_config_bool(key, value); + } else { + return git_default_config(key, value, cb); } return 0; } +/* merge data per repository where the merged tips came from */ struct src_data { struct string_list branch, tag, r_branch, generic; int head_status; }; -void init_src_data(struct src_data *data) +struct origin_data { + unsigned char sha1[20]; + unsigned is_local_branch:1; +}; + +static void init_src_data(struct src_data *data) { data->branch.strdup_strings = 1; data->tag.strdup_strings = 1; @@ -42,14 +55,56 @@ void init_src_data(struct src_data *data) static struct string_list srcs = STRING_LIST_INIT_DUP; static struct string_list origins = STRING_LIST_INIT_DUP; -static int handle_line(char *line) +struct merge_parents { + int alloc, nr; + struct merge_parent { + unsigned char given[20]; + unsigned char commit[20]; + unsigned char used; + } *item; +}; + +/* + * I know, I know, this is inefficient, but you won't be pulling and merging + * hundreds of heads at a time anyway. + */ +static struct merge_parent *find_merge_parent(struct merge_parents *table, + unsigned char *given, + unsigned char *commit) +{ + int i; + for (i = 0; i < table->nr; i++) { + if (given && hashcmp(table->item[i].given, given)) + continue; + if (commit && hashcmp(table->item[i].commit, commit)) + continue; + return &table->item[i]; + } + return NULL; +} + +static void add_merge_parent(struct merge_parents *table, + unsigned char *given, + unsigned char *commit) +{ + if (table->nr && find_merge_parent(table, given, commit)) + return; + ALLOC_GROW(table->item, table->nr + 1, table->alloc); + hashcpy(table->item[table->nr].given, given); + hashcpy(table->item[table->nr].commit, commit); + table->item[table->nr].used = 0; + table->nr++; +} + +static int handle_line(char *line, struct merge_parents *merge_parents) { int i, len = strlen(line); - unsigned char *sha1; + struct origin_data *origin_data; char *src, *origin; struct src_data *src_data; struct string_list_item *item; int pulling_head = 0; + unsigned char sha1[20]; if (len < 43 || line[40] != '\t') return 1; @@ -60,17 +115,25 @@ static int handle_line(char *line) if (line[41] != '\t') return 2; - line[40] = 0; - sha1 = xmalloc(20); - i = get_sha1(line, sha1); - line[40] = '\t'; + i = get_sha1_hex(line, sha1); if (i) return 3; + if (!find_merge_parent(merge_parents, sha1, NULL)) + return 0; /* subsumed by other parents */ + + origin_data = xcalloc(1, sizeof(struct origin_data)); + hashcpy(origin_data->sha1, sha1); + if (line[len - 1] == '\n') line[len - 1] = 0; line += 42; + /* + * At this point, line points at the beginning of comment e.g. + * "branch 'frotz' of git://that/repository.git". + * Find the repository name and point it with src. + */ src = strstr(line, " of "); if (src) { *src = 0; @@ -93,6 +156,7 @@ static int handle_line(char *line) origin = src; src_data->head_status |= 1; } else if (!prefixcmp(line, "branch ")) { + origin_data->is_local_branch = 1; origin = line + 7; string_list_append(&src_data->branch, origin); src_data->head_status |= 2; @@ -100,8 +164,8 @@ static int handle_line(char *line) origin = line; string_list_append(&src_data->tag, origin + 4); src_data->head_status |= 2; - } else if (!prefixcmp(line, "remote branch ")) { - origin = line + 14; + } else if (!prefixcmp(line, "remote-tracking branch ")) { + origin = line + strlen("remote-tracking branch "); string_list_append(&src_data->r_branch, origin); src_data->head_status |= 2; } else { @@ -119,7 +183,9 @@ static int handle_line(char *line) sprintf(new_origin, "%s of %s", origin, src); origin = new_origin; } - string_list_append(&origins, origin)->util = sha1; + if (strcmp(".", src)) + origin_data->is_local_branch = 0; + string_list_append(&origins, origin)->util = origin_data; return 0; } @@ -140,23 +206,141 @@ static void print_joined(const char *singular, const char *plural, } } -static void shortlog(const char *name, unsigned char *sha1, - struct commit *head, struct rev_info *rev, int limit, - struct strbuf *out) +static void add_branch_desc(struct strbuf *out, const char *name) +{ + struct strbuf desc = STRBUF_INIT; + + if (!read_branch_desc(&desc, name)) { + const char *bp = desc.buf; + while (*bp) { + const char *ep = strchrnul(bp, '\n'); + if (*ep) + ep++; + strbuf_addf(out, " : %.*s", (int)(ep - bp), bp); + bp = ep; + } + if (out->buf[out->len - 1] != '\n') + strbuf_addch(out, '\n'); + } + strbuf_release(&desc); +} + +#define util_as_integral(elem) ((intptr_t)((elem)->util)) + +static void record_person(int which, struct string_list *people, + struct commit *commit) +{ + char *name_buf, *name, *name_end; + struct string_list_item *elem; + const char *field = (which == 'a') ? "\nauthor " : "\ncommitter "; + + name = strstr(commit->buffer, field); + if (!name) + return; + name += strlen(field); + name_end = strchrnul(name, '<'); + if (*name_end) + name_end--; + while (isspace(*name_end) && name <= name_end) + name_end--; + if (name_end < name) + return; + name_buf = xmemdupz(name, name_end - name + 1); + + elem = string_list_lookup(people, name_buf); + if (!elem) { + elem = string_list_insert(people, name_buf); + elem->util = (void *)0; + } + elem->util = (void*)(util_as_integral(elem) + 1); + free(name_buf); +} + +static int cmp_string_list_util_as_integral(const void *a_, const void *b_) +{ + const struct string_list_item *a = a_, *b = b_; + return util_as_integral(b) - util_as_integral(a); +} + +static void add_people_count(struct strbuf *out, struct string_list *people) +{ + if (people->nr == 1) + strbuf_addf(out, "%s", people->items[0].string); + else if (people->nr == 2) + strbuf_addf(out, "%s (%d) and %s (%d)", + people->items[0].string, + (int)util_as_integral(&people->items[0]), + people->items[1].string, + (int)util_as_integral(&people->items[1])); + else if (people->nr) + strbuf_addf(out, "%s (%d) and others", + people->items[0].string, + (int)util_as_integral(&people->items[0])); +} + +static void credit_people(struct strbuf *out, + struct string_list *them, + int kind) +{ + const char *label; + const char *me; + + if (kind == 'a') { + label = "\n# By "; + me = git_author_info(IDENT_NO_DATE); + } else { + label = "\n# Via "; + me = git_committer_info(IDENT_NO_DATE); + } + + if (!them->nr || + (them->nr == 1 && + me && + (me = skip_prefix(me, them->items->string)) != NULL && + skip_prefix(me, " <"))) + return; + strbuf_addstr(out, label); + add_people_count(out, them); +} + +static void add_people_info(struct strbuf *out, + struct string_list *authors, + struct string_list *committers) +{ + if (authors->nr) + qsort(authors->items, + authors->nr, sizeof(authors->items[0]), + cmp_string_list_util_as_integral); + if (committers->nr) + qsort(committers->items, + committers->nr, sizeof(committers->items[0]), + cmp_string_list_util_as_integral); + + credit_people(out, authors, 'a'); + credit_people(out, committers, 'c'); +} + +static void shortlog(const char *name, + struct origin_data *origin_data, + struct commit *head, + struct rev_info *rev, int limit, + struct strbuf *out) { int i, count = 0; struct commit *commit; struct object *branch; struct string_list subjects = STRING_LIST_INIT_DUP; + struct string_list authors = STRING_LIST_INIT_DUP; + struct string_list committers = STRING_LIST_INIT_DUP; int flags = UNINTERESTING | TREESAME | SEEN | SHOWN | ADDED; struct strbuf sb = STRBUF_INIT; + const unsigned char *sha1 = origin_data->sha1; 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; @@ -165,10 +349,15 @@ static void shortlog(const char *name, unsigned char *sha1, while ((commit = get_revision(rev)) != NULL) { struct pretty_print_context ctx = {0}; - /* ignore merges */ - if (commit->parents && commit->parents->next) + if (commit->parents && commit->parents->next) { + /* do not list a merge but count committer */ + record_person('c', &committers, commit); continue; - + } + if (!count) + /* the 'tip' committer */ + record_person('c', &committers, commit); + record_person('a', &authors, commit); count++; if (subjects.nr > limit) continue; @@ -183,11 +372,15 @@ static void shortlog(const char *name, unsigned char *sha1, string_list_append(&subjects, strbuf_detach(&sb, NULL)); } + add_people_info(out, &authors, &committers); if (count > limit) strbuf_addf(out, "\n* %s: (%d commits)\n", name, count); else strbuf_addf(out, "\n* %s:\n", name); + if (origin_data->is_local_branch && use_branch_desc) + add_branch_desc(out, name); + for (i = 0; i < subjects.nr; i++) if (i >= limit) strbuf_addf(out, " ...\n"); @@ -200,10 +393,12 @@ static void shortlog(const char *name, unsigned char *sha1, rev->commits = NULL; rev->pending.nr = 0; + string_list_clear(&authors, 0); + string_list_clear(&committers, 0); string_list_clear(&subjects, 0); } -static void do_fmt_merge_msg_title(struct strbuf *out, +static void fmt_merge_msg_title(struct strbuf *out, const char *current_branch) { int i = 0; char *sep = ""; @@ -233,7 +428,7 @@ static void do_fmt_merge_msg_title(struct strbuf *out, if (src_data->r_branch.nr) { strbuf_addstr(out, subsep); subsep = ", "; - print_joined("remote branch ", "remote branches ", + print_joined("remote-tracking branch ", "remote-tracking branches ", &src_data->r_branch, out); } if (src_data->tag.nr) { @@ -256,19 +451,155 @@ static void do_fmt_merge_msg_title(struct strbuf *out, strbuf_addf(out, " into %s\n", current_branch); } -static int do_fmt_merge_msg(int merge_title, struct strbuf *in, - struct strbuf *out, int shortlog_len) { +static void fmt_tag_signature(struct strbuf *tagbuf, + struct strbuf *sig, + const char *buf, + unsigned long len) +{ + const char *tag_body = strstr(buf, "\n\n"); + if (tag_body) { + tag_body += 2; + strbuf_add(tagbuf, tag_body, buf + len - tag_body); + } + strbuf_complete_line(tagbuf); + if (sig->len) { + strbuf_addch(tagbuf, '\n'); + strbuf_add_lines(tagbuf, "# ", sig->buf, sig->len); + } +} + +static void fmt_merge_msg_sigs(struct strbuf *out) +{ + int i, tag_number = 0, first_tag = 0; + struct strbuf tagbuf = STRBUF_INIT; + + for (i = 0; i < origins.nr; i++) { + unsigned char *sha1 = origins.items[i].util; + enum object_type type; + unsigned long size, len; + char *buf = read_sha1_file(sha1, &type, &size); + struct strbuf sig = STRBUF_INIT; + + if (!buf || type != OBJ_TAG) + goto next; + len = parse_signature(buf, size); + + if (size == len) + ; /* merely annotated */ + else if (verify_signed_buffer(buf, len, buf + len, size - len, &sig)) { + if (!sig.len) + strbuf_addstr(&sig, "gpg verification failed.\n"); + } + + if (!tag_number++) { + fmt_tag_signature(&tagbuf, &sig, buf, len); + first_tag = i; + } else { + if (tag_number == 2) { + struct strbuf tagline = STRBUF_INIT; + strbuf_addf(&tagline, "\n# %s\n", + origins.items[first_tag].string); + strbuf_insert(&tagbuf, 0, tagline.buf, + tagline.len); + strbuf_release(&tagline); + } + strbuf_addf(&tagbuf, "\n# %s\n", + origins.items[i].string); + fmt_tag_signature(&tagbuf, &sig, buf, len); + } + strbuf_release(&sig); + next: + free(buf); + } + if (tagbuf.len) { + strbuf_addch(out, '\n'); + strbuf_addbuf(out, &tagbuf); + } + strbuf_release(&tagbuf); +} + +static void find_merge_parents(struct merge_parents *result, + struct strbuf *in, unsigned char *head) +{ + struct commit_list *parents, *next; + struct commit *head_commit; + int pos = 0, i, j; + + parents = NULL; + while (pos < in->len) { + int len; + char *p = in->buf + pos; + char *newline = strchr(p, '\n'); + unsigned char sha1[20]; + struct commit *parent; + struct object *obj; + + len = newline ? newline - p : strlen(p); + pos += len + !!newline; + + if (len < 43 || + get_sha1_hex(p, sha1) || + p[40] != '\t' || + p[41] != '\t') + continue; /* skip not-for-merge */ + /* + * Do not use get_merge_parent() here; we do not have + * "name" here and we do not want to contaminate its + * util field yet. + */ + obj = parse_object(sha1); + parent = (struct commit *)peel_to_type(NULL, 0, obj, OBJ_COMMIT); + if (!parent) + continue; + commit_list_insert(parent, &parents); + add_merge_parent(result, obj->sha1, parent->object.sha1); + } + head_commit = lookup_commit(head); + if (head_commit) + commit_list_insert(head_commit, &parents); + parents = reduce_heads(parents); + + while (parents) { + for (i = 0; i < result->nr; i++) + if (!hashcmp(result->item[i].commit, + parents->item->object.sha1)) + result->item[i].used = 1; + next = parents->next; + free(parents); + parents = next; + } + + for (i = j = 0; i < result->nr; i++) { + if (result->item[i].used) { + if (i != j) + result->item[j] = result->item[i]; + j++; + } + } + result->nr = j; +} + +int fmt_merge_msg(struct strbuf *in, struct strbuf *out, + struct fmt_merge_msg_opts *opts) +{ int i = 0, pos = 0; unsigned char head_sha1[20]; const char *current_branch; + void *current_branch_to_free; + struct merge_parents merge_parents; + + memset(&merge_parents, 0, sizeof(merge_parents)); /* get current branch */ - current_branch = resolve_ref("HEAD", head_sha1, 1, NULL); + current_branch = current_branch_to_free = + resolve_refdup("HEAD", head_sha1, 1, NULL); if (!current_branch) die("No current branch"); if (!prefixcmp(current_branch, "refs/heads/")) current_branch += 11; + find_merge_parents(&merge_parents, in, head_sha1); + /* get a line */ while (pos < in->len) { int len; @@ -279,45 +610,45 @@ static int do_fmt_merge_msg(int merge_title, struct strbuf *in, pos += len + !!newline; i++; p[len] = 0; - if (handle_line(p)) + if (handle_line(p, &merge_parents)) die ("Error in line %d: %.*s", i, len, p); } - if (!srcs.nr) - return 0; + if (opts->add_title && srcs.nr) + fmt_merge_msg_title(out, current_branch); - if (merge_title) - do_fmt_merge_msg_title(out, current_branch); + if (origins.nr) + fmt_merge_msg_sigs(out); - if (shortlog_len) { + if (opts->shortlog_len) { struct commit *head; struct rev_info rev; - head = lookup_commit(head_sha1); + head = lookup_commit_or_die(head_sha1, "HEAD"); init_revisions(&rev, NULL); rev.commit_format = CMIT_FMT_ONELINE; rev.ignore_merges = 1; rev.limited = 1; - if (suffixcmp(out->buf, "\n")) - strbuf_addch(out, '\n'); + strbuf_complete_line(out); for (i = 0; i < origins.nr; i++) - shortlog(origins.items[i].string, origins.items[i].util, - head, &rev, shortlog_len, out); + shortlog(origins.items[i].string, + origins.items[i].util, + head, &rev, opts->shortlog_len, out); } - return 0; -} -int fmt_merge_msg(struct strbuf *in, struct strbuf *out, - int merge_title, int shortlog_len) { - return do_fmt_merge_msg(merge_title, in, out, shortlog_len); + strbuf_complete_line(out); + free(current_branch_to_free); + free(merge_parents.item); + return 0; } int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix) { const char *inpath = NULL; const char *message = NULL; + int shortlog_len = -1; struct option options[] = { { OPTION_INTEGER, 0, "log", &shortlog_len, "n", "populate log with at most <n> entries from shortlog", @@ -335,20 +666,15 @@ int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix) FILE *in = stdin; struct strbuf input = STRBUF_INIT, output = STRBUF_INIT; int ret; + struct fmt_merge_msg_opts opts; 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 (message && !shortlog_len) { - char nl = '\n'; - write_in_full(STDOUT_FILENO, message, strlen(message)); - write_in_full(STDOUT_FILENO, &nl, 1); - return 0; - } if (shortlog_len < 0) - die("Negative --log=%d", shortlog_len); + shortlog_len = (merge_log_config > 0) ? merge_log_config : 0; if (inpath && strcmp(inpath, "-")) { in = fopen(inpath, "r"); @@ -361,10 +687,12 @@ int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix) if (message) strbuf_addstr(&output, message); - ret = fmt_merge_msg(&input, &output, - message ? 0 : 1, - shortlog_len); + memset(&opts, 0, sizeof(opts)); + opts.add_title = !message; + opts.shortlog_len = shortlog_len; + + ret = fmt_merge_msg(&input, &output, &opts); if (ret) return ret; write_in_full(STDOUT_FILENO, output.buf, output.len); diff --git a/builtin/for-each-ref.c b/builtin/for-each-ref.c index 89e75c6894..b01d76a243 100644 --- a/builtin/for-each-ref.c +++ b/builtin/for-each-ref.c @@ -69,6 +69,9 @@ static struct { { "subject" }, { "body" }, { "contents" }, + { "contents:subject" }, + { "contents:body" }, + { "contents:signature" }, { "upstream" }, { "symref" }, { "flag" }, @@ -361,6 +364,18 @@ static const char *copy_email(const char *buf) return xmemdupz(email, eoemail + 1 - email); } +static char *copy_subject(const char *buf, unsigned long len) +{ + char *r = xmemdupz(buf, len); + int i; + + for (i = 0; i < len; i++) + if (r[i] == '\n') + r[i] = ' '; + + return r; +} + static void grab_date(const char *buf, struct atom_value *v, const char *atomname) { const char *eoemail = strstr(buf, "> "); @@ -458,38 +473,56 @@ static void grab_person(const char *who, struct atom_value *val, int deref, stru } } -static void find_subpos(const char *buf, unsigned long sz, const char **sub, const char **body) +static void find_subpos(const char *buf, unsigned long sz, + const char **sub, unsigned long *sublen, + const char **body, unsigned long *bodylen, + unsigned long *nonsiglen, + const char **sig, unsigned long *siglen) { - 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; + const char *eol; + /* skip past header until we hit empty line */ + while (*buf && *buf != '\n') { + eol = strchrnul(buf, '\n'); + if (*eol) + eol++; + buf = eol; } + /* skip any empty lines */ while (*buf == '\n') buf++; - if (!*buf) - return; - *sub = buf; /* first non-empty line */ - buf = strchr(buf, '\n'); - if (!buf) { - *body = ""; - return; /* no body */ + + /* parse signature first; we might not even have a subject line */ + *sig = buf + parse_signature(buf, strlen(buf)); + *siglen = strlen(*sig); + + /* subject is first non-empty line */ + *sub = buf; + /* subject goes to first empty line */ + while (buf < *sig && *buf && *buf != '\n') { + eol = strchrnul(buf, '\n'); + if (*eol) + eol++; + buf = eol; } + *sublen = buf - *sub; + /* drop trailing newline, if present */ + if (*sublen && (*sub)[*sublen - 1] == '\n') + *sublen -= 1; + + /* skip any empty lines */ while (*buf == '\n') - buf++; /* skip blank between subject and body */ + buf++; *body = buf; + *bodylen = strlen(buf); + *nonsiglen = *sig - 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; + const char *subpos = NULL, *bodypos = NULL, *sigpos = NULL; + unsigned long sublen = 0, bodylen = 0, nonsiglen = 0, siglen = 0; for (i = 0; i < used_atom_cnt; i++) { const char *name = used_atom[i]; @@ -500,17 +533,27 @@ static void grab_sub_body_contents(struct atom_value *val, int deref, struct obj name++; if (strcmp(name, "subject") && strcmp(name, "body") && - strcmp(name, "contents")) + strcmp(name, "contents") && + strcmp(name, "contents:subject") && + strcmp(name, "contents:body") && + strcmp(name, "contents:signature")) continue; if (!subpos) - find_subpos(buf, sz, &subpos, &bodypos); - if (!subpos) - return; + find_subpos(buf, sz, + &subpos, &sublen, + &bodypos, &bodylen, &nonsiglen, + &sigpos, &siglen); if (!strcmp(name, "subject")) - v->s = copy_line(subpos); + v->s = copy_subject(subpos, sublen); + else if (!strcmp(name, "contents:subject")) + v->s = copy_subject(subpos, sublen); else if (!strcmp(name, "body")) - v->s = xstrdup(bodypos); + v->s = xmemdupz(bodypos, bodylen); + else if (!strcmp(name, "contents:body")) + v->s = xmemdupz(bodypos, nonsiglen); + else if (!strcmp(name, "contents:signature")) + v->s = xmemdupz(sigpos, siglen); else if (!strcmp(name, "contents")) v->s = xstrdup(subpos); } @@ -585,11 +628,8 @@ static void populate_value(struct refinfo *ref) if (need_symref && (ref->flag & REF_ISSYMREF) && !ref->symref) { unsigned char unused1[20]; - const char *symref; - symref = resolve_ref(ref->refname, unused1, 1, NULL); - if (symref) - ref->symref = xstrdup(symref); - else + ref->symref = resolve_refdup(ref->refname, unused1, 1, NULL); + if (!ref->symref) ref->symref = ""; } diff --git a/builtin/fsck.c b/builtin/fsck.c index 0929c7f245..a710227a64 100644 --- a/builtin/fsck.c +++ b/builtin/fsck.c @@ -11,6 +11,8 @@ #include "fsck.h" #include "parse-options.h" #include "dir.h" +#include "progress.h" +#include "streaming.h" #define REACHABLE 0x0001 #define SEEN 0x0002 @@ -27,8 +29,11 @@ static const char *head_points_at; static int errors_found; static int write_lost_and_found; static int verbose; +static int show_progress = -1; +static int show_dangling = 1; #define ERROR_OBJECT 01 #define ERROR_REACHABLE 02 +#define ERROR_PACK 04 #ifdef NO_D_INO_IN_DIRENT #define SORT_DIRENT 0 @@ -74,7 +79,13 @@ static int mark_object(struct object *obj, int type, void *data) { struct object *parent = data; + /* + * The only case data is NULL or type is OBJ_ANY is when + * mark_object_reachable() calls us. All the callers of + * that function has non-NULL obj hence ... + */ if (!obj) { + /* ... these references to parent->fld are safe here */ printf("broken link from %7s %s\n", typename(parent->type), sha1_to_hex(parent->sha1)); printf("broken link from %7s %s\n", @@ -84,6 +95,7 @@ static int mark_object(struct object *obj, int type, void *data) } if (type != OBJ_ANY && obj->type != type) + /* ... and the reference to parent is safe here */ objerror(parent, "wrong object type in link"); if (obj->flags & REACHABLE) @@ -109,7 +121,7 @@ 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) +static int traverse_one_object(struct object *obj) { int result; struct tree *tree = NULL; @@ -130,16 +142,21 @@ static int traverse_one_object(struct object *obj, struct object *parent) static int traverse_reachable(void) { + struct progress *progress = NULL; + unsigned int nr = 0; int result = 0; + if (show_progress) + progress = start_progress_delay("Checking connectivity", 0, 0, 2); while (pending.nr) { struct object_array_entry *entry; - struct object *obj, *parent; + struct object *obj; entry = pending.objects + --pending.nr; obj = entry->item; - parent = (struct object *) entry->name; - result |= traverse_one_object(obj, parent); + result |= traverse_one_object(obj); + display_progress(progress, ++nr); } + stop_progress(&progress); return !!result; } @@ -206,8 +223,9 @@ static void check_unreachable_object(struct object *obj) * start looking at, for example. */ if (!obj->used) { - printf("dangling %s %s\n", typename(obj->type), - sha1_to_hex(obj->sha1)); + if (show_dangling) + 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", @@ -221,16 +239,8 @@ static void check_unreachable_object(struct object *obj) 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); - } + if (stream_blob_to_fd(fileno(f), obj->sha1, NULL, 1)) + die_errno("Could not write '%s'", filename); } else fprintf(f, "%s\n", sha1_to_hex(obj->sha1)); if (fclose(f)) @@ -278,14 +288,8 @@ static void check_connectivity(void) } } -static int fsck_sha1(const unsigned char *sha1) +static int fsck_obj(struct object *obj) { - 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; @@ -328,6 +332,29 @@ static int fsck_sha1(const unsigned char *sha1) return 0; } +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)); + } + return fsck_obj(obj); +} + +static int fsck_obj_buffer(const unsigned char *sha1, enum object_type type, + unsigned long size, void *buffer, int *eaten) +{ + struct object *obj; + obj = parse_object_buffer(sha1, type, size, buffer, eaten); + if (!obj) { + errors_found |= ERROR_OBJECT; + return error("%s: object corrupt or missing", sha1_to_hex(sha1)); + } + return fsck_obj(obj); +} + /* * This is the sorting chunk size: make it reasonably * big so that we can sort well.. @@ -385,10 +412,20 @@ static void add_sha1_list(unsigned char *sha1, unsigned long ino) sha1_list.nr = ++nr; } +static inline int is_loose_object_file(struct dirent *de, + char *name, unsigned char *sha1) +{ + if (strlen(de->d_name) != 38) + return 0; + memcpy(name + 2, de->d_name, 39); + return !get_sha1_hex(name, sha1); +} + static void fsck_dir(int i, char *path) { DIR *dir = opendir(path); struct dirent *de; + char name[100]; if (!dir) return; @@ -396,17 +433,13 @@ static void fsck_dir(int i, char *path) if (verbose) fprintf(stderr, "Checking directory %s\n", path); + sprintf(name, "%02x", i); 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; + if (is_loose_object_file(de, name, sha1)) { add_sha1_list(sha1, DIRENT_SORT_HINT(de)); continue; } @@ -503,15 +536,20 @@ static void get_default_heads(void) static void fsck_object_dir(const char *path) { int i; + struct progress *progress = NULL; if (verbose) fprintf(stderr, "Checking object directory\n"); + if (show_progress) + progress = start_progress("Checking object directories", 256); for (i = 0; i < 256; i++) { static char dir[4096]; sprintf(dir, "%s/%02x", path, i); fsck_dir(i, dir); + display_progress(progress, i+1); } + stop_progress(&progress); fsck_sha1_list(); } @@ -523,7 +561,7 @@ static int fsck_head_link(void) if (verbose) fprintf(stderr, "Checking HEAD link\n"); - head_points_at = resolve_ref("HEAD", head_sha1, 0, &flag); + head_points_at = resolve_ref_unsafe("HEAD", head_sha1, 0, &flag); if (!head_points_at) return error("Invalid HEAD"); if (!strcmp(head_points_at, "HEAD")) @@ -556,8 +594,8 @@ static int fsck_cache_tree(struct cache_tree *it) sha1_to_hex(it->sha1)); return 1; } - mark_object_reachable(obj); obj->used = 1; + mark_object_reachable(obj); if (obj->type != OBJ_TREE) err |= objerror(obj, "non-tree in cache-tree"); } @@ -572,8 +610,9 @@ static char const * const fsck_usage[] = { }; static struct option fsck_opts[] = { - OPT__VERBOSE(&verbose), + OPT__VERBOSE(&verbose, "be verbose"), OPT_BOOLEAN(0, "unreachable", &show_unreachable, "show unreachable objects"), + OPT_BOOL(0, "dangling", &show_dangling, "show dangling 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"), @@ -582,6 +621,7 @@ static struct option fsck_opts[] = { 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_BOOL(0, "progress", &show_progress, "show progress"), OPT_END(), }; @@ -594,6 +634,12 @@ int cmd_fsck(int argc, const char **argv, const char *prefix) read_replace_refs = 0; argc = parse_options(argc, argv, prefix, fsck_opts, fsck_usage, 0); + + if (show_progress == -1) + show_progress = isatty(2); + if (verbose) + show_progress = 0; + if (write_lost_and_found) { check_full = 1; include_reflogs = 0; @@ -613,20 +659,28 @@ int cmd_fsck(int argc, const char **argv, const char *prefix) if (check_full) { struct packed_git *p; + uint32_t total = 0, count = 0; + struct progress *progress = NULL; prepare_packed_git(); - for (p = packed_git; p; p = p->next) - /* verify gives error messages itself */ - verify_pack(p); + if (show_progress) { + for (p = packed_git; p; p = p->next) { + if (open_pack_index(p)) + continue; + total += p->num_objects; + } + + progress = start_progress("Checking objects", total); + } 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)); + /* verify gives error messages itself */ + if (verify_pack(p, fsck_obj_buffer, + progress, count)) + errors_found |= ERROR_PACK; + count += p->num_objects; } + stop_progress(&progress); } heads = 0; diff --git a/builtin/gc.c b/builtin/gc.c index c304638b78..9b4232c8f3 100644 --- a/builtin/gc.c +++ b/builtin/gc.c @@ -14,6 +14,7 @@ #include "cache.h" #include "parse-options.h" #include "run-command.h" +#include "argv-array.h" #define FAILED_RUN "failed to run %s" @@ -28,12 +29,11 @@ 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 struct argv_array pack_refs_cmd = ARGV_ARRAY_INIT; +static struct argv_array reflog = ARGV_ARRAY_INIT; +static struct argv_array repack = ARGV_ARRAY_INIT; +static struct argv_array prune = ARGV_ARRAY_INIT; +static struct argv_array rerere = ARGV_ARRAY_INIT; static int gc_config(const char *var, const char *value, void *cb) { @@ -60,26 +60,13 @@ static int gc_config(const char *var, const char *value, void *cb) if (value && strcmp(value, "now")) { unsigned long now = approxidate("now"); if (approxidate(value) >= now) - return error("Invalid %s: '%s'", var, value); + 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) { /* @@ -100,7 +87,7 @@ static int too_many_loose_objects(void) return 0; if (sizeof(path) <= snprintf(path, sizeof(path), "%s/17", objdir)) { - warning("insanely long object directory %.*s", 50, objdir); + warning(_("insanely long object directory %.*s"), 50, objdir); return 0; } dir = opendir(path); @@ -144,6 +131,17 @@ static int too_many_packs(void) return gc_auto_pack_limit <= cnt; } +static void add_repack_all_option(void) +{ + if (prune_expire && !strcmp(prune_expire, "now")) + argv_array_push(&repack, "-a"); + else { + argv_array_push(&repack, "-A"); + if (prune_expire) + argv_array_pushf(&repack, "--unpack-unreachable=%s", prune_expire); + } +} + static int need_to_gc(void) { /* @@ -160,10 +158,7 @@ static int need_to_gc(void) * there is no need. */ if (too_many_packs()) - append_option(argv_repack, - prune_expire && !strcmp(prune_expire, "now") ? - "-a" : "-A", - MAX_ADD); + add_repack_all_option(); else if (!too_many_loose_objects()) return 0; @@ -177,10 +172,9 @@ 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), + OPT__QUIET(&quiet, "suppress progress reporting"), { OPTION_STRING, 0, "prune", &prune_expire, "date", "prune unreferenced objects", PARSE_OPT_OPTARG, NULL, (intptr_t)prune_expire }, @@ -189,6 +183,15 @@ int cmd_gc(int argc, const char **argv, const char *prefix) OPT_END() }; + if (argc == 2 && !strcmp(argv[1], "-h")) + usage_with_options(builtin_gc_usage, builtin_gc_options); + + argv_array_pushl(&pack_refs_cmd, "pack-refs", "--all", "--prune", NULL); + argv_array_pushl(&reflog, "reflog", "expire", "--all", NULL); + argv_array_pushl(&repack, "repack", "-d", "-l", NULL); + argv_array_pushl(&prune, "prune", "--expire", NULL ); + argv_array_pushl(&rerere, "rerere", "gc", NULL); + git_config(gc_config, NULL); if (pack_refs < 0) @@ -200,15 +203,13 @@ int cmd_gc(int argc, const char **argv, const char *prefix) 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); - } + argv_array_push(&repack, "-f"); + argv_array_push(&repack, "--depth=250"); + if (aggressive_window > 0) + argv_array_pushf(&repack, "--window=%d", aggressive_window); } if (quiet) - append_option(argv_repack, "-q", MAX_ADD); + argv_array_push(&repack, "-q"); if (auto_gc) { /* @@ -216,40 +217,39 @@ int cmd_gc(int argc, const char **argv, const char *prefix) */ 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.")); + if (quiet) + fprintf(stderr, _("Auto packing the repository for optimum performance.\n")); + else + fprintf(stderr, + _("Auto packing the repository for optimum performance. You may also\n" + "run \"git gc\" manually. See " + "\"git help gc\" for more information.\n")); } else - append_option(argv_repack, - prune_expire && !strcmp(prune_expire, "now") - ? "-a" : "-A", - MAX_ADD); + add_repack_all_option(); - if (pack_refs && run_command_v_opt(argv_pack_refs, RUN_GIT_CMD)) - return error(FAILED_RUN, argv_pack_refs[0]); + if (pack_refs && run_command_v_opt(pack_refs_cmd.argv, RUN_GIT_CMD)) + return error(FAILED_RUN, pack_refs_cmd.argv[0]); - if (run_command_v_opt(argv_reflog, RUN_GIT_CMD)) - return error(FAILED_RUN, argv_reflog[0]); + if (run_command_v_opt(reflog.argv, RUN_GIT_CMD)) + return error(FAILED_RUN, reflog.argv[0]); - if (run_command_v_opt(argv_repack, RUN_GIT_CMD)) - return error(FAILED_RUN, argv_repack[0]); + if (run_command_v_opt(repack.argv, RUN_GIT_CMD)) + return error(FAILED_RUN, repack.argv[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]); + argv_array_push(&prune, prune_expire); + if (quiet) + argv_array_push(&prune, "--no-progress"); + if (run_command_v_opt(prune.argv, RUN_GIT_CMD)) + return error(FAILED_RUN, prune.argv[0]); } - if (run_command_v_opt(argv_rerere, RUN_GIT_CMD)) - return error(FAILED_RUN, argv_rerere[0]); + if (run_command_v_opt(rerere.argv, RUN_GIT_CMD)) + return error(FAILED_RUN, rerere.argv[0]); if (auto_gc && too_many_loose_objects()) - warning("There are too many unreachable loose objects; " - "run 'git prune' to remove them."); + 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 index 3d5f6ace97..fe1726f5ef 100644 --- a/builtin/grep.c +++ b/builtin/grep.c @@ -18,11 +18,6 @@ #include "quote.h" #include "dir.h" -#ifndef NO_PTHREADS -#include <pthread.h> -#include "thread-utils.h" -#endif - static char const * const grep_usage[] = { "git grep [options] [-e] <pattern> [<rev>...] [[--] <path>...]", NULL @@ -34,26 +29,12 @@ static int use_threads = 1; #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; +struct work_item { + struct grep_source source; char done; struct strbuf out; }; @@ -79,13 +60,17 @@ 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; +static inline void grep_lock(void) +{ + if (use_threads) + pthread_mutex_lock(&grep_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) +static inline void grep_unlock(void) +{ + if (use_threads) + pthread_mutex_unlock(&grep_mutex); +} /* Signalled when a new work_item is added to todo. */ static pthread_cond_t cond_add; @@ -98,10 +83,10 @@ static pthread_cond_t cond_write; /* Signalled when we are finished with everything. */ static pthread_cond_t cond_result; -static int print_hunk_marks_between_files; -static int printed_something; +static int skip_first_line; -static void add_work(enum work_type type, char *name, void *id) +static void add_work(struct grep_opt *opt, enum grep_source_type type, + const char *name, const void *id) { grep_lock(); @@ -109,9 +94,9 @@ static void add_work(enum work_type type, char *name, void *id) pthread_cond_wait(&cond_write, &grep_mutex); } - todo[todo_end].type = type; - todo[todo_end].name = name; - todo[todo_end].identifier = id; + grep_source_init(&todo[todo_end].source, type, name, id); + if (opt->binary != GREP_BINARY_TEXT) + grep_source_load_driver(&todo[todo_end].source); todo[todo_end].done = 0; strbuf_reset(&todo[todo_end].out); todo_end = (todo_end + 1) % ARRAY_SIZE(todo); @@ -139,21 +124,6 @@ static struct work_item *get_work(void) 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; @@ -165,13 +135,22 @@ static void work_done(struct work_item *w) todo_done = (todo_done+1) % ARRAY_SIZE(todo)) { w = &todo[todo_done]; if (w->out.len) { - if (print_hunk_marks_between_files && printed_something) - write_or_die(1, "--\n", 3); - write_or_die(1, w->out.buf, w->out.len); - printed_something = 1; + const char *p = w->out.buf; + size_t len = w->out.len; + + /* Skip the leading hunk mark of the first file. */ + if (skip_first_line) { + while (len) { + len--; + if (*p++ == '\n') + break; + } + skip_first_line = 0; + } + + write_or_die(1, p, len); } - free(w->name); - free(w->identifier); + grep_source_clear(&w->source); } if (old_done != todo_done) @@ -194,25 +173,8 @@ static void *run(void *arg) 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); - } - + hit |= grep_source(opt, &w->source); + grep_source_clear_data(&w->source); work_done(w); } free_grep_patterns(arg); @@ -232,10 +194,12 @@ static void start_threads(struct grep_opt *opt) int i; pthread_mutex_init(&grep_mutex, NULL); - pthread_mutex_init(&read_sha1_mutex, NULL); + pthread_mutex_init(&grep_read_mutex, NULL); + pthread_mutex_init(&grep_attr_mutex, NULL); pthread_cond_init(&cond_add, NULL); pthread_cond_init(&cond_write, NULL); pthread_cond_init(&cond_result, NULL); + grep_use_locks = 1; for (i = 0; i < ARRAY_SIZE(todo); i++) { strbuf_init(&todo[i].out, 0); @@ -249,7 +213,7 @@ static void start_threads(struct grep_opt *opt) err = pthread_create(&threads[i], NULL, run, o); if (err) - die("grep: failed to create thread: %s", + die(_("grep: failed to create thread: %s"), strerror(err)); } } @@ -279,16 +243,16 @@ static int wait_all(void) } pthread_mutex_destroy(&grep_mutex); - pthread_mutex_destroy(&read_sha1_mutex); + pthread_mutex_destroy(&grep_read_mutex); + pthread_mutex_destroy(&grep_attr_mutex); pthread_cond_destroy(&cond_add); pthread_cond_destroy(&cond_write); pthread_cond_destroy(&cond_result); + grep_use_locks = 0; return hit; } #else /* !NO_PTHREADS */ -#define read_sha1_lock() -#define read_sha1_unlock() static int wait_all(void) { @@ -301,14 +265,24 @@ static int grep_config(const char *var, const char *value, void *cb) struct grep_opt *opt = cb; char *color = NULL; - switch (userdiff_config(var, value)) { - case 0: break; - case -1: return -1; - default: return 0; + if (userdiff_config(var, value) < 0) + return -1; + + if (!strcmp(var, "grep.extendedregexp")) { + if (git_config_bool(var, value)) + opt->regflags |= REG_EXTENDED; + else + opt->regflags &= ~REG_EXTENDED; + return 0; + } + + if (!strcmp(var, "grep.linenumber")) { + opt->linenum = git_config_bool(var, value); + return 0; } if (!strcmp(var, "color.grep")) - opt->color = git_config_colorbool(var, value, -1); + opt->color = git_config_colorbool(var, value); else if (!strcmp(var, "color.grep.context")) color = opt->color_context; else if (!strcmp(var, "color.grep.filename")) @@ -333,129 +307,13 @@ static int grep_config(const char *var, const char *value, void *cb) return 0; } -/* - * 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)); - + grep_read_lock(); + data = read_sha1_file(sha1, type, size); + grep_read_unlock(); return data; } @@ -463,7 +321,6 @@ 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, @@ -473,87 +330,51 @@ static int grep_sha1(struct grep_opt *opt, const unsigned char *sha1, strbuf_addstr(&pathbuf, filename); } - name = strbuf_detach(&pathbuf, NULL); - #ifndef NO_PTHREADS if (use_threads) { - grep_sha1_async(opt, name, sha1); + add_work(opt, GREP_SOURCE_SHA1, pathbuf.buf, sha1); + strbuf_release(&pathbuf); return 0; } else #endif { + struct grep_source gs; 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; + grep_source_init(&gs, GREP_SOURCE_SHA1, pathbuf.buf, sha1); + strbuf_release(&pathbuf); + hit = grep_source(opt, &gs); - 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; + grep_source_clear(&gs); + return hit; } - 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); + add_work(opt, GREP_SOURCE_FILE, buf.buf, filename); + strbuf_release(&buf); return 0; } else #endif { + struct grep_source gs; 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); + grep_source_init(&gs, GREP_SOURCE_FILE, buf.buf, filename); + strbuf_release(&buf); + hit = grep_source(opt, &gs); + + grep_source_clear(&gs); return hit; } } @@ -578,14 +399,14 @@ static void run_pager(struct grep_opt *opt, const char *prefix) argv[path_list->nr] = NULL; if (prefix && chdir(prefix)) - die("Failed to chdir: %s", prefix); + die(_("Failed to chdir: %s"), prefix); status = run_command_v_opt(argv, RUN_USING_SHELL); if (status) exit(status); free(argv); } -static int grep_cache(struct grep_opt *opt, const char **paths, int cached) +static int grep_cache(struct grep_opt *opt, const struct pathspec *pathspec, int cached) { int hit = 0; int nr; @@ -595,7 +416,7 @@ static int grep_cache(struct grep_opt *opt, const char **paths, int cached) struct cache_entry *ce = active_cache[nr]; if (!S_ISREG(ce->ce_mode)) continue; - if (!pathspec_matches(paths, ce->name, opt->max_depth)) + if (!match_pathspec_depth(pathspec, ce->name, ce_namelen(ce), 0, NULL)) continue; /* * If CE_VALID is on, we assume worktree file and its cache entry @@ -622,44 +443,30 @@ static int grep_cache(struct grep_opt *opt, const char **paths, int cached) return hit; } -static int grep_tree(struct grep_opt *opt, const char **paths, - struct tree_desc *tree, - const char *tree_name, const char *base) +static int grep_tree(struct grep_opt *opt, const struct pathspec *pathspec, + struct tree_desc *tree, struct strbuf *base, int tn_len) { - int len; int hit = 0; + enum interesting match = entry_not_interesting; struct name_entry entry; - char *down; - int tn_len = strlen(tree_name); - struct strbuf pathbuf; + int old_baselen = base->len; - strbuf_init(&pathbuf, PATH_MAX + tn_len); + while (tree_entry(tree, &entry)) { + int te_len = tree_entry_len(&entry); - if (tn_len) { - strbuf_add(&pathbuf, tree_name, tn_len); - strbuf_addch(&pathbuf, ':'); - tn_len = pathbuf.len; - } - strbuf_addstr(&pathbuf, base); - len = pathbuf.len; + if (match != all_entries_interesting) { + match = tree_entry_interesting(&entry, base, tn_len, pathspec); + if (match == all_entries_not_interesting) + break; + if (match == entry_not_interesting) + continue; + } - 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); + strbuf_add(base, entry.path, te_len); + + if (S_ISREG(entry.mode)) { + hit |= grep_sha1(opt, entry.sha1, base->buf, tn_len); + } else if (S_ISDIR(entry.mode)) { enum object_type type; struct tree_desc sub; @@ -668,20 +475,23 @@ static int grep_tree(struct grep_opt *opt, const char **paths, data = lock_and_read_sha1_file(entry.sha1, &type, &size); if (!data) - die("unable to read tree (%s)", + die(_("unable to read tree (%s)"), sha1_to_hex(entry.sha1)); + + strbuf_addch(base, '/'); init_tree_desc(&sub, data, size); - hit |= grep_tree(opt, paths, &sub, tree_name, down); + hit |= grep_tree(opt, pathspec, &sub, base, tn_len); free(data); } + strbuf_setlen(base, old_baselen); + if (hit && opt->status_only) break; } - strbuf_release(&pathbuf); return hit; } -static int grep_object(struct grep_opt *opt, const char **paths, +static int grep_object(struct grep_opt *opt, const struct pathspec *pathspec, struct object *obj, const char *name) { if (obj->type == OBJ_BLOB) @@ -690,20 +500,33 @@ static int grep_object(struct grep_opt *opt, const char **paths, struct tree_desc tree; void *data; unsigned long size; - int hit; + struct strbuf base; + int hit, len; + + grep_read_lock(); data = read_object_with_reference(obj->sha1, tree_type, &size, NULL); + grep_read_unlock(); + if (!data) - die("unable to read tree (%s)", sha1_to_hex(obj->sha1)); + die(_("unable to read tree (%s)"), sha1_to_hex(obj->sha1)); + + len = name ? strlen(name) : 0; + strbuf_init(&base, PATH_MAX + len + 1); + if (len) { + strbuf_add(&base, name, len); + strbuf_addch(&base, ':'); + } init_tree_desc(&tree, data, size); - hit = grep_tree(opt, paths, &tree, name, ""); + hit = grep_tree(opt, pathspec, &tree, &base, base.len); + strbuf_release(&base); free(data); return hit; } - die("unable to grep from object of type %s", typename(obj->type)); + die(_("unable to grep from object of type %s"), typename(obj->type)); } -static int grep_objects(struct grep_opt *opt, const char **paths, +static int grep_objects(struct grep_opt *opt, const struct pathspec *pathspec, const struct object_array *list) { unsigned int i; @@ -713,7 +536,7 @@ static int grep_objects(struct grep_opt *opt, const char **paths, for (i = 0; i < 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)) { + if (grep_object(opt, pathspec, real_obj, list->objects[i].name)) { hit = 1; if (opt->status_only) break; @@ -722,16 +545,22 @@ static int grep_objects(struct grep_opt *opt, const char **paths, return hit; } -static int grep_directory(struct grep_opt *opt, const char **paths) +static int grep_directory(struct grep_opt *opt, const struct pathspec *pathspec, + int exc_std) { struct dir_struct dir; int i, hit = 0; memset(&dir, 0, sizeof(dir)); - setup_standard_excludes(&dir); + if (exc_std) + setup_standard_excludes(&dir); - fill_directory(&dir, paths); + fill_directory(&dir, pathspec->raw); for (i = 0; i < dir.nr; i++) { + const char *name = dir.entries[i]->name; + int namelen = strlen(name); + if (!match_pathspec_depth(pathspec, name, namelen, 0, NULL)) + continue; hit |= grep_file(opt, dir.entries[i]->name); if (hit && opt->status_only) break; @@ -752,7 +581,7 @@ static int context_callback(const struct option *opt, const char *arg, } value = strtol(arg, (char **)&endp, 10); if (*endp) { - return error("switch `%c' expects a numerical value", + return error(_("switch `%c' expects a numerical value"), opt->short_name); } grep_opt->pre_context = grep_opt->post_context = value; @@ -762,25 +591,24 @@ static int context_callback(const struct option *opt, const char *arg, static int file_callback(const struct option *opt, const char *arg, int unset) { struct grep_opt *grep_opt = opt->value; + int from_stdin = !strcmp(arg, "-"); FILE *patterns; int lno = 0; struct strbuf sb = STRBUF_INIT; - patterns = fopen(arg, "r"); + patterns = from_stdin ? stdin : fopen(arg, "r"); if (!patterns) - die_errno("cannot open '%s'", arg); + die_errno(_("cannot open '%s'"), arg); while (strbuf_getline(&sb, patterns, '\n') == 0) { - char *s; - size_t len; - /* ignore empty line like grep does */ if (sb.len == 0) continue; - s = strbuf_detach(&sb, &len); - append_grep_pat(grep_opt, s, len, arg, ++lno, GREP_PATTERN); + append_grep_pat(grep_opt, sb.buf, sb.len, arg, ++lno, + GREP_PATTERN); } - fclose(patterns); + if (!from_stdin) + fclose(patterns); strbuf_release(&sb); return 0; } @@ -829,22 +657,36 @@ static int help_callback(const struct option *opt, const char *arg, int unset) int cmd_grep(int argc, const char **argv, const char *prefix) { int hit = 0; - int cached = 0; + int cached = 0, untracked = 0, opt_exclude = -1; int seen_dashdash = 0; int external_grep_allowed__ignored; const char *show_in_pager = NULL, *default_pager = "dummy"; struct grep_opt opt; struct object_array list = OBJECT_ARRAY_INIT; const char **paths = NULL; + struct pathspec pathspec; struct string_list path_list = STRING_LIST_INIT_NODUP; int i; int dummy; int use_index = 1; + enum { + pattern_type_unspecified = 0, + pattern_type_bre, + pattern_type_ere, + pattern_type_fixed, + pattern_type_pcre, + }; + int pattern_type = pattern_type_unspecified; + 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_NEGBIT(0, "no-index", &use_index, + "finds in contents not managed by git", 1), + OPT_BOOLEAN(0, "untracked", &untracked, + "search in both tracked and untracked files"), + OPT_SET_INT(0, "exclude-standard", &opt_exclude, + "search also in ignored files", 1), OPT_GROUP(""), OPT_BOOLEAN('v', "invert-match", &opt.invert, "show non-matching lines"), @@ -861,15 +703,20 @@ int cmd_grep(int argc, const char **argv, const char *prefix) "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_SET_INT('E', "extended-regexp", &pattern_type, + "use extended POSIX regular expressions", + pattern_type_ere), + OPT_SET_INT('G', "basic-regexp", &pattern_type, + "use basic POSIX regular expressions (default)", + pattern_type_bre), + OPT_SET_INT('F', "fixed-strings", &pattern_type, + "interpret patterns as fixed strings", + pattern_type_fixed), + OPT_SET_INT('P', "perl-regexp", &pattern_type, + "use Perl-compatible regular expressions", + pattern_type_pcre), OPT_GROUP(""), - OPT_BOOLEAN('n', NULL, &opt.linenum, "show line numbers"), + OPT_BOOLEAN('n', "line-number", &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, @@ -886,18 +733,24 @@ int cmd_grep(int argc, const char **argv, const char *prefix) OPT_BOOLEAN('c', "count", &opt.count, "show the number of matches instead of matching lines"), OPT__COLOR(&opt.color, "highlight matches"), + OPT_BOOLEAN(0, "break", &opt.file_break, + "print empty line between matches from different files"), + OPT_BOOLEAN(0, "heading", &opt.heading, + "show filename only once above matches from same file"), OPT_GROUP(""), - OPT_CALLBACK('C', NULL, &opt, "n", + OPT_CALLBACK('C', "context", &opt, "n", "show <n> context lines before and after matches", context_callback), - OPT_INTEGER('B', NULL, &opt.pre_context, + OPT_INTEGER('B', "before-context", &opt.pre_context, "show <n> context lines before matches"), - OPT_INTEGER('A', NULL, &opt.post_context, + OPT_INTEGER('A', "after-context", &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_BOOLEAN('W', "function-context", &opt.funcbody, + "show the surrounding function"), OPT_GROUP(""), OPT_CALLBACK('f', NULL, &opt, "file", "read patterns from file", file_callback), @@ -915,8 +768,8 @@ int cmd_grep(int argc, const char **argv, const char *prefix) { 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__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(""), @@ -956,8 +809,6 @@ int cmd_grep(int argc, const char **argv, const char *prefix) strcpy(opt.color_sep, GIT_COLOR_CYAN); 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 @@ -973,6 +824,28 @@ int cmd_grep(int argc, const char **argv, const char *prefix) PARSE_OPT_KEEP_DASHDASH | PARSE_OPT_STOP_AT_NON_OPTION | PARSE_OPT_NO_INTERNAL_HELP); + switch (pattern_type) { + case pattern_type_fixed: + opt.fixed = 1; + opt.pcre = 0; + break; + case pattern_type_bre: + opt.fixed = 0; + opt.pcre = 0; + opt.regflags &= ~REG_EXTENDED; + break; + case pattern_type_ere: + opt.fixed = 0; + opt.pcre = 0; + opt.regflags |= REG_EXTENDED; + break; + case pattern_type_pcre: + opt.fixed = 0; + opt.pcre = 1; + break; + default: + break; /* nothing */ + } if (use_index && !startup_info->have_repository) /* die the same way as if we did it at the beginning */ @@ -1009,24 +882,9 @@ int cmd_grep(int argc, const char **argv, const char *prefix) } if (!opt.pattern_list) - die("no pattern given."); + 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) { - if (opt.pre_context || opt.post_context) - print_hunk_marks_between_files = 1; - start_threads(&opt); - } -#else - use_threads = 0; -#endif compile_grep_patterns(&opt); @@ -1038,7 +896,7 @@ int cmd_grep(int argc, const char **argv, const char *prefix) if (!get_sha1(arg, sha1)) { struct object *object = parse_object(sha1); if (!object) - die("bad object %s", arg); + die(_("bad object %s"), arg); add_object_array(object, arg, &list); continue; } @@ -1049,6 +907,23 @@ int cmd_grep(int argc, const char **argv, const char *prefix) break; } +#ifndef NO_PTHREADS + if (list.nr || cached || online_cpus() == 1) + use_threads = 0; +#else + use_threads = 0; +#endif + +#ifndef NO_PTHREADS + if (use_threads) { + if (!(opt.name_only || opt.unmatch_name_only || opt.count) + && (opt.pre_context || opt.post_context || + opt.file_break || opt.funcbody)) + skip_first_line = 1; + start_threads(&opt); + } +#endif + /* The rest are paths */ if (!seen_dashdash) { int j; @@ -1056,16 +931,13 @@ int cmd_grep(int argc, const char **argv, const char *prefix) 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; - } + paths = get_pathspec(prefix, argv + i); + init_pathspec(&pathspec, paths); + pathspec.max_depth = opt.max_depth; + pathspec.recursive = 1; if (show_in_pager && (cached || list.nr)) - die("--open-files-in-pager only works on the worktree"); + die(_("--open-files-in-pager only works on the worktree")); if (show_in_pager && opt.pattern_list && !opt.pattern_list->next) { const char *pager = path_list.items[0].string; @@ -1087,22 +959,25 @@ int cmd_grep(int argc, const char **argv, const char *prefix) if (!show_in_pager) setup_pager(); + if (!use_index && (untracked || cached)) + die(_("--cached or --untracked cannot be used with --no-index.")); - if (!use_index) { - if (cached) - die("--cached cannot be used with --no-index."); + if (!use_index || untracked) { + int use_exclude = (opt_exclude < 0) ? use_index : !!opt_exclude; if (list.nr) - die("--no-index cannot be used with revs."); - hit = grep_directory(&opt, paths); + die(_("--no-index or --untracked cannot be used with revs.")); + hit = grep_directory(&opt, &pathspec, use_exclude); + } else if (0 <= opt_exclude) { + die(_("--[no-]exclude-standard cannot be used for tracked contents.")); } else if (!list.nr) { if (!cached) setup_work_tree(); - hit = grep_cache(&opt, paths, cached); + hit = grep_cache(&opt, &pathspec, cached); } else { if (cached) - die("both --cached and trees are given."); - hit = grep_objects(&opt, paths, &list); + die(_("both --cached and trees are given.")); + hit = grep_objects(&opt, &pathspec, &list); } if (use_threads) diff --git a/builtin/hash-object.c b/builtin/hash-object.c index 080af1a01b..33911fd5e9 100644 --- a/builtin/hash-object.c +++ b/builtin/hash-object.c @@ -4,7 +4,7 @@ * Copyright (C) Linus Torvalds, 2005 * Copyright (C) Junio C Hamano, 2005 */ -#include "cache.h" +#include "builtin.h" #include "blob.h" #include "quote.h" #include "parse-options.h" @@ -14,8 +14,11 @@ static void hash_fd(int fd, const char *type, int write_object, const char *path { struct stat st; unsigned char sha1[20]; + unsigned flags = (HASH_FORMAT_CHECK | + (write_object ? HASH_WRITE_OBJECT : 0)); + if (fstat(fd, &st) < 0 || - index_fd(sha1, fd, &st, write_object, type_from_string(type), path)) + index_fd(sha1, fd, &st, type_from_string(type), path, flags)) die(write_object ? "Unable to add %s to database" : "Unable to hash %s", path); diff --git a/builtin/help.c b/builtin/help.c index 61ff79839b..8f9cd60548 100644 --- a/builtin/help.c +++ b/builtin/help.c @@ -9,8 +9,13 @@ #include "common-cmds.h" #include "parse-options.h" #include "run-command.h" +#include "column.h" #include "help.h" +#ifndef DEFAULT_HELP_FORMAT +#define DEFAULT_HELP_FORMAT "man" +#endif + static struct man_viewer_list { struct man_viewer_list *next; char name[FLEX_ARRAY]; @@ -30,6 +35,7 @@ enum help_format { }; static int show_all = 0; +static unsigned int colopts; 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"), @@ -54,7 +60,7 @@ static enum help_format parse_help_format(const char *format) return HELP_FORMAT_INFO; if (!strcmp(format, "web") || !strcmp(format, "html")) return HELP_FORMAT_WEB; - die("unrecognized help format '%s'", format); + die(_("unrecognized help format '%s'"), format); } static const char *get_man_viewer_info(const char *name) @@ -82,7 +88,7 @@ static int check_emacsclient_version(void) ec_process.err = -1; ec_process.stdout_to_stderr = 1; if (start_command(&ec_process)) - return error("Failed to start emacsclient."); + return error(_("Failed to start emacsclient.")); strbuf_read(&buffer, ec_process.err, 20); close(ec_process.err); @@ -95,7 +101,7 @@ static int check_emacsclient_version(void) if (prefixcmp(buffer.buf, "emacsclient")) { strbuf_release(&buffer); - return error("Failed to parse emacsclient version."); + return error(_("Failed to parse emacsclient version.")); } strbuf_remove(&buffer, 0, strlen("emacsclient")); @@ -103,7 +109,7 @@ static int check_emacsclient_version(void) if (version < 22) { strbuf_release(&buffer); - return error("emacsclient version '%d' too old (< 22).", + return error(_("emacsclient version '%d' too old (< 22)."), version); } @@ -121,7 +127,7 @@ static void exec_woman_emacs(const char *path, const char *page) path = "emacsclient"; strbuf_addf(&man_page, "(woman \"%s\")", page); execlp(path, "emacsclient", "-e", man_page.buf, (char *)NULL); - warning("failed to exec '%s': %s", path, strerror(errno)); + warning(_("failed to exec '%s': %s"), path, strerror(errno)); } } @@ -149,7 +155,7 @@ static void exec_man_konqueror(const char *path, const char *page) path = "kfmclient"; strbuf_addf(&man_page, "man:%s(1)", page); execlp(path, filename, "newTab", man_page.buf, (char *)NULL); - warning("failed to exec '%s': %s", path, strerror(errno)); + warning(_("failed to exec '%s': %s"), path, strerror(errno)); } } @@ -158,7 +164,7 @@ static void exec_man_man(const char *path, const char *page) if (!path) path = "man"; execlp(path, "man", page, (char *)NULL); - warning("failed to exec '%s': %s", path, strerror(errno)); + warning(_("failed to exec '%s': %s"), path, strerror(errno)); } static void exec_man_cmd(const char *cmd, const char *page) @@ -166,7 +172,7 @@ 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, (char *)NULL); - warning("failed to exec '%s': %s", cmd, strerror(errno)); + warning(_("failed to exec '%s': %s"), cmd, strerror(errno)); } static void add_man_viewer(const char *name) @@ -206,8 +212,8 @@ static int add_man_viewer_path(const char *name, 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.", + warning(_("'%s': path for unsupported man viewer.\n" + "Please consider using 'man.<tool>.cmd' instead."), name); return 0; @@ -218,8 +224,8 @@ static int add_man_viewer_cmd(const char *name, const char *value) { if (supported_man_viewer(name, len)) - warning("'%s': cmd for supported man viewer.\n" - "Please consider using 'man.<tool>.path' instead.", + 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); @@ -251,6 +257,8 @@ static int add_man_viewer_info(const char *var, const char *value) static int git_help_config(const char *var, const char *value, void *cb) { + if (!prefixcmp(var, "column.")) + return git_column_config(var, value, "help", &colopts); if (!strcmp(var, "help.format")) { if (!value) return config_error_nonbool(var); @@ -280,11 +288,11 @@ void list_common_cmds_help(void) longest = strlen(common_cmds[i].name); } - puts("The most commonly used git commands are:"); + 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); + puts(_(common_cmds[i].help)); } } @@ -348,7 +356,7 @@ static void exec_viewer(const char *name, const char *page) else if (info) exec_man_cmd(info, page); else - warning("'%s': unknown man viewer.", name); + warning(_("'%s': unknown man viewer."), name); } static void show_man_page(const char *git_cmd) @@ -365,7 +373,7 @@ static void show_man_page(const char *git_cmd) if (fallback) exec_viewer(fallback, page); exec_viewer("man", page); - die("no man viewer handled the request"); + die(_("no man viewer handled the request")); } static void show_info_page(const char *git_cmd) @@ -373,7 +381,7 @@ 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, (char *)NULL); - die("no info viewer handled the request"); + die(_("no info viewer handled the request")); } static void get_html_page_path(struct strbuf *page_path, const char *page) @@ -384,7 +392,7 @@ static void get_html_page_path(struct strbuf *page_path, const char *page) /* 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); + die(_("'%s': not a documentation directory."), html_path); strbuf_init(page_path, 0); strbuf_addf(page_path, "%s/%s.html", html_path, page); @@ -424,16 +432,17 @@ int cmd_help(int argc, const char **argv, const char *prefix) 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); + git_config(git_help_config, NULL); + printf(_("usage: %s%s"), _(git_usage_string), "\n\n"); + list_commands(colopts, &main_cmds, &other_cmds); + printf("%s\n", _(git_more_info_string)); return 0; } if (!argv[0]) { - printf("usage: %s\n\n", git_usage_string); + printf(_("usage: %s%s"), _(git_usage_string), "\n\n"); list_common_cmds_help(); - printf("\n%s\n", git_more_info_string); + printf("\n%s\n", _(git_more_info_string)); return 0; } @@ -442,10 +451,12 @@ int cmd_help(int argc, const char **argv, const char *prefix) if (parsed_help_format != HELP_FORMAT_NONE) help_format = parsed_help_format; + if (help_format == HELP_FORMAT_NONE) + help_format = parse_help_format(DEFAULT_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); + printf_ln(_("`git %s' is aliased to `%s'"), argv[0], alias); return 0; } diff --git a/builtin/index-pack.c b/builtin/index-pack.c index 8dc5c0b541..dc2cfe6e6f 100644 --- a/builtin/index-pack.c +++ b/builtin/index-pack.c @@ -1,4 +1,4 @@ -#include "cache.h" +#include "builtin.h" #include "delta.h" #include "pack.h" #include "csum-file.h" @@ -9,17 +9,19 @@ #include "progress.h" #include "fsck.h" #include "exec_cmd.h" +#include "thread-utils.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>])"; +"git index-pack [-v] [-o <index-file>] [--keep | --keep=<msg>] [--verify] [--strict] (<pack-file> | --stdin [--fix-thin] [<pack-file>])"; -struct object_entry -{ +struct object_entry { struct pack_idx_entry idx; unsigned long size; unsigned int hdr_size; enum object_type type; enum object_type real_type; + unsigned delta_depth; + int base_object_no; }; union delta_base { @@ -33,6 +35,21 @@ struct base_data { struct object_entry *obj; void *data; unsigned long size; + int ref_first, ref_last; + int ofs_first, ofs_last; +}; + +#if !defined(NO_PTHREADS) && defined(NO_PREAD) +/* NO_PREAD uses compat/pread.c, which is not thread-safe. Disable threading. */ +#define NO_PTHREADS +#endif + +struct thread_local { +#ifndef NO_PTHREADS + pthread_t thread; +#endif + struct base_data *base_cache; + size_t base_cache_used; }; /* @@ -44,19 +61,18 @@ struct base_data { #define FLAG_LINK (1u<<20) #define FLAG_CHECKED (1u<<21) -struct delta_entry -{ +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 struct thread_local nothread_data; static int nr_objects; static int nr_deltas; static int nr_resolved_deltas; +static int nr_threads; static int from_stdin; static int strict; @@ -68,17 +84,89 @@ static struct progress *progress; static unsigned char input_buffer[4096]; static unsigned int input_offset, input_len; static off_t consumed_bytes; +static unsigned deepest_delta; static git_SHA_CTX input_ctx; static uint32_t input_crc32; static int input_fd, output_fd, pack_fd; +#ifndef NO_PTHREADS + +static struct thread_local *thread_data; +static int nr_dispatched; +static int threads_active; + +static pthread_mutex_t read_mutex; +#define read_lock() lock_mutex(&read_mutex) +#define read_unlock() unlock_mutex(&read_mutex) + +static pthread_mutex_t counter_mutex; +#define counter_lock() lock_mutex(&counter_mutex) +#define counter_unlock() unlock_mutex(&counter_mutex) + +static pthread_mutex_t work_mutex; +#define work_lock() lock_mutex(&work_mutex) +#define work_unlock() unlock_mutex(&work_mutex) + +static pthread_key_t key; + +static inline void lock_mutex(pthread_mutex_t *mutex) +{ + if (threads_active) + pthread_mutex_lock(mutex); +} + +static inline void unlock_mutex(pthread_mutex_t *mutex) +{ + if (threads_active) + pthread_mutex_unlock(mutex); +} + +/* + * Mutex and conditional variable can't be statically-initialized on Windows. + */ +static void init_thread(void) +{ + init_recursive_mutex(&read_mutex); + pthread_mutex_init(&counter_mutex, NULL); + pthread_mutex_init(&work_mutex, NULL); + pthread_key_create(&key, NULL); + thread_data = xcalloc(nr_threads, sizeof(*thread_data)); + threads_active = 1; +} + +static void cleanup_thread(void) +{ + if (!threads_active) + return; + threads_active = 0; + pthread_mutex_destroy(&read_mutex); + pthread_mutex_destroy(&counter_mutex); + pthread_mutex_destroy(&work_mutex); + pthread_key_delete(key); + free(thread_data); +} + +#else + +#define read_lock() +#define read_unlock() + +#define counter_lock() +#define counter_unlock() + +#define work_lock() +#define work_unlock() + +#endif + + 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)); + die(_("object type mismatch at %s"), sha1_to_hex(obj->sha1)); obj->flags |= FLAG_LINK; return 0; @@ -98,7 +186,7 @@ static void check_object(struct object *obj) unsigned long size; int type = sha1_object_info(obj->sha1, &size); if (type != obj->type || type <= 0) - die("object of unexpected type"); + die(_("object of unexpected type")); obj->flags |= FLAG_CHECKED; return; } @@ -135,15 +223,18 @@ 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); + die(Q_("cannot fill %d byte", + "cannot fill %d bytes", + min), + 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"); + die(_("early EOF")); + die_errno(_("read error on input")); } input_len += ret; if (from_stdin) @@ -155,14 +246,14 @@ static void *fill(int min) static void use(int bytes) { if (bytes > input_len) - die("used more bytes than were available"); + 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 (signed_add_overflows(consumed_bytes, bytes)) - die("pack too large for current definition of off_t"); + die(_("pack too large for current definition of off_t")); consumed_bytes += bytes; } @@ -171,19 +262,19 @@ 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), + static char tmp_file[PATH_MAX]; + output_fd = odb_mkstemp(tmp_file, sizeof(tmp_file), "pack/tmp_pack_XXXXXX"); - pack_name = xstrdup(tmpfile); + pack_name = xstrdup(tmp_file); } 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); + 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); + die_errno(_("cannot open packfile '%s'"), pack_name); output_fd = -1; pack_fd = input_fd; } @@ -197,7 +288,7 @@ static void parse_pack_header(void) /* Header consistency check */ if (hdr->hdr_signature != htonl(PACK_SIGNATURE)) - die("pack signature mismatch"); + die(_("pack signature mismatch")); if (!pack_version_ok(hdr->hdr_version)) die("pack version %"PRIu32" unsupported", ntohl(hdr->hdr_version)); @@ -209,7 +300,7 @@ static void parse_pack_header(void) 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, ...) +static NORETURN void bad_object(unsigned long offset, const char *format, ...) { va_list params; char buf[1024]; @@ -217,7 +308,35 @@ static void bad_object(unsigned long offset, const char *format, ...) va_start(params, format); vsnprintf(buf, sizeof(buf), format, params); va_end(params); - die("pack has bad object at offset %lu: %s", offset, buf); + die(_("pack has bad object at offset %lu: %s"), offset, buf); +} + +static inline struct thread_local *get_thread_data(void) +{ +#ifndef NO_PTHREADS + if (threads_active) + return pthread_getspecific(key); + assert(!threads_active && + "This should only be reached when all threads are gone"); +#endif + return ¬hread_data; +} + +#ifndef NO_PTHREADS +static void set_thread_data(struct thread_local *data) +{ + if (threads_active) + pthread_setspecific(key, data); +} +#endif + +static struct base_data *alloc_base_data(void) +{ + struct base_data *base = xmalloc(sizeof(struct base_data)); + memset(base, 0, sizeof(*base)); + base->ref_last = -1; + base->ofs_last = -1; + return base; } static void free_base_data(struct base_data *c) @@ -225,15 +344,16 @@ static void free_base_data(struct base_data *c) if (c->data) { free(c->data); c->data = NULL; - base_cache_used -= c->size; + get_thread_data()->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; + struct thread_local *data = get_thread_data(); + for (b = data->base_cache; + data->base_cache_used > delta_base_cache_limit && b; b = b->child) { if (b->data && b != retain) free_base_data(b); @@ -245,12 +365,12 @@ static void link_base_data(struct base_data *base, struct base_data *c) if (base) base->child = c; else - base_cache = c; + get_thread_data()->base_cache = c; c->base = base; c->child = NULL; if (c->data) - base_cache_used += c->size; + get_thread_data()->base_cache_used += c->size; prune_base_data(c); } @@ -260,14 +380,14 @@ static void unlink_base_data(struct base_data *c) if (base) base->child = NULL; else - base_cache = NULL; + get_thread_data()->base_cache = NULL; free_base_data(c); } static void *unpack_entry_data(unsigned long offset, unsigned long size) { int status; - z_stream stream; + git_zstream stream; void *buf = xmalloc(size); memset(&stream, 0, sizeof(stream)); @@ -282,7 +402,7 @@ static void *unpack_entry_data(unsigned long offset, unsigned long size) use(input_len - stream.avail_in); } while (status == Z_OK); if (stream.total_out != size || status != Z_STREAM_END) - bad_object(offset, "inflate returned %d", status); + bad_object(offset, _("inflate returned %d"), status); git_inflate_end(&stream); return buf; } @@ -296,7 +416,7 @@ static void *unpack_raw_entry(struct object_entry *obj, union delta_base *delta_ void *data; obj->idx.offset = consumed_bytes; - input_crc32 = crc32(0, Z_NULL, 0); + input_crc32 = crc32(0, NULL, 0); p = fill(1); c = *p; @@ -327,7 +447,7 @@ static void *unpack_raw_entry(struct object_entry *obj, union delta_base *delta_ 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"); + bad_object(obj->idx.offset, _("offset value overflow for delta base object")); p = fill(1); c = *p; use(1); @@ -335,7 +455,7 @@ static void *unpack_raw_entry(struct object_entry *obj, union delta_base *delta_ } 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"); + bad_object(obj->idx.offset, _("delta base offset is out of bound")); break; case OBJ_COMMIT: case OBJ_TREE: @@ -343,7 +463,7 @@ static void *unpack_raw_entry(struct object_entry *obj, union delta_base *delta_ case OBJ_TAG: break; default: - bad_object(obj->idx.offset, "unknown object type %d", obj->type); + bad_object(obj->idx.offset, _("unknown object type %d"), obj->type); } obj->hdr_size = consumed_bytes - obj->idx.offset; @@ -357,7 +477,7 @@ 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 char *data, *inbuf; - z_stream stream; + git_zstream stream; int status; data = xmalloc(obj->size); @@ -372,9 +492,12 @@ static void *get_data_from_pack(struct object_entry *obj) ssize_t n = (len < 64*1024) ? len : 64*1024; n = pread(pack_fd, inbuf, n, from); if (n < 0) - die_errno("cannot pread pack file"); + die_errno(_("cannot pread pack file")); if (!n) - die("premature end of pack file, %lu bytes missing", len); + die(Q_("premature end of pack file, %lu byte missing", + "premature end of pack file, %lu bytes missing", + len), + len); from += n; len -= n; stream.next_in = inbuf; @@ -384,14 +507,25 @@ static void *get_data_from_pack(struct object_entry *obj) /* This has been inflated OK when first encountered, so... */ if (status != Z_STREAM_END || stream.total_out != obj->size) - die("serious inflate inconsistency"); + die(_("serious inflate inconsistency")); git_inflate_end(&stream); free(inbuf); return data; } -static int find_delta(const union delta_base *base) +static int compare_delta_bases(const union delta_base *base1, + const union delta_base *base2, + enum object_type type1, + enum object_type type2) +{ + int cmp = type1 - type2; + if (cmp) + return cmp; + return memcmp(base1, base2, UNION_BASE_SZ); +} + +static int find_delta(const union delta_base *base, enum object_type type) { int first = 0, last = nr_deltas; @@ -400,7 +534,8 @@ static int find_delta(const union delta_base *base) struct delta_entry *delta = &deltas[next]; int cmp; - cmp = memcmp(base, &delta->base, UNION_BASE_SZ); + cmp = compare_delta_bases(base, &delta->base, + type, objects[delta->obj_no].type); if (!cmp) return next; if (cmp < 0) { @@ -413,9 +548,10 @@ static int find_delta(const union delta_base *base) } static void find_delta_children(const union delta_base *base, - int *first_index, int *last_index) + int *first_index, int *last_index, + enum object_type type) { - int first = find_delta(base); + int first = find_delta(base, type); int last = first; int end = nr_deltas - 1; @@ -436,25 +572,30 @@ static void sha1_object(const void *data, unsigned long size, enum object_type type, unsigned char *sha1) { hash_sha1_file(data, size, typename(type), sha1); + read_lock(); if (has_sha1_file(sha1)) { void *has_data; enum object_type has_type; unsigned long has_size; has_data = read_sha1_file(sha1, &has_type, &has_size); + read_unlock(); if (!has_data) - die("cannot read existing object %s", sha1_to_hex(sha1)); + die(_("cannot read existing object %s"), sha1_to_hex(sha1)); if (size != has_size || type != has_type || memcmp(data, has_data, size) != 0) - die("SHA1 COLLISION FOUND WITH %s !", sha1_to_hex(sha1)); + die(_("SHA1 COLLISION FOUND WITH %s !"), sha1_to_hex(sha1)); free(has_data); - } + } else + read_unlock(); + if (strict) { + read_lock(); if (type == OBJ_BLOB) { struct blob *blob = lookup_blob(sha1); if (blob) blob->object.flags |= FLAG_CHECKED; else - die("invalid blob object %s", sha1_to_hex(sha1)); + die(_("invalid blob object %s"), sha1_to_hex(sha1)); } else { struct object *obj; int eaten; @@ -466,11 +607,11 @@ static void sha1_object(const void *data, unsigned long size, */ obj = parse_object_buffer(sha1, type, size, buf, &eaten); if (!obj) - die("invalid %s", typename(type)); + die(_("invalid %s"), typename(type)); if (fsck_object(obj, 1, fsck_error_function)) - die("Error in object"); + die(_("Error in object")); if (fsck_walk(obj, mark_link, NULL)) - die("Not all child objects of %s are reachable", sha1_to_hex(obj->sha1)); + die(_("Not all child objects of %s are reachable"), sha1_to_hex(obj->sha1)); if (obj->type == OBJ_TREE) { struct tree *item = (struct tree *) obj; @@ -482,31 +623,72 @@ static void sha1_object(const void *data, unsigned long size, } obj->flags |= FLAG_CHECKED; } + read_unlock(); } } +static int is_delta_type(enum object_type type) +{ + return (type == OBJ_REF_DELTA || type == OBJ_OFS_DELTA); +} + +/* + * This function is part of find_unresolved_deltas(). There are two + * walkers going in the opposite ways. + * + * The first one in find_unresolved_deltas() traverses down from + * parent node to children, deflating nodes along the way. However, + * memory for deflated nodes is limited by delta_base_cache_limit, so + * at some point parent node's deflated content may be freed. + * + * The second walker is this function, which goes from current node up + * to top parent if necessary to deflate the node. In normal + * situation, its parent node would be already deflated, so it just + * needs to apply delta. + * + * In the worst case scenario, parent node is no longer deflated because + * we're running out of delta_base_cache_limit; we need to re-deflate + * parents, possibly up to the top base. + * + * All deflated objects here are subject to be freed if we exceed + * delta_base_cache_limit, just like in find_unresolved_deltas(), we + * just need to make sure the last node is not freed. + */ static void *get_base_data(struct base_data *c) { if (!c->data) { struct object_entry *obj = c->obj; + struct base_data **delta = NULL; + int delta_nr = 0, delta_alloc = 0; - if (obj->type == OBJ_REF_DELTA || obj->type == OBJ_OFS_DELTA) { - void *base = get_base_data(c->base); - void *raw = get_data_from_pack(obj); + while (is_delta_type(c->obj->type) && !c->data) { + ALLOC_GROW(delta, delta_nr + 1, delta_alloc); + delta[delta_nr++] = c; + c = c->base; + } + if (!delta_nr) { + c->data = get_data_from_pack(obj); + c->size = obj->size; + get_thread_data()->base_cache_used += c->size; + prune_base_data(c); + } + for (; delta_nr > 0; delta_nr--) { + void *base, *raw; + c = delta[delta_nr - 1]; + obj = c->obj; + base = get_base_data(c->base); + raw = get_data_from_pack(obj); c->data = patch_delta( base, c->base->size, raw, obj->size, &c->size); free(raw); if (!c->data) - bad_object(obj->idx.offset, "failed to apply delta"); - } else { - c->data = get_data_from_pack(obj); - c->size = obj->size; + bad_object(obj->idx.offset, _("failed to apply delta")); + get_thread_data()->base_cache_used += c->size; + prune_base_data(c); } - - base_cache_used += c->size; - prune_base_data(c); + free(delta); } return c->data; } @@ -517,6 +699,10 @@ static void resolve_delta(struct object_entry *delta_obj, void *base_data, *delta_data; delta_obj->real_type = base->obj->real_type; + delta_obj->delta_depth = base->obj->delta_depth + 1; + if (deepest_delta < delta_obj->delta_depth) + deepest_delta = delta_obj->delta_depth; + delta_obj->base_object_no = base->obj - objects; delta_data = get_data_from_pack(delta_obj); base_data = get_base_data(base); result->obj = delta_obj; @@ -524,93 +710,150 @@ static void resolve_delta(struct object_entry *delta_obj, delta_data, delta_obj->size, &result->size); free(delta_data); if (!result->data) - bad_object(delta_obj->idx.offset, "failed to apply delta"); + bad_object(delta_obj->idx.offset, _("failed to apply delta")); sha1_object(result->data, result->size, delta_obj->real_type, delta_obj->idx.sha1); + counter_lock(); nr_resolved_deltas++; + counter_unlock(); } -static void find_unresolved_deltas(struct base_data *base, - struct base_data *prev_base) +static struct base_data *find_unresolved_deltas_1(struct base_data *base, + struct base_data *prev_base) { - int i, ref_first, ref_last, ofs_first, ofs_last; - - /* - * This is a recursive function. Those brackets should help reducing - * stack usage by limiting the scope of the delta_base union. - */ - { + if (base->ref_last == -1 && base->ofs_last == -1) { union delta_base base_spec; hashcpy(base_spec.sha1, base->obj->idx.sha1); - find_delta_children(&base_spec, &ref_first, &ref_last); + find_delta_children(&base_spec, + &base->ref_first, &base->ref_last, OBJ_REF_DELTA); memset(&base_spec, 0, sizeof(base_spec)); base_spec.offset = base->obj->idx.offset; - find_delta_children(&base_spec, &ofs_first, &ofs_last); - } + find_delta_children(&base_spec, + &base->ofs_first, &base->ofs_last, OBJ_OFS_DELTA); - if (ref_last == -1 && ofs_last == -1) { - free(base->data); - return; + if (base->ref_last == -1 && base->ofs_last == -1) { + free(base->data); + return NULL; + } + + link_base_data(prev_base, base); } - link_base_data(prev_base, base); + if (base->ref_first <= base->ref_last) { + struct object_entry *child = objects + deltas[base->ref_first].obj_no; + struct base_data *result = alloc_base_data(); - for (i = ref_first; i <= ref_last; i++) { - struct object_entry *child = objects + deltas[i].obj_no; - if (child->real_type == OBJ_REF_DELTA) { - struct base_data result; - resolve_delta(child, base, &result); - if (i == ref_last && ofs_last == -1) - free_base_data(base); - find_unresolved_deltas(&result, base); - } + assert(child->real_type == OBJ_REF_DELTA); + resolve_delta(child, base, result); + if (base->ref_first == base->ref_last && base->ofs_last == -1) + free_base_data(base); + + base->ref_first++; + return result; } - for (i = ofs_first; i <= ofs_last; i++) { - struct object_entry *child = objects + deltas[i].obj_no; - if (child->real_type == OBJ_OFS_DELTA) { - struct base_data result; - resolve_delta(child, base, &result); - if (i == ofs_last) - free_base_data(base); - find_unresolved_deltas(&result, base); - } + if (base->ofs_first <= base->ofs_last) { + struct object_entry *child = objects + deltas[base->ofs_first].obj_no; + struct base_data *result = alloc_base_data(); + + assert(child->real_type == OBJ_OFS_DELTA); + resolve_delta(child, base, result); + if (base->ofs_first == base->ofs_last) + free_base_data(base); + + base->ofs_first++; + return result; } unlink_base_data(base); + return NULL; +} + +static void find_unresolved_deltas(struct base_data *base) +{ + struct base_data *new_base, *prev_base = NULL; + for (;;) { + new_base = find_unresolved_deltas_1(base, prev_base); + + if (new_base) { + prev_base = base; + base = new_base; + } else { + free(base); + base = prev_base; + if (!base) + return; + prev_base = base->base; + } + } } static int compare_delta_entry(const void *a, const void *b) { const struct delta_entry *delta_a = a; const struct delta_entry *delta_b = b; - return memcmp(&delta_a->base, &delta_b->base, UNION_BASE_SZ); + + /* group by type (ref vs ofs) and then by value (sha-1 or offset) */ + return compare_delta_bases(&delta_a->base, &delta_b->base, + objects[delta_a->obj_no].type, + objects[delta_b->obj_no].type); +} + +static void resolve_base(struct object_entry *obj) +{ + struct base_data *base_obj = alloc_base_data(); + base_obj->obj = obj; + base_obj->data = NULL; + find_unresolved_deltas(base_obj); } -/* Parse all objects and return the pack content SHA1 hash */ +#ifndef NO_PTHREADS +static void *threaded_second_pass(void *data) +{ + set_thread_data(data); + for (;;) { + int i; + work_lock(); + display_progress(progress, nr_resolved_deltas); + while (nr_dispatched < nr_objects && + is_delta_type(objects[nr_dispatched].type)) + nr_dispatched++; + if (nr_dispatched >= nr_objects) { + work_unlock(); + break; + } + i = nr_dispatched++; + work_unlock(); + + resolve_base(&objects[i]); + } + return NULL; +} +#endif + +/* + * First pass: + * - find locations of all objects; + * - calculate SHA1 of all non-delta objects; + * - remember base (SHA1 or offset) for all deltas. + */ static void parse_pack_objects(unsigned char *sha1) { int i; struct delta_entry *delta = deltas; struct stat st; - /* - * First pass: - * - find locations of all objects; - * - calculate SHA1 of all non-delta objects; - * - remember base (SHA1 or offset) for all deltas. - */ if (verbose) progress = start_progress( - from_stdin ? "Receiving objects" : "Indexing objects", + from_stdin ? _("Receiving objects") : _("Indexing objects"), nr_objects); for (i = 0; i < nr_objects; i++) { struct object_entry *obj = &objects[i]; void *data = unpack_raw_entry(obj, &delta->base); obj->real_type = obj->type; - if (obj->type == OBJ_REF_DELTA || obj->type == OBJ_OFS_DELTA) { + if (is_delta_type(obj->type)) { nr_deltas++; delta->obj_no = i; delta++; @@ -626,15 +869,28 @@ static void parse_pack_objects(unsigned char *sha1) flush(); git_SHA1_Final(sha1, &input_ctx); if (hashcmp(fill(20), sha1)) - die("pack is corrupted (SHA1 mismatch)"); + die(_("pack is corrupted (SHA1 mismatch)")); use(20); /* If input_fd is a file, we should have reached its end now. */ if (fstat(input_fd, &st)) - die_errno("cannot fstat packfile"); + die_errno(_("cannot fstat packfile")); if (S_ISREG(st.st_mode) && lseek(input_fd, 0, SEEK_CUR) - input_len != st.st_size) - die("pack has junk at the end"); + die(_("pack has junk at the end")); +} + +/* + * Second pass: + * - for all non-delta objects, look if it is used as a base for + * deltas; + * - if used as a base, uncompress the object and apply all deltas, + * recursively checking if the resulting object is used as a base + * for some more deltas. + */ +static void resolve_deltas(void) +{ + int i; if (!nr_deltas) return; @@ -643,51 +899,105 @@ static void parse_pack_objects(unsigned char *sha1) qsort(deltas, nr_deltas, sizeof(struct delta_entry), compare_delta_entry); - /* - * Second pass: - * - for all non-delta objects, look if it is used as a base for - * deltas; - * - if used as a base, uncompress the object and apply all deltas, - * recursively checking if the resulting object is used as a base - * for some more deltas. - */ if (verbose) - progress = start_progress("Resolving deltas", nr_deltas); + progress = start_progress(_("Resolving deltas"), nr_deltas); + +#ifndef NO_PTHREADS + nr_dispatched = 0; + if (nr_threads > 1 || getenv("GIT_FORCE_THREADS")) { + init_thread(); + for (i = 0; i < nr_threads; i++) { + int ret = pthread_create(&thread_data[i].thread, NULL, + threaded_second_pass, thread_data + i); + if (ret) + die("unable to create thread: %s", strerror(ret)); + } + for (i = 0; i < nr_threads; i++) + pthread_join(thread_data[i].thread, NULL); + cleanup_thread(); + return; + } +#endif + for (i = 0; i < nr_objects; i++) { struct object_entry *obj = &objects[i]; - struct base_data base_obj; - if (obj->type == OBJ_REF_DELTA || obj->type == OBJ_OFS_DELTA) + if (is_delta_type(obj->type)) continue; - base_obj.obj = obj; - base_obj.data = NULL; - find_unresolved_deltas(&base_obj, NULL); + resolve_base(obj); display_progress(progress, nr_resolved_deltas); } } +/* + * Third pass: + * - append objects to convert thin pack to full pack if required + * - write the final 20-byte SHA-1 + */ +static void fix_unresolved_deltas(struct sha1file *f, int nr_unresolved); +static void conclude_pack(int fix_thin_pack, const char *curr_pack, unsigned char *pack_sha1) +{ + if (nr_deltas == nr_resolved_deltas) { + stop_progress(&progress); + /* Flush remaining pack final 20-byte SHA1. */ + flush(); + return; + } + + if (fix_thin_pack) { + struct sha1file *f; + unsigned char read_sha1[20], tail_sha1[20]; + char msg[48]; + int nr_unresolved = nr_deltas - nr_resolved_deltas; + int nr_objects_initial = nr_objects; + if (nr_unresolved <= 0) + die(_("confusion beyond insanity")); + objects = xrealloc(objects, + (nr_objects + nr_unresolved + 1) + * sizeof(*objects)); + f = sha1fd(output_fd, curr_pack); + fix_unresolved_deltas(f, nr_unresolved); + sprintf(msg, "completed with %d local objects", + nr_objects - nr_objects_initial); + stop_progress_msg(&progress, msg); + sha1close(f, tail_sha1, 0); + hashcpy(read_sha1, pack_sha1); + fixup_pack_header_footer(output_fd, pack_sha1, + curr_pack, nr_objects, + read_sha1, consumed_bytes-20); + if (hashcmp(read_sha1, tail_sha1) != 0) + die("Unexpected tail checksum for %s " + "(disk corruption?)", curr_pack); + } + if (nr_deltas != nr_resolved_deltas) + die(Q_("pack has %d unresolved delta", + "pack has %d unresolved deltas", + nr_deltas - nr_resolved_deltas), + nr_deltas - nr_resolved_deltas); +} + static int write_compressed(struct sha1file *f, void *in, unsigned int size) { - z_stream stream; + git_zstream stream; int status; unsigned char outbuf[4096]; memset(&stream, 0, sizeof(stream)); - deflateInit(&stream, zlib_compression_level); + git_deflate_init(&stream, zlib_compression_level); stream.next_in = in; stream.avail_in = size; do { stream.next_out = outbuf; stream.avail_out = sizeof(outbuf); - status = deflate(&stream, Z_FINISH); + status = git_deflate(&stream, Z_FINISH); sha1write(f, outbuf, sizeof(outbuf) - stream.avail_out); } while (status == Z_OK); if (status != Z_STREAM_END) - die("unable to deflate appended object (%d)", status); + die(_("unable to deflate appended object (%d)"), status); size = stream.total_out; - deflateEnd(&stream); + git_deflate_end(&stream); return size; } @@ -754,20 +1064,20 @@ static void fix_unresolved_deltas(struct sha1file *f, int nr_unresolved) for (i = 0; i < n; i++) { struct delta_entry *d = sorted_by_pos[i]; enum object_type type; - struct base_data base_obj; + struct base_data *base_obj = alloc_base_data(); if (objects[d->obj_no].real_type != OBJ_REF_DELTA) continue; - base_obj.data = read_sha1_file(d->base.sha1, &type, &base_obj.size); - if (!base_obj.data) + base_obj->data = read_sha1_file(d->base.sha1, &type, &base_obj->size); + if (!base_obj->data) continue; - if (check_sha1_signature(d->base.sha1, base_obj.data, - base_obj.size, typename(type))) - die("local object %s is corrupt", sha1_to_hex(d->base.sha1)); - base_obj.obj = append_obj_to_pack(f, d->base.sha1, - base_obj.data, base_obj.size, type); - find_unresolved_deltas(&base_obj, NULL); + if (check_sha1_signature(d->base.sha1, base_obj->data, + base_obj->size, typename(type))) + die(_("local object %s is corrupt"), sha1_to_hex(d->base.sha1)); + base_obj->obj = append_obj_to_pack(f, d->base.sha1, + base_obj->data, base_obj->size, type); + find_unresolved_deltas(base_obj); display_progress(progress, nr_resolved_deltas); } free(sorted_by_pos); @@ -788,7 +1098,7 @@ static void final(const char *final_pack_name, const char *curr_pack_name, fsync_or_die(output_fd, curr_pack_name); err = close(output_fd); if (err) - die_errno("error while closing pack file"); + die_errno(_("error while closing pack file")); } if (keep_msg) { @@ -801,7 +1111,7 @@ static void final(const char *final_pack_name, const char *curr_pack_name, if (keep_fd < 0) { if (errno != EEXIST) - die_errno("cannot write keep file '%s'", + die_errno(_("cannot write keep file '%s'"), keep_name); } else { if (keep_msg_len > 0) { @@ -809,7 +1119,7 @@ static void final(const char *final_pack_name, const char *curr_pack_name, write_or_die(keep_fd, "\n", 1); } if (close(keep_fd) != 0) - die_errno("cannot close written keep file '%s'", + die_errno(_("cannot close written keep file '%s'"), keep_name); report = "keep"; } @@ -822,7 +1132,7 @@ static void final(const char *final_pack_name, const char *curr_pack_name, final_pack_name = name; } if (move_temp_to_file(curr_pack_name, final_pack_name)) - die("cannot store pack file"); + die(_("cannot store pack file")); } else if (from_stdin) chmod(final_pack_name, 0444); @@ -833,7 +1143,7 @@ static void final(const char *final_pack_name, const char *curr_pack_name, final_index_name = name; } if (move_temp_to_file(curr_index_name, final_index_name)) - die("cannot store index file"); + die(_("cannot store index file")); } else chmod(final_index_name, 0444); @@ -861,24 +1171,152 @@ static void final(const char *final_pack_name, const char *curr_pack_name, static int git_index_pack_config(const char *k, const char *v, void *cb) { + struct pack_idx_option *opts = cb; + if (!strcmp(k, "pack.indexversion")) { - pack_idx_default_version = git_config_int(k, v); - if (pack_idx_default_version > 2) - die("bad pack.indexversion=%"PRIu32, - pack_idx_default_version); + opts->version = git_config_int(k, v); + if (opts->version > 2) + die("bad pack.indexversion=%"PRIu32, opts->version); + return 0; + } + if (!strcmp(k, "pack.threads")) { + nr_threads = git_config_int(k, v); + if (nr_threads < 0) + die("invalid number of threads specified (%d)", + nr_threads); +#ifdef NO_PTHREADS + if (nr_threads != 1) + warning("no threads support, ignoring %s", k); + nr_threads = 1; +#endif return 0; } return git_default_config(k, v, cb); } +static int cmp_uint32(const void *a_, const void *b_) +{ + uint32_t a = *((uint32_t *)a_); + uint32_t b = *((uint32_t *)b_); + + return (a < b) ? -1 : (a != b); +} + +static void read_v2_anomalous_offsets(struct packed_git *p, + struct pack_idx_option *opts) +{ + const uint32_t *idx1, *idx2; + uint32_t i; + + /* The address of the 4-byte offset table */ + idx1 = (((const uint32_t *)p->index_data) + + 2 /* 8-byte header */ + + 256 /* fan out */ + + 5 * p->num_objects /* 20-byte SHA-1 table */ + + p->num_objects /* CRC32 table */ + ); + + /* The address of the 8-byte offset table */ + idx2 = idx1 + p->num_objects; + + for (i = 0; i < p->num_objects; i++) { + uint32_t off = ntohl(idx1[i]); + if (!(off & 0x80000000)) + continue; + off = off & 0x7fffffff; + if (idx2[off * 2]) + continue; + /* + * The real offset is ntohl(idx2[off * 2]) in high 4 + * octets, and ntohl(idx2[off * 2 + 1]) in low 4 + * octets. But idx2[off * 2] is Zero!!! + */ + ALLOC_GROW(opts->anomaly, opts->anomaly_nr + 1, opts->anomaly_alloc); + opts->anomaly[opts->anomaly_nr++] = ntohl(idx2[off * 2 + 1]); + } + + if (1 < opts->anomaly_nr) + qsort(opts->anomaly, opts->anomaly_nr, sizeof(uint32_t), cmp_uint32); +} + +static void read_idx_option(struct pack_idx_option *opts, const char *pack_name) +{ + struct packed_git *p = add_packed_git(pack_name, strlen(pack_name), 1); + + if (!p) + die(_("Cannot open existing pack file '%s'"), pack_name); + if (open_pack_index(p)) + die(_("Cannot open existing pack idx file for '%s'"), pack_name); + + /* Read the attributes from the existing idx file */ + opts->version = p->index_version; + + if (opts->version == 2) + read_v2_anomalous_offsets(p, opts); + + /* + * Get rid of the idx file as we do not need it anymore. + * NEEDSWORK: extract this bit from free_pack_by_name() in + * sha1_file.c, perhaps? It shouldn't matter very much as we + * know we haven't installed this pack (hence we never have + * read anything from it). + */ + close_pack_index(p); + free(p); +} + +static void show_pack_info(int stat_only) +{ + int i, baseobjects = nr_objects - nr_deltas; + unsigned long *chain_histogram = NULL; + + if (deepest_delta) + chain_histogram = xcalloc(deepest_delta, sizeof(unsigned long)); + + for (i = 0; i < nr_objects; i++) { + struct object_entry *obj = &objects[i]; + + if (is_delta_type(obj->type)) + chain_histogram[obj->delta_depth - 1]++; + if (stat_only) + continue; + printf("%s %-6s %lu %lu %"PRIuMAX, + sha1_to_hex(obj->idx.sha1), + typename(obj->real_type), obj->size, + (unsigned long)(obj[1].idx.offset - obj->idx.offset), + (uintmax_t)obj->idx.offset); + if (is_delta_type(obj->type)) { + struct object_entry *bobj = &objects[obj->base_object_no]; + printf(" %u %s", obj->delta_depth, sha1_to_hex(bobj->idx.sha1)); + } + putchar('\n'); + } + + if (baseobjects) + printf_ln(Q_("non delta: %d object", + "non delta: %d objects", + baseobjects), + baseobjects); + for (i = 0; i < deepest_delta; i++) { + if (!chain_histogram[i]) + continue; + printf_ln(Q_("chain length = %d: %lu object", + "chain length = %d: %lu objects", + chain_histogram[i]), + i + 1, + chain_histogram[i]); + } +} + int cmd_index_pack(int argc, const char **argv, const char *prefix) { - int i, fix_thin_pack = 0; + int i, fix_thin_pack = 0, verify = 0, stat_only = 0, stat = 0; const char *curr_pack, *curr_index; const char *index_name = NULL, *pack_name = NULL; const char *keep_name = NULL, *keep_msg = NULL; char *index_name_buf = NULL, *keep_name_buf = NULL; struct pack_idx_entry **idx_objects; + struct pack_idx_option opts; unsigned char pack_sha1[20]; if (argc == 2 && !strcmp(argv[1], "-h")) @@ -886,9 +1324,10 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix) read_replace_refs = 0; - git_config(git_index_pack_config, NULL); + reset_pack_idx_option(&opts); + git_config(git_index_pack_config, &opts); if (prefix && chdir(prefix)) - die("Cannot come back to cwd"); + die(_("Cannot come back to cwd")); for (i = 1; i < argc; i++) { const char *arg = argv[i]; @@ -900,10 +1339,30 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix) fix_thin_pack = 1; } else if (!strcmp(arg, "--strict")) { strict = 1; + } else if (!strcmp(arg, "--verify")) { + verify = 1; + } else if (!strcmp(arg, "--verify-stat")) { + verify = 1; + stat = 1; + } else if (!strcmp(arg, "--verify-stat-only")) { + verify = 1; + stat = 1; + stat_only = 1; } else if (!strcmp(arg, "--keep")) { keep_msg = ""; } else if (!prefixcmp(arg, "--keep=")) { keep_msg = arg + 7; + } else if (!prefixcmp(arg, "--threads=")) { + char *end; + nr_threads = strtoul(arg+10, &end, 0); + if (!arg[10] || *end || nr_threads < 0) + usage(index_pack_usage); +#ifdef NO_PTHREADS + if (nr_threads != 1) + warning("no threads support, " + "ignoring %s", arg); + nr_threads = 1; +#endif } else if (!prefixcmp(arg, "--pack_header=")) { struct pack_header *hdr; char *c; @@ -912,10 +1371,10 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix) hdr->hdr_signature = htonl(PACK_SIGNATURE); hdr->hdr_version = htonl(strtoul(arg + 14, &c, 10)); if (*c != ',') - die("bad %s", arg); + die(_("bad %s"), arg); hdr->hdr_entries = htonl(strtoul(c + 1, &c, 10)); if (*c) - die("bad %s", arg); + die(_("bad %s"), arg); input_len = sizeof(*hdr); } else if (!strcmp(arg, "-v")) { verbose = 1; @@ -925,13 +1384,13 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix) index_name = argv[++i]; } else if (!prefixcmp(arg, "--index-version=")) { char *c; - pack_idx_default_version = strtoul(arg + 16, &c, 10); - if (pack_idx_default_version > 2) - die("bad %s", arg); + opts.version = strtoul(arg + 16, &c, 10); + if (opts.version > 2) + die(_("bad %s"), arg); if (*c == ',') - pack_idx_off32_limit = strtoul(c+1, &c, 0); - if (*c || pack_idx_off32_limit & 0x80000000) - die("bad %s", arg); + opts.off32_limit = strtoul(c+1, &c, 0); + if (*c || opts.off32_limit & 0x80000000) + die(_("bad %s"), arg); } else usage(index_pack_usage); continue; @@ -945,11 +1404,11 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix) if (!pack_name && !from_stdin) usage(index_pack_usage); if (fix_thin_pack && !from_stdin) - die("--fix-thin cannot be used without --stdin"); + die(_("--fix-thin cannot be used without --stdin")); if (!index_name && pack_name) { int len = strlen(pack_name); if (!has_extension(pack_name, ".pack")) - die("packfile name '%s' does not end with '.pack'", + die(_("packfile name '%s' does not end with '.pack'"), pack_name); index_name_buf = xmalloc(len); memcpy(index_name_buf, pack_name, len - 5); @@ -959,67 +1418,58 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix) if (keep_msg && !keep_name && pack_name) { int len = strlen(pack_name); if (!has_extension(pack_name, ".pack")) - die("packfile name '%s' does not end with '.pack'", + die(_("packfile name '%s' does not end with '.pack'"), pack_name); keep_name_buf = xmalloc(len); memcpy(keep_name_buf, pack_name, len - 5); strcpy(keep_name_buf + len - 5, ".keep"); keep_name = keep_name_buf; } + if (verify) { + if (!index_name) + die(_("--verify with no packfile name given")); + read_idx_option(&opts, index_name); + opts.flags |= WRITE_IDX_VERIFY | WRITE_IDX_STRICT; + } + if (strict) + opts.flags |= WRITE_IDX_STRICT; + +#ifndef NO_PTHREADS + if (!nr_threads) { + nr_threads = online_cpus(); + /* An experiment showed that more threads does not mean faster */ + if (nr_threads > 3) + nr_threads = 3; + } +#endif curr_pack = open_pack_file(pack_name); parse_pack_header(); - objects = xmalloc((nr_objects + 1) * sizeof(struct object_entry)); - deltas = xmalloc(nr_objects * sizeof(struct delta_entry)); + objects = xcalloc(nr_objects + 1, sizeof(struct object_entry)); + deltas = xcalloc(nr_objects, sizeof(struct delta_entry)); parse_pack_objects(pack_sha1); - if (nr_deltas == nr_resolved_deltas) { - stop_progress(&progress); - /* Flush remaining pack final 20-byte SHA1. */ - flush(); - } else { - if (fix_thin_pack) { - struct sha1file *f; - unsigned char read_sha1[20], tail_sha1[20]; - char msg[48]; - int nr_unresolved = nr_deltas - nr_resolved_deltas; - int nr_objects_initial = nr_objects; - if (nr_unresolved <= 0) - die("confusion beyond insanity"); - objects = xrealloc(objects, - (nr_objects + nr_unresolved + 1) - * sizeof(*objects)); - f = sha1fd(output_fd, curr_pack); - fix_unresolved_deltas(f, nr_unresolved); - sprintf(msg, "completed with %d local objects", - nr_objects - nr_objects_initial); - stop_progress_msg(&progress, msg); - sha1close(f, tail_sha1, 0); - hashcpy(read_sha1, pack_sha1); - fixup_pack_header_footer(output_fd, pack_sha1, - curr_pack, nr_objects, - read_sha1, consumed_bytes-20); - if (hashcmp(read_sha1, tail_sha1) != 0) - die("Unexpected tail checksum for %s " - "(disk corruption?)", curr_pack); - } - if (nr_deltas != nr_resolved_deltas) - die("pack has %d unresolved deltas", - nr_deltas - nr_resolved_deltas); - } + resolve_deltas(); + conclude_pack(fix_thin_pack, curr_pack, pack_sha1); free(deltas); if (strict) check_objects(); + if (stat) + show_pack_info(stat_only); + idx_objects = xmalloc((nr_objects) * sizeof(struct pack_idx_entry *)); for (i = 0; i < nr_objects; i++) idx_objects[i] = &objects[i].idx; - curr_index = write_idx_file(index_name, idx_objects, nr_objects, pack_sha1); + curr_index = write_idx_file(index_name, idx_objects, nr_objects, &opts, pack_sha1); free(idx_objects); - final(pack_name, curr_pack, - index_name, curr_index, - keep_name, keep_msg, - pack_sha1); + if (!verify) + final(pack_name, curr_pack, + index_name, curr_index, + keep_name, keep_msg, + pack_sha1); + else + close(input_fd); free(objects); free(index_name_buf); free(keep_name_buf); diff --git a/builtin/init-db.c b/builtin/init-db.c index 9d4886c716..0dacb8b79c 100644 --- a/builtin/init-db.c +++ b/builtin/init-db.c @@ -21,6 +21,7 @@ static int init_is_bare_repository = 0; static int init_shared_repository = -1; static const char *init_db_template_dir; +static const char *git_link; static void safe_create_dir(const char *dir, int share) { @@ -31,7 +32,7 @@ static void safe_create_dir(const char *dir, int share) } } else if (share && adjust_shared_perm(dir)) - die("Could not make %s writable by group", dir); + die(_("Could not make %s writable by group"), dir); } static void copy_templates_1(char *path, int baselen, @@ -58,25 +59,25 @@ static void copy_templates_1(char *path, int baselen, namelen = strlen(de->d_name); if ((PATH_MAX <= baselen + namelen) || (PATH_MAX <= template_baselen + namelen)) - die("insanely long template name %s", de->d_name); + die(_("insanely long template name %s"), de->d_name); memcpy(path + baselen, de->d_name, namelen+1); memcpy(template + template_baselen, de->d_name, namelen+1); if (lstat(path, &st_git)) { if (errno != ENOENT) - die_errno("cannot stat '%s'", path); + die_errno(_("cannot stat '%s'"), path); } else exists = 1; if (lstat(template, &st_template)) - die_errno("cannot stat template '%s'", template); + die_errno(_("cannot stat template '%s'"), template); if (S_ISDIR(st_template.st_mode)) { DIR *subdir = opendir(template); int baselen_sub = baselen + namelen; int template_baselen_sub = template_baselen + namelen; if (!subdir) - die_errno("cannot opendir '%s'", template); + die_errno(_("cannot opendir '%s'"), template); path[baselen_sub++] = template[template_baselen_sub++] = '/'; path[baselen_sub] = @@ -93,20 +94,20 @@ static void copy_templates_1(char *path, int baselen, int len; len = readlink(template, lnk, sizeof(lnk)); if (len < 0) - die_errno("cannot readlink '%s'", template); + die_errno(_("cannot readlink '%s'"), template); if (sizeof(lnk) <= len) - die("insanely long symlink %s", template); + die(_("insanely long symlink %s"), template); lnk[len] = 0; if (symlink(lnk, path)) - die_errno("cannot symlink '%s' '%s'", lnk, path); + die_errno(_("cannot symlink '%s' '%s'"), lnk, path); } else if (S_ISREG(st_template.st_mode)) { if (copy_file(path, template, st_template.st_mode)) - die_errno("cannot copy '%s' to '%s'", template, + die_errno(_("cannot copy '%s' to '%s'"), template, path); } else - error("ignoring template %s", template); + error(_("ignoring template %s"), template); } } @@ -129,7 +130,7 @@ static void copy_templates(const char *template_dir) return; template_len = strlen(template_dir); if (PATH_MAX <= (template_len+strlen("/config"))) - die("insanely long template path %s", template_dir); + die(_("insanely long template path %s"), template_dir); strcpy(template_path, template_dir); if (template_path[template_len-1] != '/') { template_path[template_len++] = '/'; @@ -137,7 +138,7 @@ static void copy_templates(const char *template_dir) } dir = opendir(template_path); if (!dir) { - warning("templates not found %s", template_dir); + warning(_("templates not found %s"), template_dir); return; } @@ -150,8 +151,8 @@ static void copy_templates(const char *template_dir) if (repository_format_version && repository_format_version != GIT_REPO_VERSION) { - warning("not copying templates of " - "a wrong format version %d from '%s'", + warning(_("not copying templates of " + "a wrong format version %d from '%s'"), repository_format_version, template_dir); closedir(dir); @@ -188,7 +189,7 @@ static int create_default_files(const char *template_path) int filemode; if (len > sizeof(path)-50) - die("insane git directory %s", git_dir); + die(_("insane git directory %s"), git_dir); memcpy(path, git_dir, len); if (len && path[len-1] != '/') @@ -311,11 +312,67 @@ static void create_object_directory(void) free(path); } +int set_git_dir_init(const char *git_dir, const char *real_git_dir, + int exist_ok) +{ + if (real_git_dir) { + struct stat st; + + if (!exist_ok && !stat(git_dir, &st)) + die(_("%s already exists"), git_dir); + + if (!exist_ok && !stat(real_git_dir, &st)) + die(_("%s already exists"), real_git_dir); + + /* + * make sure symlinks are resolved because we'll be + * moving the target repo later on in separate_git_dir() + */ + git_link = xstrdup(real_path(git_dir)); + } + else { + real_git_dir = real_path(git_dir); + git_link = NULL; + } + set_git_dir(real_path(real_git_dir)); + return 0; +} + +static void separate_git_dir(const char *git_dir) +{ + struct stat st; + FILE *fp; + + if (!stat(git_link, &st)) { + const char *src; + + if (S_ISREG(st.st_mode)) + src = read_gitfile(git_link); + else if (S_ISDIR(st.st_mode)) + src = git_link; + else + die(_("unable to handle file type %d"), (int)st.st_mode); + + if (rename(src, git_dir)) + die_errno(_("unable to move %s to %s"), src, git_dir); + } + + fp = fopen(git_link, "w"); + if (!fp) + die(_("Could not create git link %s"), git_link); + fprintf(fp, "gitdir: %s\n", git_dir); + fclose(fp); +} + int init_db(const char *template_dir, unsigned int flags) { int reinit; + const char *git_dir = get_git_dir(); + + if (git_link) + separate_git_dir(git_dir); - safe_create_dir(get_git_dir(), 0); + safe_create_dir(git_dir, 0); init_is_bare_repository = is_bare_repository(); @@ -352,11 +409,16 @@ int init_db(const char *template_dir, unsigned int flags) } if (!(flags & INIT_DB_QUIET)) { - const char *git_dir = get_git_dir(); int len = strlen(git_dir); - printf("%s%s Git repository in %s%s\n", - reinit ? "Reinitialized existing" : "Initialized empty", - shared_repository ? " shared" : "", + + /* + * TRANSLATORS: The first '%s' is either "Reinitialized + * existing" or "Initialized empty", the second " shared" or + * "", and the last '%s%s' is the verbatim directory name. + */ + printf(_("%s%s Git repository in %s%s\n"), + reinit ? _("Reinitialized existing") : _("Initialized empty"), + shared_repository ? _(" shared") : "", git_dir, len && git_dir[len-1] != '/' ? "/" : ""); } @@ -375,7 +437,7 @@ static int guess_repository_type(const char *git_dir) if (!strcmp(".", git_dir)) return 1; if (!getcwd(cwd, sizeof(cwd))) - die_errno("cannot tell cwd"); + die_errno(_("cannot tell cwd")); if (!strcmp(git_dir, cwd)) return 1; /* @@ -414,11 +476,13 @@ static const char *const init_db_usage[] = { int cmd_init_db(int argc, const char **argv, const char *prefix) { const char *git_dir; + const char *real_git_dir = NULL; + const char *work_tree; const char *template_dir = NULL; unsigned int flags = 0; const struct option init_db_options[] = { OPT_STRING(0, "template", &template_dir, "template-directory", - "provide the directory from which templates will be used"), + "directory from which templates will be used"), OPT_SET_INT(0, "bare", &is_bare_repository_cfg, "create a bare repository", 1), { OPTION_CALLBACK, 0, "shared", &init_shared_repository, @@ -426,11 +490,16 @@ int cmd_init_db(int argc, const char **argv, const char *prefix) "specify that the git repository is to be shared amongst several users", PARSE_OPT_OPTARG | PARSE_OPT_NONEG, shared_callback, 0}, OPT_BIT('q', "quiet", &flags, "be quiet", INIT_DB_QUIET), + OPT_STRING(0, "separate-git-dir", &real_git_dir, "gitdir", + "separate git dir from working tree"), OPT_END() }; argc = parse_options(argc, argv, prefix, init_db_options, init_db_usage, 0); + if (real_git_dir && !is_absolute_path(real_git_dir)) + real_git_dir = xstrdup(real_path(real_git_dir)); + if (argc == 1) { int mkdir_tried = 0; retry: @@ -449,18 +518,18 @@ int cmd_init_db(int argc, const char **argv, const char *prefix) errno = EEXIST; /* fallthru */ case -1: - die_errno("cannot mkdir %s", argv[0]); + die_errno(_("cannot mkdir %s"), argv[0]); break; default: break; } shared_repository = saved; if (mkdir(argv[0], 0777) < 0) - die_errno("cannot mkdir %s", argv[0]); + die_errno(_("cannot mkdir %s"), argv[0]); mkdir_tried = 1; goto retry; } - die_errno("cannot chdir to %s", argv[0]); + die_errno(_("cannot chdir to %s"), argv[0]); } } else if (0 < argc) { usage(init_db_usage[0]); @@ -480,10 +549,10 @@ int cmd_init_db(int argc, const char **argv, const char *prefix) * without --bare. Catch the error early. */ git_dir = getenv(GIT_DIR_ENVIRONMENT); - if ((!git_dir || is_bare_repository_cfg == 1) - && getenv(GIT_WORK_TREE_ENVIRONMENT)) - die("%s (or --work-tree=<directory>) not allowed without " - "specifying %s (or --git-dir=<directory>)", + work_tree = getenv(GIT_WORK_TREE_ENVIRONMENT); + if ((!git_dir || is_bare_repository_cfg == 1) && work_tree) + die(_("%s (or --work-tree=<directory>) not allowed without " + "specifying %s (or --git-dir=<directory>)"), GIT_WORK_TREE_ENVIRONMENT, GIT_DIR_ENVIRONMENT); @@ -497,25 +566,31 @@ int cmd_init_db(int argc, const char **argv, const char *prefix) is_bare_repository_cfg = guess_repository_type(git_dir); if (!is_bare_repository_cfg) { - if (git_dir) { - const char *git_dir_parent = strrchr(git_dir, '/'); - if (git_dir_parent) { - char *rel = xstrndup(git_dir, git_dir_parent - git_dir); - git_work_tree_cfg = xstrdup(make_absolute_path(rel)); - free(rel); - } + const char *git_dir_parent = strrchr(git_dir, '/'); + if (git_dir_parent) { + char *rel = xstrndup(git_dir, git_dir_parent - git_dir); + git_work_tree_cfg = xstrdup(real_path(rel)); + free(rel); } if (!git_work_tree_cfg) { git_work_tree_cfg = xcalloc(PATH_MAX, 1); if (!getcwd(git_work_tree_cfg, PATH_MAX)) - die_errno ("Cannot access current working directory"); + die_errno (_("Cannot access current working directory")); } + if (work_tree) + set_git_work_tree(real_path(work_tree)); + else + set_git_work_tree(git_work_tree_cfg); if (access(get_git_work_tree(), X_OK)) - die_errno ("Cannot access work tree '%s'", + die_errno (_("Cannot access work tree '%s'"), get_git_work_tree()); } + else { + if (work_tree) + set_git_work_tree(real_path(work_tree)); + } - set_git_dir(make_absolute_path(git_dir)); + set_git_dir_init(git_dir, real_git_dir, 1); return init_db(template_dir, flags); } diff --git a/builtin/log.c b/builtin/log.c index 22d12903ac..4f1b42a685 100644 --- a/builtin/log.c +++ b/builtin/log.c @@ -19,18 +19,25 @@ #include "remote.h" #include "string-list.h" #include "parse-options.h" +#include "branch.h" +#include "streaming.h" +#include "version.h" /* Set a default date-time format for git log ("log.date" config variable) */ static const char *default_date_mode = NULL; +static int default_abbrev_commit; static int default_show_root = 1; static int decoration_style; +static int decoration_given; static const char *fmt_patch_subject_prefix = "PATCH"; static const char *fmt_pretty; -static const char * const builtin_log_usage = +static const char * const builtin_log_usage[] = { "git log [<options>] [<since>..<until>] [[--] <path>...]\n" - " or: git show [options] <object>..."; + " or: git show [options] <object>...", + NULL +}; static int parse_decoration_style(const char *var, const char *value) { @@ -49,33 +56,66 @@ static int parse_decoration_style(const char *var, const char *value) return -1; } -static void cmd_log_init(int argc, const char **argv, const char *prefix, - struct rev_info *rev, struct setup_revision_opt *opt) +static int decorate_callback(const struct option *opt, const char *arg, int unset) { - int i; - int decoration_given = 0; - struct userformat_want w; + if (unset) + decoration_style = 0; + else if (arg) + decoration_style = parse_decoration_style("command line", arg); + else + decoration_style = DECORATE_SHORT_REFS; - rev->abbrev = DEFAULT_ABBREV; - rev->commit_format = CMIT_FMT_DEFAULT; + if (decoration_style < 0) + die("invalid --decorate option: %s", arg); + + decoration_given = 1; + + return 0; +} + +static void cmd_log_init_defaults(struct rev_info *rev) +{ if (fmt_pretty) get_commit_format(fmt_pretty, rev); rev->verbose_header = 1; DIFF_OPT_SET(&rev->diffopt, RECURSIVE); + rev->diffopt.stat_width = -1; /* use full terminal width */ + rev->diffopt.stat_graph_width = -1; /* respect statGraphWidth config */ + rev->abbrev_commit = default_abbrev_commit; rev->show_root_diff = default_show_root; rev->subject_prefix = fmt_patch_subject_prefix; DIFF_OPT_SET(&rev->diffopt, ALLOW_TEXTCONV); if (default_date_mode) rev->date_mode = parse_date_format(default_date_mode); +} + +static void cmd_log_init_finish(int argc, const char **argv, const char *prefix, + struct rev_info *rev, struct setup_revision_opt *opt) +{ + struct userformat_want w; + int quiet = 0, source = 0; + + const struct option builtin_log_options[] = { + OPT_BOOLEAN(0, "quiet", &quiet, "suppress diff output"), + OPT_BOOLEAN(0, "source", &source, "show source"), + { OPTION_CALLBACK, 0, "decorate", NULL, NULL, "decorate options", + PARSE_OPT_OPTARG, decorate_callback}, + OPT_END() + }; + + argc = parse_options(argc, argv, prefix, + builtin_log_options, builtin_log_usage, + PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN | + PARSE_OPT_KEEP_DASHDASH); - /* - * Check for -h before setup_revisions(), or "git log -h" will - * fail when run without a git directory. - */ - if (argc == 2 && !strcmp(argv[1], "-h")) - usage(builtin_log_usage); argc = setup_revisions(argc, argv, rev, opt); + if (quiet) + rev->diffopt.output_format |= DIFF_FORMAT_NO_OUTPUT; + + /* Any arguments at this point are not recognized */ + if (argc > 1) + die("unrecognized argument: %s", argv[1]); memset(&w, 0, sizeof(w)); userformat_find_requirements(NULL, &w); @@ -89,38 +129,24 @@ static void cmd_log_init(int argc, const char **argv, const char *prefix, rev->always_show_header = 0; if (DIFF_OPT_TST(&rev->diffopt, FOLLOW_RENAMES)) { rev->always_show_header = 0; - if (rev->diffopt.nr_paths != 1) + if (rev->diffopt.pathspec.nr != 1) usage("git logs can only follow renames on one pathname at a time"); } - for (i = 1; i < argc; i++) { - const char *arg = argv[i]; - if (!strcmp(arg, "--decorate")) { - decoration_style = DECORATE_SHORT_REFS; - decoration_given = 1; - } else if (!prefixcmp(arg, "--decorate=")) { - const char *v = skip_prefix(arg, "--decorate="); - decoration_style = parse_decoration_style(arg, v); - if (decoration_style < 0) - die("invalid --decorate option: %s", arg); - decoration_given = 1; - } else if (!strcmp(arg, "--no-decorate")) { + + if (source) + rev->show_source = 1; + + if (rev->pretty_given && rev->commit_format == CMIT_FMT_RAW) { + /* + * "log --pretty=raw" is special; ignore UI oriented + * configuration variables such as decoration. + */ + if (!decoration_given) decoration_style = 0; - } else if (!strcmp(arg, "--source")) { - rev->show_source = 1; - } else if (!strcmp(arg, "-h")) { - usage(builtin_log_usage); - } else - die("unrecognized argument: %s", arg); + if (!rev->abbrev_commit_given) + rev->abbrev_commit = 0; } - /* - * defeat log.decorate configuration interacting with --pretty=raw - * from the command line. - */ - if (!decoration_given && rev->pretty_given - && rev->commit_format == CMIT_FMT_RAW) - decoration_style = 0; - if (decoration_style) { rev->show_decorations = 1; load_ref_decorations(decoration_style); @@ -128,6 +154,13 @@ static void cmd_log_init(int argc, const char **argv, const char *prefix, setup_pager(); } +static void cmd_log_init(int argc, const char **argv, const char *prefix, + struct rev_info *rev, struct setup_revision_opt *opt) +{ + cmd_log_init_defaults(rev); + cmd_log_init_finish(argc, argv, prefix, rev, opt); +} + /* * This gives a rough estimate for how many commits we * will print out in the list. @@ -153,7 +186,7 @@ static void show_early_header(struct rev_info *rev, const char *stage, int nr) if (rev->commit_format != CMIT_FMT_ONELINE) putchar(rev->diffopt.line_termination); } - printf("Final output: %d %s\n", nr, stage); + printf(_("Final output: %d %s\n"), nr, stage); } static struct itimerval early_output_timer; @@ -247,12 +280,14 @@ static void finish_early_output(struct rev_info *rev) static int cmd_log_walk(struct rev_info *rev) { struct commit *commit; + int saved_nrl = 0; + int saved_dcctc = 0; if (rev->early_output) setup_early_output(rev); if (prepare_revision_walk(rev)) - die("revision walk setup failed"); + die(_("revision walk setup failed")); if (rev->early_output) finish_early_output(rev); @@ -263,7 +298,13 @@ static int cmd_log_walk(struct rev_info *rev) * retain that state information if replacing rev->diffopt in this loop */ while ((commit = get_revision(rev)) != NULL) { - log_tree_commit(rev, commit); + if (!log_tree_commit(rev, commit) && + rev->max_count >= 0) + /* + * We decremented max_count in get_revision, + * but we didn't actually show the commit. + */ + rev->max_count++; if (!rev->reflog_info) { /* we allow cycles in reflog ancestry */ free(commit->buffer); @@ -271,7 +312,14 @@ static int cmd_log_walk(struct rev_info *rev) } free_commit_list(commit->parents); commit->parents = NULL; + if (saved_nrl < rev->diffopt.needed_rename_limit) + saved_nrl = rev->diffopt.needed_rename_limit; + if (rev->diffopt.degraded_cc_to_c) + saved_dcctc = 1; } + rev->diffopt.degraded_cc_to_c = saved_dcctc; + rev->diffopt.needed_rename_limit = saved_nrl; + if (rev->diffopt.output_format & DIFF_FORMAT_CHECKDIFF && DIFF_OPT_TST(&rev->diffopt, CHECK_FAILED)) { return 02; @@ -285,6 +333,10 @@ static int git_log_config(const char *var, const char *value, void *cb) return git_config_string(&fmt_pretty, var, value); if (!strcmp(var, "format.subjectprefix")) return git_config_string(&fmt_patch_subject_prefix, var, value); + if (!strcmp(var, "log.abbrevcommit")) { + default_abbrev_commit = git_config_bool(var, value); + return 0; + } if (!strcmp(var, "log.date")) return git_config_string(&default_date_mode, var, value); if (!strcmp(var, "log.decorate")) { @@ -310,9 +362,6 @@ int cmd_whatchanged(int argc, const char **argv, const char *prefix) git_config(git_log_config, NULL); - if (diff_use_color_default == -1) - diff_use_color_default = git_use_color_default; - init_revisions(&rev, prefix); rev.diff = 1; rev.simplify_history = 0; @@ -327,16 +376,22 @@ int cmd_whatchanged(int argc, const char **argv, const char *prefix) static void show_tagger(char *buf, int len, struct rev_info *rev) { struct strbuf out = STRBUF_INIT; + struct pretty_print_context pp = {0}; - pp_user_info("Tagger", rev->commit_format, &out, buf, rev->date_mode, - git_log_output_encoding ? - git_log_output_encoding: git_commit_encoding); + pp.fmt = rev->commit_format; + pp.date_mode = rev->date_mode; + pp_user_info(&pp, "Tagger", &out, buf, get_log_output_encoding()); printf("%s", out.buf); strbuf_release(&out); } -static int show_object(const unsigned char *sha1, int show_tag_object, - struct rev_info *rev) +static int show_blob_object(const unsigned char *sha1, struct rev_info *rev) +{ + fflush(stdout); + return stream_blob_to_fd(1, sha1, NULL, 0); +} + +static int show_tag_object(const unsigned char *sha1, struct rev_info *rev) { unsigned long size; enum object_type type; @@ -344,18 +399,18 @@ static int show_object(const unsigned char *sha1, int show_tag_object, int offset = 0; if (!buf) - return error("Could not read object %s", sha1_to_hex(sha1)); - - if (show_tag_object) - while (offset < size && buf[offset] != '\n') { - int new_offset = offset + 1; - while (new_offset < size && buf[new_offset++] != '\n') - ; /* do nothing */ - if (!prefixcmp(buf + offset, "tagger ")) - show_tagger(buf + offset + 7, - new_offset - offset - 7, rev); - offset = new_offset; - } + return error(_("Could not read object %s"), sha1_to_hex(sha1)); + + assert(type == OBJ_TAG); + while (offset < size && buf[offset] != '\n') { + int new_offset = offset + 1; + while (new_offset < size && buf[new_offset++] != '\n') + ; /* do nothing */ + if (!prefixcmp(buf + offset, "tagger ")) + show_tagger(buf + offset + 7, + new_offset - offset - 7, rev); + offset = new_offset; + } if (offset < size) fwrite(buf + offset, size - offset, 1, stdout); @@ -391,17 +446,18 @@ int cmd_show(int argc, const char **argv, const char *prefix) struct rev_info rev; struct object_array_entry *objects; struct setup_revision_opt opt; + struct pathspec match_all; int i, count, ret = 0; git_config(git_log_config, NULL); - if (diff_use_color_default == -1) - diff_use_color_default = git_use_color_default; - + init_pathspec(&match_all, NULL); init_revisions(&rev, prefix); rev.diff = 1; rev.always_show_header = 1; rev.no_walk = 1; + rev.diffopt.stat_width = -1; /* Scale to real terminal size */ + memset(&opt, 0, sizeof(opt)); opt.def = "HEAD"; opt.tweak = show_rev_tweak_rev; @@ -414,7 +470,7 @@ int cmd_show(int argc, const char **argv, const char *prefix) const char *name = objects[i].name; switch (o->type) { case OBJ_BLOB: - ret = show_object(o->sha1, 0, NULL); + ret = show_blob_object(o->sha1, NULL); break; case OBJ_TAG: { struct tag *t = (struct tag *)o; @@ -425,13 +481,13 @@ int cmd_show(int argc, const char **argv, const char *prefix) diff_get_color_opt(&rev.diffopt, DIFF_COMMIT), t->tag, diff_get_color_opt(&rev.diffopt, DIFF_RESET)); - ret = show_object(o->sha1, 1, &rev); + ret = show_tag_object(o->sha1, &rev); rev.shown_one = 1; if (ret) break; o = parse_object(t->tagged->sha1); if (!o) - ret = error("Could not read object %s", + ret = error(_("Could not read object %s"), sha1_to_hex(t->tagged->sha1)); objects[i].item = o; i--; @@ -444,7 +500,7 @@ int cmd_show(int argc, const char **argv, const char *prefix) diff_get_color_opt(&rev.diffopt, DIFF_COMMIT), name, diff_get_color_opt(&rev.diffopt, DIFF_RESET)); - read_tree_recursive((struct tree *)o, "", 0, 0, NULL, + read_tree_recursive((struct tree *)o, "", 0, 0, &match_all, show_tree_object, NULL); rev.shown_one = 1; break; @@ -455,7 +511,7 @@ int cmd_show(int argc, const char **argv, const char *prefix) ret = cmd_log_walk(&rev); break; default: - ret = error("Unknown type: %d", o->type); + ret = error(_("Unknown type: %d"), o->type); } } free(objects); @@ -472,25 +528,17 @@ int cmd_log_reflog(int argc, const char **argv, const char *prefix) git_config(git_log_config, NULL); - if (diff_use_color_default == -1) - diff_use_color_default = git_use_color_default; - init_revisions(&rev, prefix); init_reflog_walk(&rev.reflog_info); - rev.abbrev_commit = 1; rev.verbose_header = 1; memset(&opt, 0, sizeof(opt)); opt.def = "HEAD"; - cmd_log_init(argc, argv, prefix, &rev, &opt); - - /* - * This means that we override whatever commit format the user gave - * on the cmd line. Sad, but cmd_log_init() currently doesn't - * allow us to set a different default. - */ + cmd_log_init_defaults(&rev); + rev.abbrev_commit = 1; rev.commit_format = CMIT_FMT_ONELINE; rev.use_terminator = 1; rev.always_show_header = 1; + cmd_log_init_finish(argc, argv, prefix, &rev, &opt); return cmd_log_walk(&rev); } @@ -502,9 +550,6 @@ int cmd_log(int argc, const char **argv, const char *prefix) git_config(git_log_config, NULL); - if (diff_use_color_default == -1) - diff_use_color_default = git_use_color_default; - init_revisions(&rev, prefix); rev.always_show_header = 1; memset(&opt, 0, sizeof(opt)); @@ -555,7 +600,7 @@ static int git_format_config(const char *var, const char *value, void *cb) { if (!strcmp(var, "format.headers")) { if (!value) - die("format.headers without value"); + die(_("format.headers without value")); add_header(value); return 0; } @@ -573,7 +618,8 @@ static int git_format_config(const char *var, const char *value, void *cb) string_list_append(&extra_cc, value); return 0; } - if (!strcmp(var, "diff.color") || !strcmp(var, "color.diff")) { + if (!strcmp(var, "diff.color") || !strcmp(var, "color.diff") || + !strcmp(var, "color.ui")) { return 0; } if (!strcmp(var, "format.numbered")) { @@ -618,7 +664,8 @@ static FILE *realstdout = NULL; static const char *output_directory = NULL; static int outdir_offset; -static int reopen_stdout(struct commit *commit, struct rev_info *rev) +static int reopen_stdout(struct commit *commit, const char *subject, + struct rev_info *rev, int quiet) { struct strbuf filename = STRBUF_INIT; int suffix_len = strlen(fmt_patch_suffix) + 1; @@ -627,18 +674,18 @@ static int reopen_stdout(struct commit *commit, struct rev_info *rev) strbuf_addstr(&filename, output_directory); if (filename.len >= PATH_MAX - FORMAT_PATCH_NAME_MAX - suffix_len) - return error("name of output directory is too long"); + return error(_("name of output directory is too long")); if (filename.buf[filename.len - 1] != '/') strbuf_addch(&filename, '/'); } - get_patch_filename(commit, rev->nr, fmt_patch_suffix, &filename); + get_patch_filename(commit, subject, rev->nr, fmt_patch_suffix, &filename); - if (!DIFF_OPT_TST(&rev->diffopt, QUICK)) + if (!quiet) fprintf(realstdout, "%s\n", filename.buf + outdir_offset); if (freopen(filename.buf, "w", stdout) == NULL) - return error("Cannot open patch file %s", filename.buf); + return error(_("Cannot open patch file %s"), filename.buf); strbuf_release(&filename); return 0; @@ -652,7 +699,7 @@ static void get_patch_ids(struct rev_info *rev, struct patch_ids *ids, const cha unsigned flags1, flags2; if (rev->pending.nr != 2) - die("Need exactly one range."); + die(_("Need exactly one range.")); o1 = rev->pending.objects[0].item; flags1 = o1->flags; @@ -660,7 +707,7 @@ static void get_patch_ids(struct rev_info *rev, struct patch_ids *ids, const cha flags2 = o2->flags; if ((flags1 & UNINTERESTING) == (flags2 & UNINTERESTING)) - die("Not a range."); + die(_("Not a range.")); init_patch_ids(ids); @@ -671,7 +718,7 @@ static void get_patch_ids(struct rev_info *rev, struct patch_ids *ids, const cha add_pending_object(&check_rev, o1, "o1"); add_pending_object(&check_rev, o2, "o2"); if (prepare_revision_walk(&check_rev)) - die("revision walk setup failed"); + die(_("revision walk setup failed")); while ((commit = get_revision(&check_rev)) != NULL) { /* ignore merges */ @@ -692,15 +739,10 @@ static void get_patch_ids(struct rev_info *rev, struct patch_ids *ids, const cha static void gen_message_id(struct rev_info *info, char *base) { - const char *committer = git_committer_info(IDENT_WARN_ON_NO_NAME); - const char *email_start = strrchr(committer, '<'); - const char *email_end = strrchr(committer, '>'); struct strbuf buf = STRBUF_INIT; - if (!email_start || !email_end || email_start > email_end - 1) - die("Could not extract email from committer identity."); - strbuf_addf(&buf, "%s.%lu.git.%.*s", base, + strbuf_addf(&buf, "%s.%lu.git.%s", base, (unsigned long) time(NULL), - (int)(email_end - email_start - 1), email_start + 1); + git_committer_info(IDENT_NO_NAME|IDENT_NO_DATE|IDENT_STRICT)); info->message_id = strbuf_detach(&buf, NULL); } @@ -710,55 +752,47 @@ static void print_signature(void) printf("-- \n%s\n\n", signature); } +static void add_branch_description(struct strbuf *buf, const char *branch_name) +{ + struct strbuf desc = STRBUF_INIT; + if (!branch_name || !*branch_name) + return; + read_branch_desc(&desc, branch_name); + if (desc.len) { + strbuf_addch(buf, '\n'); + strbuf_add(buf, desc.buf, desc.len); + strbuf_addch(buf, '\n'); + } +} + static void make_cover_letter(struct rev_info *rev, int use_stdout, int numbered, int numbered_files, struct commit *origin, - int nr, struct commit **list, struct commit *head) + int nr, struct commit **list, struct commit *head, + const char *branch_name, + int quiet) { const char *committer; - const char *subject_start = NULL; const char *body = "*** SUBJECT HERE ***\n\n*** BLURB HERE ***\n"; const char *msg; - const char *extra_headers = rev->extra_headers; struct shortlog log; struct strbuf sb = STRBUF_INIT; int i; const char *encoding = "UTF-8"; struct diff_options opts; int need_8bit_cte = 0; - struct commit *commit = NULL; + struct pretty_print_context pp = {0}; if (rev->commit_format != CMIT_FMT_EMAIL) - die("Cover letter needs email format"); + die(_("Cover letter needs email format")); committer = git_committer_info(0); - if (!numbered_files) { - /* - * We fake a commit for the cover letter so we get the filename - * desired. - */ - commit = xcalloc(1, sizeof(*commit)); - commit->buffer = xmalloc(400); - snprintf(commit->buffer, 400, - "tree 0000000000000000000000000000000000000000\n" - "parent %s\n" - "author %s\n" - "committer %s\n\n" - "cover letter\n", - sha1_to_hex(head->object.sha1), committer, committer); - } - - if (!use_stdout && reopen_stdout(commit, rev)) + if (!use_stdout && + reopen_stdout(NULL, numbered_files ? NULL : "cover-letter", rev, quiet)) return; - if (commit) { - - free(commit->buffer); - free(commit); - } - - log_write_email_headers(rev, head, &subject_start, &extra_headers, + log_write_email_headers(rev, head, &pp.subject, &pp.after_subject, &need_8bit_cte); for (i = 0; !need_8bit_cte && i < nr; i++) @@ -766,11 +800,12 @@ static void make_cover_letter(struct rev_info *rev, int use_stdout, need_8bit_cte = 1; msg = body; - pp_user_info(NULL, CMIT_FMT_EMAIL, &sb, committer, DATE_RFC2822, - encoding); - pp_title_line(CMIT_FMT_EMAIL, &msg, &sb, subject_start, extra_headers, - encoding, need_8bit_cte); - pp_remainder(CMIT_FMT_EMAIL, &msg, &sb, 0); + pp.fmt = CMIT_FMT_EMAIL; + pp.date_mode = DATE_RFC2822; + pp_user_info(&pp, NULL, &sb, committer, encoding); + pp_title_line(&pp, &msg, &sb, encoding, need_8bit_cte); + pp_remainder(&pp, &msg, &sb, 0); + add_branch_description(&sb, branch_name); printf("%s\n", sb.buf); strbuf_release(&sb); @@ -822,7 +857,7 @@ static const char *clean_message_id(const char *msg_id) m++; } if (!z) - die("insane in-reply-to: %s", msg_id); + die(_("insane in-reply-to: %s"), msg_id); if (++z == m) return a; return xmemdupz(a, z - a); @@ -895,7 +930,7 @@ static int output_directory_callback(const struct option *opt, const char *arg, { const char **dir = (const char **)opt->value; if (*dir) - die("Two output directories?"); + die(_("Two output directories?")); *dir = arg; return 0; } @@ -970,6 +1005,35 @@ static int cc_callback(const struct option *opt, const char *arg, int unset) return 0; } +static char *find_branch_name(struct rev_info *rev) +{ + int i, positive = -1; + unsigned char branch_sha1[20]; + struct strbuf buf = STRBUF_INIT; + const char *branch; + + for (i = 0; i < rev->cmdline.nr; i++) { + if (rev->cmdline.rev[i].flags & UNINTERESTING) + continue; + if (positive < 0) + positive = i; + else + return NULL; + } + if (positive < 0) + return NULL; + strbuf_addf(&buf, "refs/heads/%s", rev->cmdline.rev[positive].name); + branch = resolve_ref_unsafe(buf.buf, branch_sha1, 1, NULL); + if (!branch || + prefixcmp(branch, "refs/heads/") || + hashcmp(rev->cmdline.rev[positive].item->sha1, branch_sha1)) + branch = NULL; + strbuf_release(&buf); + if (branch) + return xstrdup(rev->cmdline.rev[positive].name); + return NULL; +} + int cmd_format_patch(int argc, const char **argv, const char *prefix) { struct commit *commit; @@ -990,6 +1054,8 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) char *add_signoff = NULL; struct strbuf buf = STRBUF_INIT; int use_patch_format = 0; + int quiet = 0; + char *branch_name = NULL; const struct option builtin_format_patch_options[] = { { OPTION_CALLBACK, 'n', "numbered", &numbered, NULL, "use [PATCH n/m] even with a single patch", @@ -1045,6 +1111,8 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) PARSE_OPT_OPTARG, thread_callback }, OPT_STRING(0, "signature", &signature, "signature", "add a signature"), + OPT_BOOLEAN(0, "quiet", &quiet, + "don't print the patch filenames"), OPT_END() }; @@ -1056,7 +1124,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) rev.commit_format = CMIT_FMT_EMAIL; rev.verbose_header = 1; rev.diff = 1; - rev.no_merges = 1; + rev.max_parents = 1; DIFF_OPT_SET(&rev.diffopt, RECURSIVE); rev.subject_prefix = fmt_patch_subject_prefix; memset(&s_r_opt, 0, sizeof(s_r_opt)); @@ -1080,10 +1148,10 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) if (do_signoff) { const char *committer; const char *endpos; - committer = git_committer_info(IDENT_ERROR_ON_NO_NAME); + committer = git_committer_info(IDENT_STRICT); endpos = strchr(committer, '>'); if (!endpos) - die("bogus committer info %s", committer); + die(_("bogus committer info %s"), committer); add_signoff = xmemdupz(committer, endpos - committer + 1); } @@ -1128,20 +1196,21 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) numbered = 0; if (numbered && keep_subject) - die ("-n and -k are mutually exclusive."); + die (_("-n and -k are mutually exclusive.")); if (keep_subject && subject_prefix) - die ("--subject-prefix and -k are mutually exclusive."); + die (_("--subject-prefix and -k are mutually exclusive.")); + rev.preserve_subject = keep_subject; argc = setup_revisions(argc, argv, &rev, &s_r_opt); if (argc > 1) - die ("unrecognized argument: %s", argv[1]); + die (_("unrecognized argument: %s"), argv[1]); if (rev.diffopt.output_format & DIFF_FORMAT_NAME) - die("--name-only does not make sense"); + die(_("--name-only does not make sense")); if (rev.diffopt.output_format & DIFF_FORMAT_NAME_STATUS) - die("--name-status does not make sense"); + die(_("--name-status does not make sense")); if (rev.diffopt.output_format & DIFF_FORMAT_CHECKDIFF) - die("--check does not make sense"); + die(_("--check does not make sense")); if (!use_patch_format && (!rev.diffopt.output_format || @@ -1159,12 +1228,14 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) if (!use_stdout) output_directory = set_outdir(prefix, output_directory); + else + setup_pager(); if (output_directory) { if (use_stdout) - die("standard output, or directory, which one?"); + die(_("standard output, or directory, which one?")); if (mkdir(output_directory, 0777) < 0 && errno != EEXIST) - die_errno("Could not create directory '%s'", + die_errno(_("Could not create directory '%s'"), output_directory); } @@ -1175,8 +1246,16 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) * origin" that prepares what the origin side still * does not have. */ + unsigned char sha1[20]; + const char *ref; + rev.pending.objects[0].item->flags |= UNINTERESTING; add_head_to_pending(&rev); + ref = resolve_ref_unsafe("HEAD", sha1, 1, NULL); + if (ref && !prefixcmp(ref, "refs/heads/")) + branch_name = xstrdup(ref + strlen("refs/heads/")); + else + branch_name = xstrdup(""); /* no branch */ } /* * Otherwise, it is "format-patch -22 HEAD", and/or @@ -1192,16 +1271,26 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) rev.show_root_diff = 1; if (cover_letter) { - /* remember the range */ + /* + * NEEDSWORK:randomly pick one positive commit to show + * diffstat; this is often the tip and the command + * happens to do the right thing in most cases, but a + * complex command like "--cover-letter a b c ^bottom" + * picks "c" and shows diffstat between bottom..c + * which may not match what the series represents at + * all and totally broken. + */ int i; for (i = 0; i < rev.pending.nr; i++) { struct object *o = rev.pending.objects[i].item; if (!(o->flags & UNINTERESTING)) head = (struct commit *)o; } - /* We can't generate a cover letter without any patches */ + /* There is nothing to show; it is not an error, though. */ if (!head) return 0; + if (!branch_name) + branch_name = find_branch_name(&rev); } if (ignore_if_in_upstream) { @@ -1218,7 +1307,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) realstdout = xfdopen(xdup(1), "w"); if (prepare_revision_walk(&rev)) - die("revision walk setup failed"); + die(_("revision walk setup failed")); rev.boundary = 1; while ((commit = get_revision(&rev)) != NULL) { if (commit->object.flags & BOUNDARY) { @@ -1252,7 +1341,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) if (thread) gen_message_id(&rev, "cover"); make_cover_letter(&rev, use_stdout, numbered, numbered_files, - origin, nr, list, head); + origin, nr, list, head, branch_name, quiet); total++; start_number--; } @@ -1297,9 +1386,9 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) gen_message_id(&rev, sha1_to_hex(commit->object.sha1)); } - if (!use_stdout && reopen_stdout(numbered_files ? NULL : commit, - &rev)) - die("Failed to create output files"); + if (!use_stdout && + reopen_stdout(numbered_files ? NULL : commit, NULL, &rev, quiet)) + die(_("Failed to create output files")); shown = log_tree_commit(&rev, commit); free(commit->buffer); commit->buffer = NULL; @@ -1324,6 +1413,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) fclose(stdout); } free(list); + free(branch_name); string_list_clear(&extra_to, 0); string_list_clear(&extra_cc, 0); string_list_clear(&extra_hdr, 0); @@ -1351,6 +1441,22 @@ static const char * const cherry_usage[] = { NULL }; +static void print_commit(char sign, struct commit *commit, int verbose, + int abbrev) +{ + if (!verbose) { + printf("%c %s\n", sign, + find_unique_abbrev(commit->object.sha1, abbrev)); + } else { + struct strbuf buf = STRBUF_INIT; + pp_commit_easy(CMIT_FMT_ONELINE, commit, &buf); + printf("%c %s %s\n", sign, + find_unique_abbrev(commit->object.sha1, abbrev), + buf.buf); + strbuf_release(&buf); + } +} + int cmd_cherry(int argc, const char **argv, const char *prefix) { struct rev_info revs; @@ -1365,7 +1471,7 @@ int cmd_cherry(int argc, const char **argv, const char *prefix) struct option options[] = { OPT__ABBREV(&abbrev), - OPT__VERBOSE(&verbose), + OPT__VERBOSE(&verbose, "be verbose"), OPT_END() }; @@ -1386,9 +1492,9 @@ int cmd_cherry(int argc, const char **argv, const char *prefix) if (!current_branch || !current_branch->merge || !current_branch->merge[0] || !current_branch->merge[0]->dst) { - fprintf(stderr, "Could not find a tracked" + fprintf(stderr, _("Could not find a tracked" " remote branch, please" - " specify <upstream> manually.\n"); + " specify <upstream> manually.\n")); usage_with_options(cherry_usage, options); } @@ -1402,9 +1508,9 @@ int cmd_cherry(int argc, const char **argv, const char *prefix) DIFF_OPT_SET(&revs.diffopt, RECURSIVE); if (add_pending_commit(head, &revs, 0)) - die("Unknown commit %s", head); + die(_("Unknown commit %s"), head); if (add_pending_commit(upstream, &revs, UNINTERESTING)) - die("Unknown commit %s", upstream); + die(_("Unknown commit %s"), upstream); /* Don't say anything if head and upstream are the same. */ if (revs.pending.nr == 2) { @@ -1416,11 +1522,11 @@ int cmd_cherry(int argc, const char **argv, const char *prefix) get_patch_ids(&revs, &ids, prefix); if (limit && add_pending_commit(limit, &revs, UNINTERESTING)) - die("Unknown commit %s", limit); + die(_("Unknown commit %s"), limit); /* reverse the list of commits */ if (prepare_revision_walk(&revs)) - die("revision walk setup failed"); + die(_("revision walk setup failed")); while ((commit = get_revision(&revs)) != NULL) { /* ignore merges */ if (commit->parents && commit->parents->next) @@ -1435,22 +1541,7 @@ int cmd_cherry(int argc, const char **argv, const char *prefix) commit = list->item; if (has_commit_patch_id(commit, &ids)) sign = '-'; - - if (verbose) { - struct strbuf buf = STRBUF_INIT; - struct pretty_print_context ctx = {0}; - pretty_print_commit(CMIT_FMT_ONELINE, commit, - &buf, &ctx); - printf("%c %s %s\n", sign, - find_unique_abbrev(commit->object.sha1, abbrev), - buf.buf); - strbuf_release(&buf); - } - else { - printf("%c %s\n", sign, - find_unique_abbrev(commit->object.sha1, abbrev)); - } - + print_commit(sign, commit, verbose, abbrev); list = list->next; } diff --git a/builtin/ls-files.c b/builtin/ls-files.c index 6a307ab784..31b3f2d900 100644 --- a/builtin/ls-files.c +++ b/builtin/ls-files.c @@ -200,9 +200,19 @@ static void show_ru_info(void) } } +static int ce_excluded(struct path_exclude_check *check, struct cache_entry *ce) +{ + int dtype = ce_to_dtype(ce); + return path_excluded(check, ce->name, ce_namelen(ce), &dtype); +} + static void show_files(struct dir_struct *dir) { int i; + struct path_exclude_check check; + + if ((dir->flags & DIR_SHOW_IGNORED)) + path_exclude_check_init(&check, dir); /* For cached/deleted files we don't need to even do the readdir */ if (show_others || show_killed) { @@ -215,9 +225,8 @@ static void show_files(struct dir_struct *dir) if (show_cached | show_stage) { for (i = 0; i < active_nr; i++) { struct cache_entry *ce = active_cache[i]; - int dtype = ce_to_dtype(ce); - if (dir->flags & DIR_SHOW_IGNORED && - !excluded(dir, ce->name, &dtype)) + if ((dir->flags & DIR_SHOW_IGNORED) && + !ce_excluded(&check, ce)) continue; if (show_unmerged && !ce_stage(ce)) continue; @@ -232,9 +241,8 @@ static void show_files(struct dir_struct *dir) struct cache_entry *ce = active_cache[i]; struct stat st; int err; - int dtype = ce_to_dtype(ce); - if (dir->flags & DIR_SHOW_IGNORED && - !excluded(dir, ce->name, &dtype)) + if ((dir->flags & DIR_SHOW_IGNORED) && + !ce_excluded(&check, ce)) continue; if (ce->ce_flags & CE_UPDATE) continue; @@ -247,6 +255,9 @@ static void show_files(struct dir_struct *dir) show_ce_entry(tag_modified, ce); } } + + if ((dir->flags & DIR_SHOW_IGNORED)) + path_exclude_check_clear(&check); } /* @@ -276,41 +287,6 @@ static void prune_cache(const char *prefix) active_nr = last; } -static const char *pathspec_prefix(const char *prefix) -{ - const char **p, *n, *prev; - unsigned long max; - - if (!pathspec) { - max_prefix_len = prefix ? strlen(prefix) : 0; - return prefix; - } - - prev = NULL; - max = PATH_MAX; - for (p = pathspec; (n = *p) != NULL; p++) { - int i, len = 0; - for (i = 0; i < max; i++) { - char c = n[i]; - if (prev && prev[i] != c) - break; - if (!c || c == '*' || c == '?') - break; - if (c == '/') - len = i+1; - } - prev = n; - if (len < max) { - max = len; - if (!max) - break; - } - } - - max_prefix_len = max; - return max ? xmemdupz(prev, max) : NULL; -} - static void strip_trailing_slash_from_submodules(void) { const char **p; @@ -338,7 +314,7 @@ void overlay_tree_on_cache(const char *tree_name, const char *prefix) { struct tree *tree; unsigned char sha1[20]; - const char **match; + struct pathspec pathspec; struct cache_entry *last_stage0 = NULL; int i; @@ -360,10 +336,11 @@ void overlay_tree_on_cache(const char *tree_name, const char *prefix) static const char *(matchbuf[2]); matchbuf[0] = prefix; matchbuf[1] = NULL; - match = matchbuf; + init_pathspec(&pathspec, matchbuf); + pathspec.items[0].use_wildcard = 0; } else - match = NULL; - if (read_tree(tree, 1, match)) + init_pathspec(&pathspec, NULL); + if (read_tree(tree, 1, &pathspec)) die("unable to read tree entries %s", tree_name); for (i = 0; i < active_nr; i++) { @@ -387,11 +364,13 @@ void overlay_tree_on_cache(const char *tree_name, const char *prefix) } } -int report_path_error(const char *ps_matched, const char **pathspec, int prefix_len) +int report_path_error(const char *ps_matched, const char **pathspec, const char *prefix) { /* * Make sure all pathspec matched; otherwise it is an error. */ + struct strbuf sb = STRBUF_INIT; + const char *name; int num, errors = 0; for (num = 0; pathspec[num]; num++) { int other, found_dup; @@ -416,10 +395,12 @@ int report_path_error(const char *ps_matched, const char **pathspec, int prefix_ if (found_dup) continue; + name = quote_path_relative(pathspec[num], -1, &sb, prefix); error("pathspec '%s' did not match any file(s) known to git.", - pathspec[num] + prefix_len); + name); errors++; } + strbuf_release(&sb); return errors; } @@ -530,6 +511,9 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix) OPT_END() }; + if (argc == 2 && !strcmp(argv[1], "-h")) + usage_with_options(ls_files_usage, builtin_ls_files_options); + memset(&dir, 0, sizeof(dir)); prefix = cmd_prefix; if (prefix) @@ -572,7 +556,8 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix) strip_trailing_slash_from_submodules(); /* Find common prefix for all pathspec's */ - max_prefix = pathspec_prefix(prefix); + max_prefix = common_prefix(pathspec); + max_prefix_len = max_prefix ? strlen(max_prefix) : 0; /* Treat unmatching pathspec elements as errors */ if (pathspec && error_unmatch) { @@ -607,7 +592,7 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix) if (ps_matched) { int bad; - bad = report_path_error(ps_matched, pathspec, prefix_len); + bad = report_path_error(ps_matched, pathspec, prefix); if (bad) fprintf(stderr, "Did you forget to 'git add'?\n"); diff --git a/builtin/ls-remote.c b/builtin/ls-remote.c index 97eed4012b..41c88a98a2 100644 --- a/builtin/ls-remote.c +++ b/builtin/ls-remote.c @@ -5,7 +5,7 @@ static const char ls_remote_usage[] = "git ls-remote [--heads] [--tags] [-u <exec> | --upload-pack <exec>]\n" -" [-q|--quiet] [<repository> [<refs>...]]"; +" [-q|--quiet] [--exit-code] [<repository> [<refs>...]]"; /* * Is there one among the list of patterns that match the tail part @@ -33,7 +33,9 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix) int i; const char *dest = NULL; unsigned flags = 0; + int get_url = 0; int quiet = 0; + int status = 0; const char *uploadpack = NULL; const char **pattern = NULL; @@ -41,6 +43,9 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix) struct transport *transport; const struct ref *ref; + if (argc == 2 && !strcmp("-h", argv[1])) + usage(ls_remote_usage); + for (i = 1; i < argc; i++) { const char *arg = argv[i]; @@ -69,6 +74,15 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix) quiet = 1; continue; } + if (!strcmp("--get-url", arg)) { + get_url = 1; + continue; + } + if (!strcmp("--exit-code", arg)) { + /* return this code if no refs are reported */ + status = 2; + continue; + } usage(ls_remote_usage); } dest = arg; @@ -94,6 +108,12 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix) } if (!remote->url_nr) die("remote %s has no configured URL", dest); + + if (get_url) { + printf("%s\n", *remote->url); + return 0; + } + transport = transport_get(remote, NULL); if (uploadpack != NULL) transport_set_option(transport, TRANS_OPT_UPLOADPACK, uploadpack); @@ -110,6 +130,7 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix) if (!tail_match(pattern, ref->name)) continue; printf("%s %s\n", sha1_to_hex(ref->old_sha1), ref->name); + status = 0; /* we found something */ } - return 0; + return status; } diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c index f73e6bd962..6b666e1e87 100644 --- a/builtin/ls-tree.c +++ b/builtin/ls-tree.c @@ -19,7 +19,7 @@ static int line_termination = '\n'; #define LS_SHOW_SIZE 16 static int abbrev; static int ls_options; -static const char **pathspec; +static struct pathspec pathspec; static int chomp_prefix; static const char *ls_tree_prefix; @@ -35,7 +35,7 @@ static int show_recursive(const char *base, int baselen, const char *pathname) if (ls_options & LS_RECURSIVE) return 1; - s = pathspec; + s = pathspec.raw; if (!s) return 0; @@ -120,7 +120,7 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix) { unsigned char sha1[20]; struct tree *tree; - int full_tree = 0; + int i, full_tree = 0; const struct option ls_tree_options[] = { OPT_BIT('d', NULL, &ls_options, "only show trees", LS_TREE_ONLY), @@ -166,11 +166,12 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix) if (get_sha1(argv[0], sha1)) die("Not a valid object name %s", argv[0]); - pathspec = get_pathspec(prefix, argv + 1); + init_pathspec(&pathspec, get_pathspec(prefix, argv + 1)); + for (i = 0; i < pathspec.nr; i++) + pathspec.items[i].use_wildcard = 0; + pathspec.has_wildcard = 0; tree = parse_tree_indirect(sha1); if (!tree) die("not a tree object"); - read_tree_recursive(tree, "", 0, 0, pathspec, show_tree, NULL); - - return 0; + return !!read_tree_recursive(tree, "", 0, 0, &pathspec, show_tree, NULL); } diff --git a/builtin/mailinfo.c b/builtin/mailinfo.c index 2320d981ce..eaf9e157a3 100644 --- a/builtin/mailinfo.c +++ b/builtin/mailinfo.c @@ -250,8 +250,17 @@ static void cleanup_subject(struct strbuf *subject) (7 <= remove && memmem(subject->buf + at, remove, "PATCH", 5))) strbuf_remove(subject, at, remove); - else + else { at += remove; + /* + * If the input had a space after the ], keep + * it. We don't bother with finding the end of + * the space, since we later normalize it + * anyway. + */ + if (isspace(subject->buf[at])) + at += 1; + } continue; } break; @@ -400,7 +409,7 @@ static int read_one_header_line(struct strbuf *line, FILE *in) break; if (strbuf_getline(&continuation, in, '\n')) break; - continuation.buf[0] = '\n'; + continuation.buf[0] = ' '; strbuf_rtrim(&continuation); strbuf_addbuf(line, &continuation); } @@ -1032,7 +1041,7 @@ int cmd_mailinfo(int argc, const char **argv, const char *prefix) */ git_config(git_mailinfo_config, NULL); - def_charset = (git_commit_encoding ? git_commit_encoding : "UTF-8"); + def_charset = get_commit_output_encoding(); metainfo_charset = def_charset; while (1 < argc && argv[1][0] == '-') { diff --git a/builtin/merge-base.c b/builtin/merge-base.c index 96dd160731..4f30f1b0c8 100644 --- a/builtin/merge-base.c +++ b/builtin/merge-base.c @@ -23,7 +23,8 @@ static int show_merge_base(struct commit **rev, int rev_nr, int show_all) } static const char * const merge_base_usage[] = { - "git merge-base [-a|--all] [--octopus] <commit> <commit>...", + "git merge-base [-a|--all] <commit> <commit>...", + "git merge-base [-a|--all] --octopus <commit>...", "git merge-base --independent <commit>...", NULL }; diff --git a/builtin/merge-file.c b/builtin/merge-file.c index b6664d49be..6f0efef43c 100644 --- a/builtin/merge-file.c +++ b/builtin/merge-file.c @@ -28,6 +28,7 @@ int cmd_merge_file(int argc, const char **argv, const char *prefix) xmparam_t xmp = {{0}}; int ret = 0, i = 0, to_stdout = 0; int quiet = 0; + int prefixlen = 0; struct option options[] = { OPT_BOOLEAN('p', "stdout", &to_stdout, "send results to standard output"), OPT_SET_INT(0, "diff3", &xmp.style, "use a diff3 based merge", XDL_MERGE_DIFF3), @@ -39,7 +40,7 @@ int cmd_merge_file(int argc, const char **argv, const char *prefix) XDL_MERGE_FAVOR_UNION), OPT_INTEGER(0, "marker-size", &xmp.marker_size, "for conflicts, use this marker size"), - OPT__QUIET(&quiet), + OPT__QUIET(&quiet, "do not warn about conflicts"), OPT_CALLBACK('L', NULL, names, "name", "set labels for file1/orig_file/file2", &label_cb), OPT_END(), @@ -62,16 +63,20 @@ int cmd_merge_file(int argc, const char **argv, const char *prefix) if (quiet) { if (!freopen("/dev/null", "w", stderr)) return error("failed to redirect stderr to /dev/null: " - "%s\n", strerror(errno)); + "%s", strerror(errno)); } + if (prefix) + prefixlen = strlen(prefix); + for (i = 0; i < 3; i++) { + const char *fname = prefix_filename(prefix, prefixlen, argv[i]); if (!names[i]) names[i] = argv[i]; - if (read_mmfile(mmfs + i, argv[i])) + if (read_mmfile(mmfs + i, fname)) return -1; if (buffer_is_binary(mmfs[i].ptr, mmfs[i].size)) - return error("Cannot merge binary files: %s\n", + return error("Cannot merge binary files: %s", argv[i]); } diff --git a/builtin/merge-index.c b/builtin/merge-index.c index 2c4cf5e559..2338832587 100644 --- a/builtin/merge-index.c +++ b/builtin/merge-index.c @@ -1,6 +1,5 @@ -#include "cache.h" +#include "builtin.h" #include "run-command.h" -#include "exec_cmd.h" static const char *pgm; static int one_shot, quiet; diff --git a/builtin/merge-recursive.c b/builtin/merge-recursive.c index c33091b3ed..3a64f5d0bd 100644 --- a/builtin/merge-recursive.c +++ b/builtin/merge-recursive.c @@ -1,4 +1,4 @@ -#include "cache.h" +#include "builtin.h" #include "commit.h" #include "tag.h" #include "merge-recursive.h" diff --git a/builtin/merge-tree.c b/builtin/merge-tree.c index 9b25ddc979..897a563bc6 100644 --- a/builtin/merge-tree.c +++ b/builtin/merge-tree.c @@ -1,8 +1,9 @@ -#include "cache.h" +#include "builtin.h" #include "tree-walk.h" #include "xdiff-interface.h" #include "blob.h" #include "exec_cmd.h" +#include "merge-file.h" static const char merge_tree_usage[] = "git merge-tree <base-tree> <branch1> <branch2>"; static int resolve_directories = 1; @@ -54,8 +55,6 @@ static const char *explanation(struct merge_list *entry) return "removed in remote"; } -extern void *merge_file(const char *, struct blob *, struct blob *, struct blob *, unsigned long *); - static void *result(struct merge_list *entry, unsigned long *size) { enum object_type type; diff --git a/builtin/merge.c b/builtin/merge.c index 10f091b519..dd50a0c57b 100644 --- a/builtin/merge.c +++ b/builtin/merge.c @@ -25,6 +25,9 @@ #include "help.h" #include "merge-recursive.h" #include "resolve-undo.h" +#include "remote.h" +#include "fmt-merge-msg.h" +#include "gpg-interface.h" #define DEFAULT_TWOHEAD (1<<0) #define DEFAULT_OCTOPUS (1<<1) @@ -37,26 +40,31 @@ struct strategy { }; static const char * const builtin_merge_usage[] = { - "git merge [options] <remote>...", - "git merge [options] <msg> HEAD <remote>", + "git merge [options] [<commit>...]", + "git merge [options] <msg> HEAD <commit>", + "git merge --abort", NULL }; -static int show_diffstat = 1, shortlog_len, squash; +static int show_diffstat = 1, shortlog_len = -1, squash; static int option_commit = 1, allow_fast_forward = 1; -static int fast_forward_only; +static int fast_forward_only, option_edit = -1; static int allow_trivial = 1, have_message; -static struct strbuf merge_msg; -static struct commit_list *remoteheads; -static unsigned char head[20], stash[20]; +static int overwrite_ignore = 1; +static struct strbuf merge_msg = STRBUF_INIT; static struct strategy **use_strategies; static size_t use_strategies_nr, use_strategies_alloc; static const char **xopts; static size_t xopts_nr, xopts_alloc; static const char *branch; +static char *branch_mergeoptions; static int option_renormalize; static int verbosity; static int allow_rerere_auto; +static int abort_current_merge; +static int show_progress = -1; +static int default_to_upstream; +static const char *sign_commit; static struct strategy all_strategy[] = { { "recursive", DEFAULT_TWOHEAD | NO_TRIVIAL }, @@ -79,7 +87,7 @@ static int option_parse_message(const struct option *opt, strbuf_addf(buf, "%s%s", buf->len ? "\n\n" : "", arg); have_message = 1; } else - return error("switch `m' requires a value"); + return error(_("switch `m' requires a value")); return 0; } @@ -116,13 +124,13 @@ static struct strategy *get_strategy(const char *name) exclude_cmds(&main_cmds, ¬_strategies); } if (!is_in_cmdlist(&main_cmds, name) && !is_in_cmdlist(&other_cmds, name)) { - fprintf(stderr, "Could not find merge strategy '%s'.\n", name); - fprintf(stderr, "Available strategies are:"); + fprintf(stderr, _("Could not find merge strategy '%s'.\n"), name); + fprintf(stderr, _("Available strategies are:")); for (i = 0; i < main_cmds.cnt; i++) fprintf(stderr, " %s", main_cmds.names[i]->name); fprintf(stderr, ".\n"); if (other_cmds.cnt) { - fprintf(stderr, "Available custom strategies are:"); + fprintf(stderr, _("Available custom strategies are:")); for (i = 0; i < other_cmds.cnt; i++) fprintf(stderr, " %s", other_cmds.names[i]->name); fprintf(stderr, ".\n"); @@ -184,6 +192,8 @@ static struct option builtin_merge_options[] = { "create a single commit instead of doing a merge"), OPT_BOOLEAN(0, "commit", &option_commit, "perform a commit if the merge succeeds (default)"), + OPT_BOOL('e', "edit", &option_edit, + "edit message before committing"), OPT_BOOLEAN(0, "ff", &allow_fast_forward, "allow fast-forward (default)"), OPT_BOOLEAN(0, "ff-only", &fast_forward_only, @@ -194,9 +204,15 @@ static struct option builtin_merge_options[] = { OPT_CALLBACK('X', "strategy-option", &xopts, "option=value", "option for selected merge strategy", option_parse_x), OPT_CALLBACK('m', "message", &merge_msg, "message", - "message to be used for the merge commit (if any)", + "merge commit message (for a non-fast-forward merge)", option_parse_message), OPT__VERBOSITY(&verbosity), + OPT_BOOLEAN(0, "abort", &abort_current_merge, + "abort the current in-progress merge"), + OPT_SET_INT(0, "progress", &show_progress, "force progress reporting", 1), + { OPTION_STRING, 'S', "gpg-sign", &sign_commit, "key id", + "GPG sign commit", PARSE_OPT_OPTARG, NULL, (intptr_t) "" }, + OPT_BOOLEAN(0, "overwrite-ignore", &overwrite_ignore, "update ignored files (default)"), OPT_END() }; @@ -208,7 +224,7 @@ static void drop_save(void) unlink(git_path("MERGE_MODE")); } -static void save_state(void) +static int save_state(unsigned char *stash) { int len; struct child_process cp; @@ -221,17 +237,36 @@ static void save_state(void) cp.git_cmd = 1; if (start_command(&cp)) - die("could not run stash."); + die(_("could not run stash.")); len = strbuf_read(&buffer, cp.out, 1024); close(cp.out); if (finish_command(&cp) || len < 0) - die("stash failed"); - else if (!len) - return; + die(_("stash failed")); + else if (!len) /* no changes */ + return -1; strbuf_setlen(&buffer, buffer.len-1); if (get_sha1(buffer.buf, stash)) - die("not a valid object: %s", buffer.buf); + die(_("not a valid object: %s"), buffer.buf); + return 0; +} + +static void read_empty(unsigned const char *sha1, int verbose) +{ + int i = 0; + const char *args[7]; + + args[i++] = "read-tree"; + if (verbose) + args[i++] = "-v"; + args[i++] = "-m"; + args[i++] = "-u"; + args[i++] = EMPTY_TREE_SHA1_HEX; + args[i++] = sha1_to_hex(sha1); + args[i] = NULL; + + if (run_command_v_opt(args, RUN_GIT_CMD)) + die(_("read-tree failed")); } static void reset_hard(unsigned const char *sha1, int verbose) @@ -248,10 +283,11 @@ static void reset_hard(unsigned const char *sha1, int verbose) args[i] = NULL; if (run_command_v_opt(args, RUN_GIT_CMD)) - die("read-tree failed"); + die(_("read-tree failed")); } -static void restore_state(void) +static void restore_state(const unsigned char *head, + const unsigned char *stash) { struct strbuf sb = STRBUF_INIT; const char *args[] = { "stash", "apply", NULL, NULL }; @@ -277,29 +313,29 @@ static void restore_state(void) static void finish_up_to_date(const char *msg) { if (verbosity >= 0) - printf("%s%s\n", squash ? " (nothing to squash)" : "", msg); + printf("%s%s\n", squash ? _(" (nothing to squash)") : "", msg); drop_save(); } -static void squash_message(void) +static void squash_message(struct commit *commit, struct commit_list *remoteheads) { struct rev_info rev; - struct commit *commit; struct strbuf out = STRBUF_INIT; struct commit_list *j; + const char *filename; int fd; struct pretty_print_context ctx = {0}; - printf("Squash commit -- not updating HEAD\n"); - fd = open(git_path("SQUASH_MSG"), O_WRONLY | O_CREAT, 0666); + printf(_("Squash commit -- not updating HEAD\n")); + filename = git_path("SQUASH_MSG"); + fd = open(filename, O_WRONLY | O_CREAT, 0666); if (fd < 0) - die_errno("Could not write to '%s'", git_path("SQUASH_MSG")); + die_errno(_("Could not write to '%s'"), filename); init_revisions(&rev, NULL); rev.ignore_merges = 1; rev.commit_format = CMIT_FMT_MEDIUM; - commit = lookup_commit(head); commit->object.flags |= UNINTERESTING; add_pending_object(&rev, &commit->object, NULL); @@ -308,28 +344,32 @@ static void squash_message(void) setup_revisions(0, NULL, &rev, NULL); if (prepare_revision_walk(&rev)) - die("revision walk setup failed"); + die(_("revision walk setup failed")); ctx.abbrev = rev.abbrev; ctx.date_mode = rev.date_mode; + ctx.fmt = rev.commit_format; strbuf_addstr(&out, "Squashed commit of the following:\n"); while ((commit = get_revision(&rev)) != NULL) { strbuf_addch(&out, '\n'); strbuf_addf(&out, "commit %s\n", sha1_to_hex(commit->object.sha1)); - pretty_print_commit(rev.commit_format, commit, &out, &ctx); + pretty_print_commit(&ctx, commit, &out); } if (write(fd, out.buf, out.len) < 0) - die_errno("Writing SQUASH_MSG"); + die_errno(_("Writing SQUASH_MSG")); if (close(fd)) - die_errno("Finishing SQUASH_MSG"); + die_errno(_("Finishing SQUASH_MSG")); strbuf_release(&out); } -static void finish(const unsigned char *new_head, const char *msg) +static void finish(struct commit *head_commit, + struct commit_list *remoteheads, + const unsigned char *new_head, const char *msg) { struct strbuf reflog_message = STRBUF_INIT; + const unsigned char *head = head_commit->object.sha1; if (!msg) strbuf_addstr(&reflog_message, getenv("GIT_REFLOG_ACTION")); @@ -340,10 +380,10 @@ static void finish(const unsigned char *new_head, const char *msg) getenv("GIT_REFLOG_ACTION"), msg); } if (squash) { - squash_message(); + squash_message(head_commit, remoteheads); } else { if (verbosity >= 0 && !merge_msg.len) - printf("No merge message -- not updating HEAD\n"); + printf(_("No merge message -- not updating HEAD\n")); else { const char *argv_gc_auto[] = { "gc", "--auto", NULL }; update_ref(reflog_message.buf, "HEAD", @@ -359,13 +399,13 @@ static void finish(const unsigned char *new_head, const char *msg) if (new_head && show_diffstat) { struct diff_options opts; diff_setup(&opts); + opts.stat_width = -1; /* use full terminal width */ + opts.stat_graph_width = -1; /* respect statGraphWidth config */ opts.output_format |= DIFF_FORMAT_SUMMARY | DIFF_FORMAT_DIFFSTAT; opts.detect_rename = DIFF_DETECT_RENAME; - if (diff_use_color_default > 0) - DIFF_OPT_SET(&opts, COLOR_DIFF); if (diff_setup_done(&opts) < 0) - die("diff_setup_done failed"); + die(_("diff_setup_done failed")); diff_tree_sha1(head, new_head, "", &opts); diffcore_std(&opts); diff_flush(&opts); @@ -380,8 +420,8 @@ static void finish(const unsigned char *new_head, const char *msg) /* Get the name for the merge commit's message. */ static void merge_name(const char *remote, struct strbuf *msg) { - struct object *remote_head; - unsigned char branch_head[20], buf_sha[20]; + struct commit *remote_head; + unsigned char branch_head[20]; struct strbuf buf = STRBUF_INIT; struct strbuf bname = STRBUF_INIT; const char *ptr; @@ -392,9 +432,9 @@ static void merge_name(const char *remote, struct strbuf *msg) remote = bname.buf; memset(branch_head, 0, sizeof(branch_head)); - remote_head = peel_to_type(remote, 0, NULL, OBJ_COMMIT); + remote_head = get_merge_parent(remote); if (!remote_head) - die("'%s' does not point to a commit", remote); + die(_("'%s' does not point to a commit"), remote); if (dwim_ref(remote, strlen(remote), branch_head, &found_ref) > 0) { if (!prefixcmp(found_ref, "refs/heads/")) { @@ -402,8 +442,13 @@ static void merge_name(const char *remote, struct strbuf *msg) sha1_to_hex(branch_head), remote); goto cleanup; } + if (!prefixcmp(found_ref, "refs/tags/")) { + strbuf_addf(msg, "%s\t\ttag '%s' of .\n", + sha1_to_hex(branch_head), remote); + goto cleanup; + } if (!prefixcmp(found_ref, "refs/remotes/")) { - strbuf_addf(msg, "%s\t\tremote branch '%s' of .\n", + strbuf_addf(msg, "%s\t\tremote-tracking branch '%s' of .\n", sha1_to_hex(branch_head), remote); goto cleanup; } @@ -440,10 +485,10 @@ static void merge_name(const char *remote, struct strbuf *msg) strbuf_addstr(&truname, "refs/heads/"); strbuf_addstr(&truname, remote); strbuf_setlen(&truname, truname.len - len); - if (resolve_ref(truname.buf, buf_sha, 1, NULL)) { + if (ref_exists(truname.buf)) { strbuf_addf(msg, "%s\t\tbranch '%s'%s of .\n", - sha1_to_hex(remote_head->sha1), + sha1_to_hex(remote_head->object.sha1), truname.buf + 11, (early ? " (early part)" : "")); strbuf_release(&truname); @@ -453,14 +498,16 @@ static void merge_name(const char *remote, struct strbuf *msg) if (!strcmp(remote, "FETCH_HEAD") && !access(git_path("FETCH_HEAD"), R_OK)) { + const char *filename; FILE *fp; struct strbuf line = STRBUF_INIT; char *ptr; - fp = fopen(git_path("FETCH_HEAD"), "r"); + filename = git_path("FETCH_HEAD"); + fp = fopen(filename, "r"); if (!fp) - die_errno("could not open '%s' for reading", - git_path("FETCH_HEAD")); + die_errno(_("could not open '%s' for reading"), + filename); strbuf_getline(&line, fp, '\n'); fclose(fp); ptr = strstr(line.buf, "\tnot-for-merge\t"); @@ -471,32 +518,42 @@ static void merge_name(const char *remote, struct strbuf *msg) goto cleanup; } strbuf_addf(msg, "%s\t\tcommit '%s'\n", - sha1_to_hex(remote_head->sha1), remote); + sha1_to_hex(remote_head->object.sha1), remote); cleanup: strbuf_release(&buf); strbuf_release(&bname); } +static void parse_branch_merge_options(char *bmo) +{ + const char **argv; + int argc; + + if (!bmo) + return; + argc = split_cmdline(bmo, &argv); + if (argc < 0) + die(_("Bad branch.%s.mergeoptions string: %s"), branch, + split_cmdline_strerror(argc)); + argv = xrealloc(argv, sizeof(*argv) * (argc + 2)); + memmove(argv + 1, argv, sizeof(*argv) * (argc + 1)); + argc++; + argv[0] = "branch.*.mergeoptions"; + parse_options(argc, argv, NULL, builtin_merge_options, + builtin_merge_usage, 0); + free(argv); +} + static int git_merge_config(const char *k, const char *v, void *cb) { + int status; + if (branch && !prefixcmp(k, "branch.") && !prefixcmp(k + 7, branch) && !strcmp(k + 7 + strlen(branch), ".mergeoptions")) { - const char **argv; - int argc; - char *buf; - - buf = xstrdup(v); - argc = split_cmdline(buf, &argv); - if (argc < 0) - die("Bad branch.%s.mergeoptions string: %s", branch, - split_cmdline_strerror(argc)); - argv = xrealloc(argv, sizeof(*argv) * (argc + 2)); - memmove(argv + 1, argv, sizeof(*argv) * (argc + 1)); - argc++; - parse_options(argc, argv, NULL, builtin_merge_options, - builtin_merge_usage, 0); - free(buf); + free(branch_mergeoptions); + branch_mergeoptions = xstrdup(v); + return 0; } if (!strcmp(k, "merge.diffstat") || !strcmp(k, "merge.stat")) @@ -507,15 +564,26 @@ static int git_merge_config(const char *k, const char *v, void *cb) return git_config_string(&pull_octopus, k, v); else if (!strcmp(k, "merge.renormalize")) option_renormalize = git_config_bool(k, v); - else if (!strcmp(k, "merge.log") || !strcmp(k, "merge.summary")) { - int is_bool; - shortlog_len = git_config_bool_or_int(k, v, &is_bool); - if (!is_bool && shortlog_len < 0) - return error("%s: negative length %s", k, v); - if (is_bool && shortlog_len) - shortlog_len = DEFAULT_MERGE_LOG_LEN; + else if (!strcmp(k, "merge.ff")) { + int boolval = git_config_maybe_bool(k, v); + if (0 <= boolval) { + allow_fast_forward = boolval; + } else if (v && !strcmp(v, "only")) { + allow_fast_forward = 1; + fast_forward_only = 1; + } /* do not barf on values from future versions of git */ + return 0; + } else if (!strcmp(k, "merge.defaulttoupstream")) { + default_to_upstream = git_config_bool(k, v); return 0; } + + status = fmt_merge_msg_config(k, v, cb); + if (status) + return status; + status = git_gpg_config(k, v, NULL); + if (status) + return status; return git_diff_ui_config(k, v, cb); } @@ -558,10 +626,19 @@ static int read_tree_trivial(unsigned char *common, unsigned char *head, static void write_tree_trivial(unsigned char *sha1) { if (write_cache_as_tree(sha1, 0, NULL)) - die("git write-tree failed to write a tree"); + die(_("git write-tree failed to write a tree")); +} + +static const char *merge_argument(struct commit *commit) +{ + if (commit) + return sha1_to_hex(commit->object.sha1); + else + return EMPTY_TREE_SHA1_HEX; } -int try_merge_command(const char *strategy, struct commit_list *common, +int try_merge_command(const char *strategy, size_t xopts_nr, + const char **xopts, struct commit_list *common, const char *head_arg, struct commit_list *remotes) { const char **args; @@ -580,11 +657,11 @@ int try_merge_command(const char *strategy, struct commit_list *common, args[i++] = s; } for (j = common; j; j = j->next) - args[i++] = xstrdup(sha1_to_hex(j->item->object.sha1)); + args[i++] = xstrdup(merge_argument(j->item)); args[i++] = "--"; args[i++] = head_arg; for (j = remotes; j; j = j->next) - args[i++] = xstrdup(sha1_to_hex(j->item->object.sha1)); + args[i++] = xstrdup(merge_argument(j->item)); args[i] = NULL; ret = run_command_v_opt(args, RUN_GIT_CMD); strbuf_release(&buf); @@ -599,14 +676,15 @@ int try_merge_command(const char *strategy, struct commit_list *common, free(args); discard_cache(); if (read_cache() < 0) - die("failed to read the cache"); + die(_("failed to read the cache")); resolve_undo_clear(); return ret; } static int try_merge_strategy(const char *strategy, struct commit_list *common, - const char *head_arg) + struct commit_list *remoteheads, + struct commit *head, const char *head_arg) { int index_fd; struct lock_file *lock = xcalloc(1, sizeof(struct lock_file)); @@ -616,7 +694,7 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common, if (active_cache_changed && (write_cache(index_fd, active_cache, active_nr) || commit_locked_index(lock))) - return error("Unable to write index."); + return error(_("Unable to write index.")); rollback_lock_file(lock); if (!strcmp(strategy, "recursive") || !strcmp(strategy, "subtree")) { @@ -629,7 +707,7 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common, struct commit_list *j; if (remoteheads->next) { - error("Not handling anything other than two heads merge."); + error(_("Not handling anything other than two heads merge.")); return 2; } @@ -638,28 +716,31 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common, o.subtree_shift = ""; o.renormalize = option_renormalize; + o.show_rename_progress = + show_progress == -1 ? isatty(2) : show_progress; for (x = 0; x < xopts_nr; x++) if (parse_merge_opt(&o, xopts[x])) - die("Unknown option for merge-recursive: -X%s", xopts[x]); + die(_("Unknown option for merge-recursive: -X%s"), xopts[x]); o.branch1 = head_arg; - o.branch2 = remoteheads->item->util; + o.branch2 = merge_remote_util(remoteheads->item)->name; for (j = common; j; j = j->next) commit_list_insert(j->item, &reversed); index_fd = hold_locked_index(lock, 1); - clean = merge_recursive(&o, lookup_commit(head), + clean = merge_recursive(&o, head, remoteheads->item, reversed, &result); if (active_cache_changed && (write_cache(index_fd, active_cache, active_nr) || commit_locked_index(lock))) - die ("unable to write %s", get_index_file()); + die (_("unable to write %s"), get_index_file()); rollback_lock_file(lock); return clean ? 0 : 1; } else { - return try_merge_command(strategy, common, head_arg, remoteheads); + return try_merge_command(strategy, xopts_nr, xopts, + common, head_arg, remoteheads); } } @@ -698,10 +779,12 @@ int checkout_fast_forward(const unsigned char *head, const unsigned char *remote memset(&trees, 0, sizeof(trees)); memset(&opts, 0, sizeof(opts)); memset(&t, 0, sizeof(t)); - memset(&dir, 0, sizeof(dir)); - dir.flags |= DIR_SHOW_IGNORED; - dir.exclude_per_dir = ".gitignore"; - opts.dir = &dir; + if (overwrite_ignore) { + memset(&dir, 0, sizeof(dir)); + dir.flags |= DIR_SHOW_IGNORED; + setup_standard_excludes(&dir); + opts.dir = &dir; + } opts.head_idx = 1; opts.src_index = &the_index; @@ -726,7 +809,7 @@ int checkout_fast_forward(const unsigned char *head, const unsigned char *remote return -1; if (write_cache(fd, active_cache, active_nr) || commit_locked_index(lock_file)) - die("unable to write new index file"); + die(_("unable to write new index file")); return 0; } @@ -774,49 +857,110 @@ static void add_strategies(const char *string, unsigned attr) } -static int merge_trivial(void) +static void write_merge_msg(struct strbuf *msg) +{ + const char *filename = git_path("MERGE_MSG"); + int fd = open(filename, O_WRONLY | O_CREAT, 0666); + if (fd < 0) + die_errno(_("Could not open '%s' for writing"), + filename); + if (write_in_full(fd, msg->buf, msg->len) != msg->len) + die_errno(_("Could not write to '%s'"), filename); + close(fd); +} + +static void read_merge_msg(struct strbuf *msg) +{ + const char *filename = git_path("MERGE_MSG"); + strbuf_reset(msg); + if (strbuf_read_file(msg, filename, 0) < 0) + die_errno(_("Could not read from '%s'"), filename); +} + +static void write_merge_state(struct commit_list *); +static void abort_commit(struct commit_list *remoteheads, const char *err_msg) +{ + if (err_msg) + error("%s", err_msg); + fprintf(stderr, + _("Not committing merge; use 'git commit' to complete the merge.\n")); + write_merge_state(remoteheads); + exit(1); +} + +static const char merge_editor_comment[] = +N_("Please enter a commit message to explain why this merge is necessary,\n" + "especially if it merges an updated upstream into a topic branch.\n" + "\n" + "Lines starting with '#' will be ignored, and an empty message aborts\n" + "the commit.\n"); + +static void prepare_to_commit(struct commit_list *remoteheads) +{ + struct strbuf msg = STRBUF_INIT; + const char *comment = _(merge_editor_comment); + strbuf_addbuf(&msg, &merge_msg); + strbuf_addch(&msg, '\n'); + if (0 < option_edit) + strbuf_add_lines(&msg, "# ", comment, strlen(comment)); + write_merge_msg(&msg); + run_hook(get_index_file(), "prepare-commit-msg", + git_path("MERGE_MSG"), "merge", NULL, NULL); + if (0 < option_edit) { + if (launch_editor(git_path("MERGE_MSG"), NULL, NULL)) + abort_commit(remoteheads, NULL); + } + read_merge_msg(&msg); + stripspace(&msg, 0 < option_edit); + if (!msg.len) + abort_commit(remoteheads, _("Empty commit message.")); + strbuf_release(&merge_msg); + strbuf_addbuf(&merge_msg, &msg); + strbuf_release(&msg); +} + +static int merge_trivial(struct commit *head, struct commit_list *remoteheads) { unsigned char result_tree[20], result_commit[20]; struct commit_list *parent = xmalloc(sizeof(*parent)); write_tree_trivial(result_tree); - printf("Wonderful.\n"); - parent->item = lookup_commit(head); + printf(_("Wonderful.\n")); + parent->item = head; parent->next = xmalloc(sizeof(*parent->next)); parent->next->item = remoteheads->item; parent->next->next = NULL; - commit_tree(merge_msg.buf, result_tree, parent, result_commit, NULL); - finish(result_commit, "In-index merge"); + prepare_to_commit(remoteheads); + if (commit_tree(&merge_msg, result_tree, parent, result_commit, NULL, + sign_commit)) + die(_("failed to write commit object")); + finish(head, remoteheads, result_commit, "In-index merge"); drop_save(); return 0; } -static int finish_automerge(struct commit_list *common, +static int finish_automerge(struct commit *head, + int head_subsumed, + struct commit_list *common, + struct commit_list *remoteheads, unsigned char *result_tree, const char *wt_strategy) { - struct commit_list *parents = NULL, *j; + struct commit_list *parents = NULL; struct strbuf buf = STRBUF_INIT; unsigned char result_commit[20]; free_commit_list(common); - if (allow_fast_forward) { - parents = remoteheads; - commit_list_insert(lookup_commit(head), &parents); - parents = reduce_heads(parents); - } else { - struct commit_list **pptr = &parents; - - pptr = &commit_list_insert(lookup_commit(head), - pptr)->next; - for (j = remoteheads; j; j = j->next) - pptr = &commit_list_insert(j->item, pptr)->next; - } - free_commit_list(remoteheads); + parents = remoteheads; + if (!head_subsumed || !allow_fast_forward) + commit_list_insert(head, &parents); strbuf_addch(&merge_msg, '\n'); - commit_tree(merge_msg.buf, result_tree, parents, result_commit, NULL); - strbuf_addf(&buf, "Merge made by %s.", wt_strategy); - finish(result_commit, buf.buf); + prepare_to_commit(remoteheads); + if (commit_tree(&merge_msg, result_tree, parents, result_commit, + NULL, sign_commit)) + die(_("failed to write commit object")); + strbuf_addf(&buf, "Merge made by the '%s' strategy.", wt_strategy); + finish(head, remoteheads, result_commit, buf.buf); strbuf_release(&buf); drop_save(); return 0; @@ -824,13 +968,14 @@ static int finish_automerge(struct commit_list *common, static int suggest_conflicts(int renormalizing) { + const char *filename; FILE *fp; int pos; - fp = fopen(git_path("MERGE_MSG"), "a"); + filename = git_path("MERGE_MSG"); + fp = fopen(filename, "a"); if (!fp) - die_errno("Could not open '%s' for writing", - git_path("MERGE_MSG")); + die_errno(_("Could not open '%s' for writing"), filename); fprintf(fp, "\nConflicts:\n"); for (pos = 0; pos < active_nr; pos++) { struct cache_entry *ce = active_cache[pos]; @@ -845,12 +990,13 @@ static int suggest_conflicts(int renormalizing) } fclose(fp); rerere(allow_rerere_auto); - printf("Automatic merge failed; " - "fix conflicts and then commit the result.\n"); + printf(_("Automatic merge failed; " + "fix conflicts and then commit the result.\n")); return 1; } -static struct commit *is_old_style_invocation(int argc, const char **argv) +static struct commit *is_old_style_invocation(int argc, const char **argv, + const unsigned char *head) { struct commit *second_token = NULL; if (argc > 2) { @@ -860,7 +1006,7 @@ static struct commit *is_old_style_invocation(int argc, const char **argv) return NULL; second_token = lookup_commit_reference_gently(second_sha1, 0); if (!second_token) - die("'%s' is not a commit", argv[1]); + die(_("'%s' is not a commit"), argv[1]); if (hashcmp(second_token->object.sha1, head)) return NULL; } @@ -890,63 +1036,232 @@ static int evaluate_result(void) return cnt; } +/* + * Pretend as if the user told us to merge with the tracking + * branch we have for the upstream of the current branch + */ +static int setup_with_upstream(const char ***argv) +{ + struct branch *branch = branch_get(NULL); + int i; + const char **args; + + if (!branch) + die(_("No current branch.")); + if (!branch->remote) + die(_("No remote for the current branch.")); + if (!branch->merge_nr) + die(_("No default upstream defined for the current branch.")); + + args = xcalloc(branch->merge_nr + 1, sizeof(char *)); + for (i = 0; i < branch->merge_nr; i++) { + if (!branch->merge[i]->dst) + die(_("No remote tracking branch for %s from %s"), + branch->merge[i]->src, branch->remote_name); + args[i] = branch->merge[i]->dst; + } + args[i] = NULL; + *argv = args; + return i; +} + +static void write_merge_state(struct commit_list *remoteheads) +{ + const char *filename; + int fd; + struct commit_list *j; + struct strbuf buf = STRBUF_INIT; + + for (j = remoteheads; j; j = j->next) { + unsigned const char *sha1; + struct commit *c = j->item; + if (c->util && merge_remote_util(c)->obj) { + sha1 = merge_remote_util(c)->obj->sha1; + } else { + sha1 = c->object.sha1; + } + strbuf_addf(&buf, "%s\n", sha1_to_hex(sha1)); + } + filename = git_path("MERGE_HEAD"); + fd = open(filename, O_WRONLY | O_CREAT, 0666); + if (fd < 0) + die_errno(_("Could not open '%s' for writing"), filename); + if (write_in_full(fd, buf.buf, buf.len) != buf.len) + die_errno(_("Could not write to '%s'"), filename); + close(fd); + strbuf_addch(&merge_msg, '\n'); + write_merge_msg(&merge_msg); + + filename = git_path("MERGE_MODE"); + fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0666); + if (fd < 0) + die_errno(_("Could not open '%s' for writing"), filename); + strbuf_reset(&buf); + if (!allow_fast_forward) + strbuf_addf(&buf, "no-ff"); + if (write_in_full(fd, buf.buf, buf.len) != buf.len) + die_errno(_("Could not write to '%s'"), filename); + close(fd); +} + +static int default_edit_option(void) +{ + static const char name[] = "GIT_MERGE_AUTOEDIT"; + const char *e = getenv(name); + struct stat st_stdin, st_stdout; + + if (have_message) + /* an explicit -m msg without --[no-]edit */ + return 0; + + if (e) { + int v = git_config_maybe_bool(name, e); + if (v < 0) + die("Bad value '%s' in environment '%s'", e, name); + return v; + } + + /* Use editor if stdin and stdout are the same and is a tty */ + return (!fstat(0, &st_stdin) && + !fstat(1, &st_stdout) && + isatty(0) && isatty(1) && + st_stdin.st_dev == st_stdout.st_dev && + st_stdin.st_ino == st_stdout.st_ino && + st_stdin.st_mode == st_stdout.st_mode); +} + +static struct commit_list *collect_parents(struct commit *head_commit, + int *head_subsumed, + int argc, const char **argv) +{ + int i; + struct commit_list *remoteheads = NULL, *parents, *next; + struct commit_list **remotes = &remoteheads; + + if (head_commit) + remotes = &commit_list_insert(head_commit, remotes)->next; + for (i = 0; i < argc; i++) { + struct commit *commit = get_merge_parent(argv[i]); + if (!commit) + die(_("%s - not something we can merge"), argv[i]); + remotes = &commit_list_insert(commit, remotes)->next; + } + *remotes = NULL; + + parents = reduce_heads(remoteheads); + + *head_subsumed = 1; /* we will flip this to 0 when we find it */ + for (remoteheads = NULL, remotes = &remoteheads; + parents; + parents = next) { + struct commit *commit = parents->item; + next = parents->next; + if (commit == head_commit) + *head_subsumed = 0; + else + remotes = &commit_list_insert(commit, remotes)->next; + } + return remoteheads; +} + int cmd_merge(int argc, const char **argv, const char *prefix) { unsigned char result_tree[20]; + unsigned char stash[20]; + unsigned char head_sha1[20]; + struct commit *head_commit; struct strbuf buf = STRBUF_INIT; const char *head_arg; - int flag, head_invalid = 0, i; + int flag, i, ret = 0, head_subsumed; int best_cnt = -1, merge_was_ok = 0, automerge_was_ok = 0; struct commit_list *common = NULL; const char *best_strategy = NULL, *wt_strategy = NULL; - struct commit_list **remotes = &remoteheads; + struct commit_list *remoteheads, *p; + void *branch_to_free; - if (read_cache_unmerged()) { - die_resolve_conflict("merge"); - } - if (file_exists(git_path("MERGE_HEAD"))) { - /* - * There is no unmerged entry, don't advise 'git - * add/rm <file>', just 'git commit'. - */ - if (advice_resolve_conflict) - die("You have not concluded your merge (MERGE_HEAD exists).\n" - "Please, commit your changes before you can merge."); - else - die("You have not concluded your merge (MERGE_HEAD exists)."); - } + if (argc == 2 && !strcmp(argv[1], "-h")) + usage_with_options(builtin_merge_usage, builtin_merge_options); - resolve_undo_clear(); /* * Check if we are _not_ on a detached HEAD, i.e. if there is a * current branch. */ - branch = resolve_ref("HEAD", head, 0, &flag); + branch = branch_to_free = resolve_refdup("HEAD", head_sha1, 0, &flag); if (branch && !prefixcmp(branch, "refs/heads/")) branch += 11; - if (is_null_sha1(head)) - head_invalid = 1; + if (!branch || is_null_sha1(head_sha1)) + head_commit = NULL; + else + head_commit = lookup_commit_or_die(head_sha1, "HEAD"); git_config(git_merge_config, NULL); - /* for color.ui */ - if (diff_use_color_default == -1) - diff_use_color_default = git_use_color_default; - + if (branch_mergeoptions) + parse_branch_merge_options(branch_mergeoptions); argc = parse_options(argc, argv, prefix, builtin_merge_options, builtin_merge_usage, 0); + if (shortlog_len < 0) + shortlog_len = (merge_log_config > 0) ? merge_log_config : 0; + + if (verbosity < 0 && show_progress == -1) + show_progress = 0; + + if (abort_current_merge) { + int nargc = 2; + const char *nargv[] = {"reset", "--merge", NULL}; + + if (!file_exists(git_path("MERGE_HEAD"))) + die(_("There is no merge to abort (MERGE_HEAD missing).")); + + /* Invoke 'git reset --merge' */ + ret = cmd_reset(nargc, nargv, prefix); + goto done; + } + + if (read_cache_unmerged()) + die_resolve_conflict("merge"); + + if (file_exists(git_path("MERGE_HEAD"))) { + /* + * There is no unmerged entry, don't advise 'git + * add/rm <file>', just 'git commit'. + */ + if (advice_resolve_conflict) + die(_("You have not concluded your merge (MERGE_HEAD exists).\n" + "Please, commit your changes before you can merge.")); + else + die(_("You have not concluded your merge (MERGE_HEAD exists).")); + } + if (file_exists(git_path("CHERRY_PICK_HEAD"))) { + if (advice_resolve_conflict) + die(_("You have not concluded your cherry-pick (CHERRY_PICK_HEAD exists).\n" + "Please, commit your changes before you can merge.")); + else + die(_("You have not concluded your cherry-pick (CHERRY_PICK_HEAD exists).")); + } + resolve_undo_clear(); + if (verbosity < 0) show_diffstat = 0; if (squash) { if (!allow_fast_forward) - die("You cannot combine --squash with --no-ff."); + die(_("You cannot combine --squash with --no-ff.")); option_commit = 0; } if (!allow_fast_forward && fast_forward_only) - die("You cannot combine --no-ff with --ff-only."); + die(_("You cannot combine --no-ff with --ff-only.")); + if (!abort_current_merge) { + if (!argc) { + if (default_to_upstream) + argc = setup_with_upstream(&argv); + else + die(_("No commit specified and merge.defaultToUpstream not set.")); + } else if (argc == 1 && !strcmp(argv[0], "-")) + argv[0] = "@{-1}"; + } if (!argc) usage_with_options(builtin_merge_usage, builtin_merge_options); @@ -960,33 +1275,36 @@ int cmd_merge(int argc, const char **argv, const char *prefix) * additional safety measure to check for it. */ - if (!have_message && is_old_style_invocation(argc, argv)) { + if (!have_message && head_commit && + is_old_style_invocation(argc, argv, head_commit->object.sha1)) { strbuf_addstr(&merge_msg, argv[0]); head_arg = argv[1]; argv += 2; argc -= 2; - } else if (head_invalid) { - struct object *remote_head; + remoteheads = collect_parents(head_commit, &head_subsumed, argc, argv); + } else if (!head_commit) { + struct commit *remote_head; /* * If the merged head is a valid one there is no reason * to forbid "git merge" into a branch yet to be born. * We do the same for "git pull". */ if (argc != 1) - die("Can merge only exactly one commit into " - "empty head"); + die(_("Can merge only exactly one commit into " + "empty head")); if (squash) - die("Squash commit into empty head not supported yet"); + die(_("Squash commit into empty head not supported yet")); if (!allow_fast_forward) - die("Non-fast-forward commit does not make sense into " - "an empty head"); - remote_head = peel_to_type(argv[0], 0, NULL, OBJ_COMMIT); + die(_("Non-fast-forward commit does not make sense into " + "an empty head")); + remoteheads = collect_parents(head_commit, &head_subsumed, argc, argv); + remote_head = remoteheads->item; if (!remote_head) - die("%s - not something we can merge", argv[0]); - update_ref("initial pull", "HEAD", remote_head->sha1, NULL, 0, - DIE_ON_ERR); - reset_hard(remote_head->sha1, 0); - return 0; + die(_("%s - not something we can merge"), argv[0]); + read_empty(remote_head->object.sha1, 0); + update_ref("initial pull", "HEAD", remote_head->object.sha1, + NULL, 0, DIE_ON_ERR); + goto done; } else { struct strbuf merge_names = STRBUF_INIT; @@ -994,52 +1312,56 @@ int cmd_merge(int argc, const char **argv, const char *prefix) head_arg = "HEAD"; /* - * All the rest are the commits being merged; - * prepare the standard merge summary message to - * be appended to the given message. If remote - * is invalid we will die later in the common - * codepath so we discard the error in this - * loop. + * All the rest are the commits being merged; prepare + * the standard merge summary message to be appended + * to the given message. */ - for (i = 0; i < argc; i++) - merge_name(argv[i], &merge_names); + remoteheads = collect_parents(head_commit, &head_subsumed, argc, argv); + for (p = remoteheads; p; p = p->next) + merge_name(merge_remote_util(p->item)->name, &merge_names); if (!have_message || shortlog_len) { - fmt_merge_msg(&merge_names, &merge_msg, !have_message, - shortlog_len); + struct fmt_merge_msg_opts opts; + memset(&opts, 0, sizeof(opts)); + opts.add_title = !have_message; + opts.shortlog_len = shortlog_len; + + fmt_merge_msg(&merge_names, &merge_msg, &opts); if (merge_msg.len) strbuf_setlen(&merge_msg, merge_msg.len - 1); } } - if (head_invalid || !argc) + if (!head_commit || !argc) usage_with_options(builtin_merge_usage, builtin_merge_options); strbuf_addstr(&buf, "merge"); - for (i = 0; i < argc; i++) - strbuf_addf(&buf, " %s", argv[i]); + for (p = remoteheads; p; p = p->next) + strbuf_addf(&buf, " %s", merge_remote_util(p->item)->name); setenv("GIT_REFLOG_ACTION", buf.buf, 0); strbuf_reset(&buf); - for (i = 0; i < argc; i++) { - struct object *o; - struct commit *commit; - - o = peel_to_type(argv[i], 0, NULL, OBJ_COMMIT); - if (!o) - die("%s - not something we can merge", argv[i]); - commit = lookup_commit(o->sha1); - commit->util = (void *)argv[i]; - remotes = &commit_list_insert(commit, remotes)->next; - - strbuf_addf(&buf, "GITHEAD_%s", sha1_to_hex(o->sha1)); - setenv(buf.buf, argv[i], 1); + for (p = remoteheads; p; p = p->next) { + struct commit *commit = p->item; + strbuf_addf(&buf, "GITHEAD_%s", + sha1_to_hex(commit->object.sha1)); + setenv(buf.buf, merge_remote_util(commit)->name, 1); strbuf_reset(&buf); + if (!fast_forward_only && + merge_remote_util(commit) && + merge_remote_util(commit)->obj && + merge_remote_util(commit)->obj->type == OBJ_TAG) + allow_fast_forward = 0; } + if (option_edit < 0) + option_edit = default_edit_option(); + if (!use_strategies) { - if (!remoteheads->next) + if (!remoteheads) + ; /* already up-to-date */ + else if (!remoteheads->next) add_strategies(pull_twohead, DEFAULT_TWOHEAD); else add_strategies(pull_octopus, DEFAULT_OCTOPUS); @@ -1052,41 +1374,43 @@ int cmd_merge(int argc, const char **argv, const char *prefix) allow_trivial = 0; } - if (!remoteheads->next) - common = get_merge_bases(lookup_commit(head), - remoteheads->item, 1); + if (!remoteheads) + ; /* already up-to-date */ + else if (!remoteheads->next) + common = get_merge_bases(head_commit, remoteheads->item, 1); else { struct commit_list *list = remoteheads; - commit_list_insert(lookup_commit(head), &list); + commit_list_insert(head_commit, &list); common = get_octopus_merge_bases(list); free(list); } - update_ref("updating ORIG_HEAD", "ORIG_HEAD", head, NULL, 0, - DIE_ON_ERR); + update_ref("updating ORIG_HEAD", "ORIG_HEAD", head_commit->object.sha1, + NULL, 0, DIE_ON_ERR); - if (!common) + if (remoteheads && !common) ; /* No common ancestors found. We need a real merge. */ - else if (!remoteheads->next && !common->next && - common->item == remoteheads->item) { + else if (!remoteheads || + (!remoteheads->next && !common->next && + common->item == remoteheads->item)) { /* * If head can reach all the merge then we are up to date. * but first the most common case of merging one remote. */ finish_up_to_date("Already up-to-date."); - return 0; + goto done; } else if (allow_fast_forward && !remoteheads->next && !common->next && - !hashcmp(common->item->object.sha1, head)) { + !hashcmp(common->item->object.sha1, head_commit->object.sha1)) { /* Again the most common case of merging one remote. */ struct strbuf msg = STRBUF_INIT; - struct object *o; + struct commit *commit; char hex[41]; - strcpy(hex, find_unique_abbrev(head, DEFAULT_ABBREV)); + strcpy(hex, find_unique_abbrev(head_commit->object.sha1, DEFAULT_ABBREV)); if (verbosity >= 0) - printf("Updating %s..%s\n", + printf(_("Updating %s..%s\n"), hex, find_unique_abbrev(remoteheads->item->object.sha1, DEFAULT_ABBREV)); @@ -1094,17 +1418,21 @@ int cmd_merge(int argc, const char **argv, const char *prefix) if (have_message) strbuf_addstr(&msg, " (no commit created; -m option ignored)"); - o = peel_to_type(sha1_to_hex(remoteheads->item->object.sha1), - 0, NULL, OBJ_COMMIT); - if (!o) - return 1; + commit = remoteheads->item; + if (!commit) { + ret = 1; + goto done; + } - if (checkout_fast_forward(head, remoteheads->item->object.sha1)) - return 1; + if (checkout_fast_forward(head_commit->object.sha1, + commit->object.sha1)) { + ret = 1; + goto done; + } - finish(o->sha1, msg.buf); + finish(head_commit, remoteheads, commit->object.sha1, msg.buf); drop_save(); - return 0; + goto done; } else if (!remoteheads->next && common->next) ; /* @@ -1119,12 +1447,15 @@ int cmd_merge(int argc, const char **argv, const char *prefix) refresh_cache(REFRESH_QUIET); if (allow_trivial && !fast_forward_only) { /* See if it is really trivial. */ - git_committer_info(IDENT_ERROR_ON_NO_NAME); - printf("Trying really trivial in-index merge...\n"); + git_committer_info(IDENT_STRICT); + printf(_("Trying really trivial in-index merge...\n")); if (!read_tree_trivial(common->item->object.sha1, - head, remoteheads->item->object.sha1)) - return merge_trivial(); - printf("Nope.\n"); + head_commit->object.sha1, + remoteheads->item->object.sha1)) { + ret = merge_trivial(head_commit, remoteheads); + goto done; + } + printf(_("Nope.\n")); } } else { /* @@ -1142,8 +1473,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) * merge_bases again, otherwise "git merge HEAD^ * HEAD^^" would be missed. */ - common_one = get_merge_bases(lookup_commit(head), - j->item, 1); + common_one = get_merge_bases(head_commit, j->item, 1); if (hashcmp(common_one->item->object.sha1, j->item->object.sha1)) { up_to_date = 0; @@ -1152,15 +1482,15 @@ int cmd_merge(int argc, const char **argv, const char *prefix) } if (up_to_date) { finish_up_to_date("Already up-to-date. Yeeah!"); - return 0; + goto done; } } if (fast_forward_only) - die("Not possible to fast-forward, aborting."); + die(_("Not possible to fast-forward, aborting.")); /* We are going to make a new commit. */ - git_committer_info(IDENT_ERROR_ON_NO_NAME); + git_committer_info(IDENT_STRICT); /* * At this point, we need a real merge. No matter what strategy @@ -1170,24 +1500,21 @@ int cmd_merge(int argc, const char **argv, const char *prefix) * sync with the head commit. The strategies are responsible * to ensure this. */ - if (use_strategies_nr != 1) { - /* - * Stash away the local changes so that we can try more - * than one. - */ - save_state(); - } else { - memcpy(stash, null_sha1, 20); - } + if (use_strategies_nr == 1 || + /* + * Stash away the local changes so that we can try more than one. + */ + save_state(stash)) + hashcpy(stash, null_sha1); for (i = 0; i < use_strategies_nr; i++) { int ret; if (i) { - printf("Rewinding the tree to pristine...\n"); - restore_state(); + printf(_("Rewinding the tree to pristine...\n")); + restore_state(head_commit->object.sha1, stash); } if (use_strategies_nr != 1) - printf("Trying merge strategy %s...\n", + printf(_("Trying merge strategy %s...\n"), use_strategies[i]->name); /* * Remember which strategy left the state in the working @@ -1196,7 +1523,8 @@ int cmd_merge(int argc, const char **argv, const char *prefix) wt_strategy = use_strategies[i]->name; ret = try_merge_strategy(use_strategies[i]->name, - common, head_arg); + common, remoteheads, + head_commit, head_arg); if (!option_commit && !ret) { merge_was_ok = 1; /* @@ -1237,73 +1565,50 @@ int cmd_merge(int argc, const char **argv, const char *prefix) * If we have a resulting tree, that means the strategy module * auto resolved the merge cleanly. */ - if (automerge_was_ok) - return finish_automerge(common, result_tree, wt_strategy); + if (automerge_was_ok) { + ret = finish_automerge(head_commit, head_subsumed, + common, remoteheads, + result_tree, wt_strategy); + goto done; + } /* * Pick the result from the best strategy and have the user fix * it up. */ if (!best_strategy) { - restore_state(); + restore_state(head_commit->object.sha1, stash); if (use_strategies_nr > 1) fprintf(stderr, - "No merge strategy handled the merge.\n"); + _("No merge strategy handled the merge.\n")); else - fprintf(stderr, "Merge with strategy %s failed.\n", + fprintf(stderr, _("Merge with strategy %s failed.\n"), use_strategies[0]->name); - return 2; + ret = 2; + goto done; } else if (best_strategy == wt_strategy) ; /* We already have its result in the working tree. */ else { - printf("Rewinding the tree to pristine...\n"); - restore_state(); - printf("Using the %s to prepare resolving by hand.\n", + printf(_("Rewinding the tree to pristine...\n")); + restore_state(head_commit->object.sha1, stash); + printf(_("Using the %s to prepare resolving by hand.\n"), best_strategy); - try_merge_strategy(best_strategy, common, head_arg); + try_merge_strategy(best_strategy, common, remoteheads, + head_commit, head_arg); } if (squash) - finish(NULL, NULL); - else { - int fd; - struct commit_list *j; - - for (j = remoteheads; j; j = j->next) - strbuf_addf(&buf, "%s\n", - sha1_to_hex(j->item->object.sha1)); - fd = open(git_path("MERGE_HEAD"), O_WRONLY | O_CREAT, 0666); - if (fd < 0) - die_errno("Could not open '%s' for writing", - git_path("MERGE_HEAD")); - if (write_in_full(fd, buf.buf, buf.len) != buf.len) - die_errno("Could not write to '%s'", git_path("MERGE_HEAD")); - close(fd); - strbuf_addch(&merge_msg, '\n'); - fd = open(git_path("MERGE_MSG"), O_WRONLY | O_CREAT, 0666); - if (fd < 0) - die_errno("Could not open '%s' for writing", - git_path("MERGE_MSG")); - if (write_in_full(fd, merge_msg.buf, merge_msg.len) != - merge_msg.len) - die_errno("Could not write to '%s'", git_path("MERGE_MSG")); - close(fd); - fd = open(git_path("MERGE_MODE"), O_WRONLY | O_CREAT | O_TRUNC, 0666); - if (fd < 0) - die_errno("Could not open '%s' for writing", - git_path("MERGE_MODE")); - strbuf_reset(&buf); - if (!allow_fast_forward) - strbuf_addf(&buf, "no-ff"); - if (write_in_full(fd, buf.buf, buf.len) != buf.len) - die_errno("Could not write to '%s'", git_path("MERGE_MODE")); - close(fd); - } - - if (merge_was_ok) { - fprintf(stderr, "Automatic merge went well; " - "stopped before committing as requested\n"); - return 0; - } else - return suggest_conflicts(option_renormalize); + finish(head_commit, remoteheads, NULL, NULL); + else + write_merge_state(remoteheads); + + if (merge_was_ok) + fprintf(stderr, _("Automatic merge went well; " + "stopped before committing as requested\n")); + else + ret = suggest_conflicts(option_renormalize); + +done: + free(branch_to_free); + return ret; } diff --git a/builtin/mktag.c b/builtin/mktag.c index 1cb0f3f2a7..640ab64f41 100644 --- a/builtin/mktag.c +++ b/builtin/mktag.c @@ -1,6 +1,5 @@ -#include "cache.h" +#include "builtin.h" #include "tag.h" -#include "exec_cmd.h" /* * A signature file has a very simple fixed format: four lines @@ -24,8 +23,8 @@ static int verify_object(const unsigned char *sha1, const char *expected_type) int ret = -1; enum object_type type; unsigned long size; - const unsigned char *repl; - void *buffer = read_sha1_file_repl(sha1, &type, &size, &repl); + void *buffer = read_sha1_file(sha1, &type, &size); + const unsigned char *repl = lookup_replace_object(sha1); if (buffer) { if (type == type_from_string(expected_type)) @@ -35,12 +34,6 @@ static int verify_object(const unsigned char *sha1, const char *expected_type) return ret; } -#ifdef NO_C99_FORMAT -#define PD_FMT "%d" -#else -#define PD_FMT "%td" -#endif - static int verify_tag(char *buffer, unsigned long size) { int typelen; @@ -70,15 +63,18 @@ static int verify_tag(char *buffer, unsigned long size) /* Verify tag-line */ tag_line = strchr(type_line, '\n'); if (!tag_line) - return error("char" PD_FMT ": could not find next \"\\n\"", type_line - buffer); + return error("char%"PRIuMAX": could not find next \"\\n\"", + (uintmax_t) (type_line - buffer)); tag_line++; if (memcmp(tag_line, "tag ", 4) || tag_line[4] == '\n') - return error("char" PD_FMT ": no \"tag \" found", tag_line - buffer); + return error("char%"PRIuMAX": no \"tag \" found", + (uintmax_t) (tag_line - buffer)); /* Get the actual type */ typelen = tag_line - type_line - strlen("type \n"); if (typelen >= sizeof(type)) - return error("char" PD_FMT ": type too long", type_line+5 - buffer); + return error("char%"PRIuMAX": type too long", + (uintmax_t) (type_line+5 - buffer)); memcpy(type, type_line+5, typelen); type[typelen] = 0; @@ -95,15 +91,16 @@ static int verify_tag(char *buffer, unsigned long size) break; if (c > ' ') continue; - return error("char" PD_FMT ": could not verify tag name", tag_line - buffer); + return error("char%"PRIuMAX": could not verify tag name", + (uintmax_t) (tag_line - buffer)); } /* Verify the tagger line */ tagger_line = tag_line; if (memcmp(tagger_line, "tagger ", 7)) - return error("char" PD_FMT ": could not find \"tagger \"", - tagger_line - buffer); + return error("char%"PRIuMAX": could not find \"tagger \"", + (uintmax_t) (tagger_line - buffer)); /* * Check for correct form for name and email @@ -115,44 +112,42 @@ static int verify_tag(char *buffer, unsigned long size) if (!(lb = strstr(tagger_line, " <")) || !(rb = strstr(lb+2, "> ")) || strpbrk(tagger_line, "<>\n") != lb+1 || strpbrk(lb+2, "><\n ") != rb) - return error("char" PD_FMT ": malformed tagger field", - tagger_line - buffer); + return error("char%"PRIuMAX": malformed tagger field", + (uintmax_t) (tagger_line - buffer)); /* Check for author name, at least one character, space is acceptable */ if (lb == tagger_line) - return error("char" PD_FMT ": missing tagger name", - tagger_line - buffer); + return error("char%"PRIuMAX": missing tagger name", + (uintmax_t) (tagger_line - buffer)); /* timestamp, 1 or more digits followed by space */ tagger_line = rb + 2; if (!(len = strspn(tagger_line, "0123456789"))) - return error("char" PD_FMT ": missing tag timestamp", - tagger_line - buffer); + return error("char%"PRIuMAX": missing tag timestamp", + (uintmax_t) (tagger_line - buffer)); tagger_line += len; if (*tagger_line != ' ') - return error("char" PD_FMT ": malformed tag timestamp", - tagger_line - buffer); + return error("char%"PRIuMAX": malformed tag timestamp", + (uintmax_t) (tagger_line - buffer)); tagger_line++; /* timezone, 5 digits [+-]hhmm, max. 1400 */ if (!((tagger_line[0] == '+' || tagger_line[0] == '-') && strspn(tagger_line+1, "0123456789") == 4 && tagger_line[5] == '\n' && atoi(tagger_line+1) <= 1400)) - return error("char" PD_FMT ": malformed tag timezone", - tagger_line - buffer); + return error("char%"PRIuMAX": malformed tag timezone", + (uintmax_t) (tagger_line - buffer)); tagger_line += 6; /* Verify the blank line separating the header from the body */ if (*tagger_line != '\n') - return error("char" PD_FMT ": trailing garbage in tag header", - tagger_line - buffer); + return error("char%"PRIuMAX": trailing garbage in tag header", + (uintmax_t) (tagger_line - buffer)); /* The actual stuff afterwards we don't care about.. */ return 0; } -#undef PD_FMT - int cmd_mktag(int argc, const char **argv, const char *prefix) { struct strbuf buf = STRBUF_INIT; diff --git a/builtin/mktree.c b/builtin/mktree.c index 098395fda1..4ae1c412d4 100644 --- a/builtin/mktree.c +++ b/builtin/mktree.c @@ -60,6 +60,7 @@ static void write_tree(unsigned char *sha1) } write_sha1_file(buf.buf, buf.len, tree_type, sha1); + strbuf_release(&buf); } static const char *mktree_usage[] = { diff --git a/builtin/mv.c b/builtin/mv.c index cdbb09473c..2a144b011c 100644 --- a/builtin/mv.c +++ b/builtin/mv.c @@ -29,7 +29,11 @@ static const char **copy_pathspec(const char *prefix, const char **pathspec, to_copy--; if (to_copy != length || base_name) { char *it = xmemdupz(result[i], to_copy); - result[i] = base_name ? strdup(basename(it)) : it; + if (base_name) { + result[i] = xstrdup(basename(it)); + free(it); + } else + result[i] = it; } } return get_pathspec(prefix, result); @@ -55,8 +59,9 @@ int cmd_mv(int argc, const char **argv, const char *prefix) int i, newfd; int verbose = 0, show_only = 0, force = 0, ignore_errors = 0; struct option builtin_mv_options[] = { - OPT__DRY_RUN(&show_only), - OPT_BOOLEAN('f', "force", &force, "force move/rename even if target exists"), + OPT__VERBOSE(&verbose, "be verbose"), + OPT__DRY_RUN(&show_only, "dry run"), + OPT__FORCE(&force, "force move/rename even if target exists"), OPT_BOOLEAN('k', NULL, &ignore_errors, "skip move/rename errors"), OPT_END(), }; @@ -74,7 +79,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix) newfd = hold_locked_index(&lock_file, 1); if (read_cache() < 0) - die("index file corrupt"); + die(_("index file corrupt")); source = copy_pathspec(prefix, argv, argc, 0); modes = xcalloc(argc, sizeof(enum update_mode)); @@ -89,7 +94,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix) destination = copy_pathspec(dest_path[0], argv, argc, 1); } else { if (argc != 1) - usage_with_options(builtin_mv_usage, builtin_mv_options); + die("destination '%s' is not a directory", dest_path[0]); destination = dest_path; } @@ -100,17 +105,17 @@ int cmd_mv(int argc, const char **argv, const char *prefix) const char *bad = NULL; if (show_only) - printf("Checking rename of '%s' to '%s'\n", src, dst); + printf(_("Checking rename of '%s' to '%s'\n"), src, dst); length = strlen(src); if (lstat(src, &st) < 0) - bad = "bad source"; + bad = _("bad source"); else if (!strncmp(src, dst, length) && (dst[length] == 0 || dst[length] == '/')) { - bad = "can not move directory into itself"; + bad = _("can not move directory into itself"); } else if ((src_is_dir = S_ISDIR(st.st_mode)) && lstat(dst, &st) == 0) - bad = "cannot move directory over file"; + bad = _("cannot move directory over file"); else if (src_is_dir) { const char *src_w_slash = add_slash(src); int len_w_slash = length + 1; @@ -120,7 +125,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix) first = cache_name_pos(src_w_slash, len_w_slash); if (first >= 0) - die ("Huh? %.*s is in index?", + die (_("Huh? %.*s is in index?"), len_w_slash, src_w_slash); first = -1 - first; @@ -132,7 +137,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix) free((char *)src_w_slash); if (last - first < 1) - bad = "source directory is empty"; + bad = _("source directory is empty"); else { int j, dst_len; @@ -163,22 +168,23 @@ int cmd_mv(int argc, const char **argv, const char *prefix) argc += last - first; } } else if (cache_name_pos(src, length) < 0) - bad = "not under version control"; + bad = _("not under version control"); else if (lstat(dst, &st) == 0) { - bad = "destination exists"; + bad = _("destination exists"); if (force) { /* * only files can overwrite each other: * check both source and destination */ if (S_ISREG(st.st_mode) || S_ISLNK(st.st_mode)) { - warning("%s; will overwrite!", bad); + if (verbose) + warning(_("overwriting '%s'"), dst); bad = NULL; } else - bad = "Cannot overwrite"; + bad = _("Cannot overwrite"); } } else if (string_list_has_string(&src_for_dst, dst)) - bad = "multiple sources for the same target"; + bad = _("multiple sources for the same target"); else string_list_insert(&src_for_dst, dst); @@ -193,7 +199,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix) i--; } } else - die ("%s, source=%s, destination=%s", + die (_("%s, source=%s, destination=%s"), bad, src, dst); } } @@ -203,10 +209,10 @@ int cmd_mv(int argc, const char **argv, const char *prefix) enum update_mode mode = modes[i]; int pos; if (show_only || verbose) - printf("Renaming %s to %s\n", src, dst); + printf(_("Renaming %s to %s\n"), src, dst); if (!show_only && mode != INDEX && rename(src, dst) < 0 && !ignore_errors) - die_errno ("renaming '%s' failed", src); + die_errno (_("renaming '%s' failed"), src); if (mode == WORKING_DIRECTORY) continue; @@ -220,7 +226,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix) if (active_cache_changed) { if (write_cache(newfd, active_cache, active_nr) || commit_locked_index(&lock_file)) - die("Unable to write new index file"); + die(_("Unable to write new index file")); } return 0; diff --git a/builtin/name-rev.c b/builtin/name-rev.c index 31f5c1c971..1b374583c2 100644 --- a/builtin/name-rev.c +++ b/builtin/name-rev.c @@ -172,7 +172,9 @@ static void show_name(const struct object *obj, } static char const * const name_rev_usage[] = { - "git name-rev [options] ( --all | --stdin | <commit>... )", + "git name-rev [options] <commit>...", + "git name-rev [options] --all", + "git name-rev [options] --stdin", NULL }; @@ -289,7 +291,7 @@ int cmd_name_rev(int argc, const char **argv, const char *prefix) max = get_max_object_index(); for (i = 0; i < max; i++) { struct object *obj = get_indexed_object(i); - if (!obj) + if (!obj || obj->type != OBJ_COMMIT) continue; show_name(obj, NULL, always, allow_undefined, data.name_only); diff --git a/builtin/notes.c b/builtin/notes.c index 6d07aac80c..3644d140ec 100644 --- a/builtin/notes.c +++ b/builtin/notes.c @@ -17,6 +17,7 @@ #include "run-command.h" #include "parse-options.h" #include "string-list.h" +#include "notes-merge.h" static const char * const git_notes_usage[] = { "git notes [--ref <notes_ref>] [list [<object>]]", @@ -25,8 +26,12 @@ static const char * const git_notes_usage[] = { "git notes [--ref <notes_ref>] append [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]", "git notes [--ref <notes_ref>] edit [<object>]", "git notes [--ref <notes_ref>] show [<object>]", - "git notes [--ref <notes_ref>] remove [<object>]", + "git notes [--ref <notes_ref>] merge [-v | -q] [-s <strategy> ] <notes_ref>", + "git notes merge --commit [-v | -q]", + "git notes merge --abort [-v | -q]", + "git notes [--ref <notes_ref>] remove [<object>...]", "git notes [--ref <notes_ref>] prune [-n | -v]", + "git notes [--ref <notes_ref>] get-ref", NULL }; @@ -61,6 +66,13 @@ static const char * const git_notes_show_usage[] = { NULL }; +static const char * const git_notes_merge_usage[] = { + "git notes merge [<options>] <notes_ref>", + "git notes merge --commit [<options>]", + "git notes merge --abort [<options>]", + NULL +}; + static const char * const git_notes_remove_usage[] = { "git notes remove [<object>]", NULL @@ -71,6 +83,11 @@ static const char * const git_notes_prune_usage[] = { NULL }; +static const char * const git_notes_get_ref_usage[] = { + "git notes get-ref", + NULL +}; + static const char note_template[] = "\n" "#\n" @@ -119,13 +136,13 @@ static void write_commented_object(int fd, const unsigned char *object) show.err = 0; show.git_cmd = 1; if (start_command(&show)) - die("unable to start 'show' for object '%s'", + die(_("unable to start 'show' for object '%s'"), sha1_to_hex(object)); /* Open the output as FILE* so strbuf_getline() can be used. */ show_out = xfdopen(show.out, "r"); if (show_out == NULL) - die_errno("can't fdopen 'show' output fd"); + die_errno(_("can't fdopen 'show' output fd")); /* Prepend "# " to each output line and write result to 'fd' */ while (strbuf_getline(&buf, show_out, '\n') != EOF) { @@ -135,10 +152,10 @@ static void write_commented_object(int fd, const unsigned char *object) } strbuf_release(&buf); if (fclose(show_out)) - die_errno("failed to close pipe to 'show' for object '%s'", + die_errno(_("failed to close pipe to 'show' for object '%s'"), sha1_to_hex(object)); if (finish_command(&show)) - die("failed to finish 'show' for object '%s'", + die(_("failed to finish 'show' for object '%s'"), sha1_to_hex(object)); } @@ -155,7 +172,7 @@ static void create_note(const unsigned char *object, struct msg_arg *msg, path = git_pathdup("NOTES_EDITMSG"); fd = open(path, O_CREAT | O_TRUNC | O_WRONLY, 0600); if (fd < 0) - die_errno("could not create file '%s'", path); + die_errno(_("could not create file '%s'"), path); if (msg->given) write_or_die(fd, msg->buf.buf, msg->buf.len); @@ -169,8 +186,8 @@ static void create_note(const unsigned char *object, struct msg_arg *msg, strbuf_reset(&(msg->buf)); if (launch_editor(path, &(msg->buf), NULL)) { - die("Please supply the note contents using either -m" \ - " or -F option"); + die(_("Please supply the note contents using either -m" \ + " or -F option")); } stripspace(&(msg->buf), 1); } @@ -190,14 +207,14 @@ static void create_note(const unsigned char *object, struct msg_arg *msg, } if (!msg->buf.len) { - fprintf(stderr, "Removing note for object %s\n", + fprintf(stderr, _("Removing note for object %s\n"), sha1_to_hex(object)); hashclr(result); } else { if (write_sha1_file(msg->buf.buf, msg->buf.len, blob_type, result)) { - error("unable to write note object"); + error(_("unable to write note object")); if (path) - error("The note contents has been left in %s", + error(_("The note contents has been left in %s"), path); exit(128); } @@ -231,9 +248,9 @@ static int parse_file_arg(const struct option *opt, const char *arg, int unset) strbuf_addch(&(msg->buf), '\n'); if (!strcmp(arg, "-")) { if (strbuf_read(&(msg->buf), 0, 1024) < 0) - die_errno("cannot read '%s'", arg); + die_errno(_("cannot read '%s'"), arg); } else if (strbuf_read_file(&(msg->buf), arg, 1024) < 0) - die_errno("could not open or read '%s'", arg); + die_errno(_("could not open or read '%s'"), arg); stripspace(&(msg->buf), 0); msg->given = 1; @@ -252,10 +269,10 @@ static int parse_reuse_arg(const struct option *opt, const char *arg, int unset) strbuf_addch(&(msg->buf), '\n'); if (get_sha1(arg, object)) - die("Failed to resolve '%s' as a valid ref.", arg); + die(_("Failed to resolve '%s' as a valid ref."), arg); if (!(buf = read_sha1_file(object, &type, &len)) || !len) { free(buf); - die("Failed to read object '%s'.", arg);; + die(_("Failed to read object '%s'."), arg);; } strbuf_add(&(msg->buf), buf, len); free(buf); @@ -271,46 +288,28 @@ static int parse_reedit_arg(const struct option *opt, const char *arg, int unset return parse_reuse_arg(opt, arg, unset); } -int commit_notes(struct notes_tree *t, const char *msg) +void commit_notes(struct notes_tree *t, const char *msg) { - struct commit_list *parent; - unsigned char tree_sha1[20], prev_commit[20], new_commit[20]; struct strbuf buf = STRBUF_INIT; + unsigned char commit_sha1[20]; if (!t) t = &default_notes_tree; if (!t->initialized || !t->ref || !*t->ref) - die("Cannot commit uninitialized/unreferenced notes tree"); + die(_("Cannot commit uninitialized/unreferenced notes tree")); if (!t->dirty) - return 0; /* don't have to commit an unchanged tree */ + return; /* don't have to commit an unchanged tree */ /* Prepare commit message and reflog message */ - strbuf_addstr(&buf, "notes: "); /* commit message starts at index 7 */ strbuf_addstr(&buf, msg); if (buf.buf[buf.len - 1] != '\n') strbuf_addch(&buf, '\n'); /* Make sure msg ends with newline */ - /* Convert notes tree to tree object */ - if (write_notes_tree(t, tree_sha1)) - die("Failed to write current notes tree to database"); - - /* Create new commit for the tree object */ - if (!read_ref(t->ref, prev_commit)) { /* retrieve parent commit */ - parent = xmalloc(sizeof(*parent)); - parent->item = lookup_commit(prev_commit); - parent->next = NULL; - } else { - hashclr(prev_commit); - parent = NULL; - } - if (commit_tree(buf.buf + 7, tree_sha1, parent, new_commit, NULL)) - die("Failed to commit notes tree to database"); - - /* Update notes ref with new commit */ - update_ref(buf.buf, t->ref, new_commit, prev_commit, 0, DIE_ON_ERR); + create_notes_commit(t, NULL, &buf, commit_sha1); + strbuf_insert(&buf, 0, "notes: ", 7); /* commit message starts at index 7 */ + update_ref(buf.buf, t->ref, commit_sha1, NULL, 0, DIE_ON_ERR); strbuf_release(&buf); - return 0; } combine_notes_fn parse_combine_notes_fn(const char *v) @@ -321,6 +320,8 @@ combine_notes_fn parse_combine_notes_fn(const char *v) return combine_notes_ignore; else if (!strcasecmp(v, "concatenate")) return combine_notes_concatenate; + else if (!strcasecmp(v, "cat_sort_uniq")) + return combine_notes_cat_sort_uniq; else return NULL; } @@ -336,7 +337,7 @@ static int notes_rewrite_config(const char *k, const char *v, void *cb) config_error_nonbool(k); c->combine = parse_combine_notes_fn(v); if (!c->combine) { - error("Bad notes.rewriteMode value: '%s'", v); + error(_("Bad notes.rewriteMode value: '%s'"), v); return 1; } return 0; @@ -346,8 +347,8 @@ static int notes_rewrite_config(const char *k, const char *v, void *cb) if (!prefixcmp(v, "refs/notes/")) string_list_add_refs_by_glob(c->refs, v); else - warning("Refusing to rewrite notes in %s" - " (outside of refs/notes/)", v); + warning(_("Refusing to rewrite notes in %s" + " (outside of refs/notes/)"), v); return 0; } @@ -371,8 +372,10 @@ struct notes_rewrite_cfg *init_copy_notes_for_rewrite(const char *cmd) c->mode_from_env = 1; c->combine = parse_combine_notes_fn(rewrite_mode_env); if (!c->combine) - error("Bad " GIT_NOTES_REWRITE_MODE_ENVIRONMENT - " value: '%s'", rewrite_mode_env); + /* TRANSLATORS: The first %s is the name of the + environment variable, the second %s is its value */ + error(_("Bad %s value: '%s'"), GIT_NOTES_REWRITE_MODE_ENVIRONMENT, + rewrite_mode_env); } if (rewrite_refs_env) { c->refs_from_env = 1; @@ -412,7 +415,7 @@ void finish_copy_notes_for_rewrite(struct notes_rewrite_cfg *c) free(c); } -int notes_copy_from_stdin(int force, const char *rewrite_cmd) +static int notes_copy_from_stdin(int force, const char *rewrite_cmd) { struct strbuf buf = STRBUF_INIT; struct notes_rewrite_cfg *c = NULL; @@ -435,13 +438,13 @@ int notes_copy_from_stdin(int force, const char *rewrite_cmd) split = strbuf_split(&buf, ' '); if (!split[0] || !split[1]) - die("Malformed input line: '%s'.", buf.buf); + die(_("Malformed input line: '%s'."), buf.buf); strbuf_rtrim(split[0]); strbuf_rtrim(split[1]); if (get_sha1(split[0]->buf, from_obj)) - die("Failed to resolve '%s' as a valid ref.", split[0]->buf); + die(_("Failed to resolve '%s' as a valid ref."), split[0]->buf); if (get_sha1(split[1]->buf, to_obj)) - die("Failed to resolve '%s' as a valid ref.", split[1]->buf); + die(_("Failed to resolve '%s' as a valid ref."), split[1]->buf); if (rewrite_cmd) err = copy_note_for_rewrite(c, from_obj, to_obj); @@ -450,7 +453,7 @@ int notes_copy_from_stdin(int force, const char *rewrite_cmd) combine_notes_overwrite); if (err) { - error("Failed to copy notes from '%s' to '%s'", + error(_("Failed to copy notes from '%s' to '%s'"), split[0]->buf, split[1]->buf); ret = 1; } @@ -494,20 +497,20 @@ static int list(int argc, const char **argv, const char *prefix) git_notes_list_usage, 0); if (1 < argc) { - error("too many parameters"); + error(_("too many parameters")); usage_with_options(git_notes_list_usage, options); } t = init_notes_check("list"); if (argc) { if (get_sha1(argv[0], object)) - die("Failed to resolve '%s' as a valid ref.", argv[0]); + die(_("Failed to resolve '%s' as a valid ref."), argv[0]); note = get_note(t, object); if (note) { puts(sha1_to_hex(note)); retval = 0; } else - retval = error("No note found for object %s.", + retval = error(_("No note found for object %s."), sha1_to_hex(object)); } else retval = for_each_note(t, 0, list_each_note, NULL); @@ -516,6 +519,8 @@ static int list(int argc, const char **argv, const char *prefix) return retval; } +static int append_edit(int argc, const char **argv, const char *prefix); + static int add(int argc, const char **argv, const char *prefix) { int retval = 0, force = 0; @@ -526,46 +531,58 @@ static int add(int argc, const char **argv, const char *prefix) const unsigned char *note; struct msg_arg msg = { 0, 0, STRBUF_INIT }; struct option options[] = { - { OPTION_CALLBACK, 'm', "message", &msg, "MSG", + { OPTION_CALLBACK, 'm', "message", &msg, "msg", "note contents as a string", PARSE_OPT_NONEG, parse_msg_arg}, - { OPTION_CALLBACK, 'F', "file", &msg, "FILE", + { OPTION_CALLBACK, 'F', "file", &msg, "file", "note contents in a file", PARSE_OPT_NONEG, parse_file_arg}, - { OPTION_CALLBACK, 'c', "reedit-message", &msg, "OBJECT", + { OPTION_CALLBACK, 'c', "reedit-message", &msg, "object", "reuse and edit specified note object", PARSE_OPT_NONEG, parse_reedit_arg}, - { OPTION_CALLBACK, 'C', "reuse-message", &msg, "OBJECT", + { OPTION_CALLBACK, 'C', "reuse-message", &msg, "object", "reuse specified note object", PARSE_OPT_NONEG, parse_reuse_arg}, - OPT_BOOLEAN('f', "force", &force, "replace existing notes"), + OPT__FORCE(&force, "replace existing notes"), OPT_END() }; argc = parse_options(argc, argv, prefix, options, git_notes_add_usage, - 0); + PARSE_OPT_KEEP_ARGV0); - if (1 < argc) { - error("too many parameters"); + if (2 < argc) { + error(_("too many parameters")); usage_with_options(git_notes_add_usage, options); } - object_ref = argc ? argv[0] : "HEAD"; + object_ref = argc > 1 ? argv[1] : "HEAD"; if (get_sha1(object_ref, object)) - die("Failed to resolve '%s' as a valid ref.", object_ref); + die(_("Failed to resolve '%s' as a valid ref."), object_ref); t = init_notes_check("add"); note = get_note(t, object); if (note) { if (!force) { - retval = error("Cannot add notes. Found existing notes " + if (!msg.given) { + /* + * Redirect to "edit" subcommand. + * + * We only end up here if none of -m/-F/-c/-C + * or -f are given. The original args are + * therefore still in argv[0-1]. + */ + argv[0] = "edit"; + free_notes(t); + return append_edit(argc, argv, prefix); + } + retval = error(_("Cannot add notes. Found existing notes " "for object %s. Use '-f' to overwrite " - "existing notes", sha1_to_hex(object)); + "existing notes"), sha1_to_hex(object)); goto out; } - fprintf(stderr, "Overwriting existing notes for object %s\n", + fprintf(stderr, _("Overwriting existing notes for object %s\n"), sha1_to_hex(object)); } @@ -573,8 +590,8 @@ static int add(int argc, const char **argv, const char *prefix) if (is_null_sha1(new_note)) remove_note(t, object); - else - add_note(t, object, new_note, combine_notes_overwrite); + else if (add_note(t, object, new_note, combine_notes_overwrite)) + die("BUG: combine_notes_overwrite failed"); snprintf(logmsg, sizeof(logmsg), "Notes %s by 'git notes %s'", is_null_sha1(new_note) ? "removed" : "added", "add"); @@ -594,7 +611,7 @@ static int copy(int argc, const char **argv, const char *prefix) struct notes_tree *t; const char *rewrite_cmd = NULL; struct option options[] = { - OPT_BOOLEAN('f', "force", &force, "replace existing notes"), + OPT__FORCE(&force, "replace existing notes"), OPT_BOOLEAN(0, "stdin", &from_stdin, "read objects from stdin"), OPT_STRING(0, "for-rewrite", &rewrite_cmd, "command", "load rewriting config for <command> (implies " @@ -607,7 +624,7 @@ static int copy(int argc, const char **argv, const char *prefix) if (from_stdin || rewrite_cmd) { if (argc) { - error("too many parameters"); + error(_("too many parameters")); usage_with_options(git_notes_copy_usage, options); } else { return notes_copy_from_stdin(force, rewrite_cmd); @@ -615,45 +632,46 @@ static int copy(int argc, const char **argv, const char *prefix) } if (argc < 2) { - error("too few parameters"); + error(_("too few parameters")); usage_with_options(git_notes_copy_usage, options); } if (2 < argc) { - error("too many parameters"); + error(_("too many parameters")); usage_with_options(git_notes_copy_usage, options); } if (get_sha1(argv[0], from_obj)) - die("Failed to resolve '%s' as a valid ref.", argv[0]); + die(_("Failed to resolve '%s' as a valid ref."), argv[0]); object_ref = 1 < argc ? argv[1] : "HEAD"; if (get_sha1(object_ref, object)) - die("Failed to resolve '%s' as a valid ref.", object_ref); + die(_("Failed to resolve '%s' as a valid ref."), object_ref); t = init_notes_check("copy"); note = get_note(t, object); if (note) { if (!force) { - retval = error("Cannot copy notes. Found existing " + retval = error(_("Cannot copy notes. Found existing " "notes for object %s. Use '-f' to " - "overwrite existing notes", + "overwrite existing notes"), sha1_to_hex(object)); goto out; } - fprintf(stderr, "Overwriting existing notes for object %s\n", + fprintf(stderr, _("Overwriting existing notes for object %s\n"), sha1_to_hex(object)); } from_note = get_note(t, from_obj); if (!from_note) { - retval = error("Missing notes on source object %s. Cannot " - "copy.", sha1_to_hex(from_obj)); + retval = error(_("Missing notes on source object %s. Cannot " + "copy."), sha1_to_hex(from_obj)); goto out; } - add_note(t, object, from_note, combine_notes_overwrite); + if (add_note(t, object, from_note, combine_notes_overwrite)) + die("BUG: combine_notes_overwrite failed"); commit_notes(t, "Notes added by 'git notes copy'"); out: free_notes(t); @@ -670,16 +688,16 @@ static int append_edit(int argc, const char **argv, const char *prefix) const char * const *usage; struct msg_arg msg = { 0, 0, STRBUF_INIT }; struct option options[] = { - { OPTION_CALLBACK, 'm', "message", &msg, "MSG", + { OPTION_CALLBACK, 'm', "message", &msg, "msg", "note contents as a string", PARSE_OPT_NONEG, parse_msg_arg}, - { OPTION_CALLBACK, 'F', "file", &msg, "FILE", + { OPTION_CALLBACK, 'F', "file", &msg, "file", "note contents in a file", PARSE_OPT_NONEG, parse_file_arg}, - { OPTION_CALLBACK, 'c', "reedit-message", &msg, "OBJECT", + { OPTION_CALLBACK, 'c', "reedit-message", &msg, "object", "reuse and edit specified note object", PARSE_OPT_NONEG, parse_reedit_arg}, - { OPTION_CALLBACK, 'C', "reuse-message", &msg, "OBJECT", + { OPTION_CALLBACK, 'C', "reuse-message", &msg, "object", "reuse specified note object", PARSE_OPT_NONEG, parse_reuse_arg}, OPT_END() @@ -691,19 +709,19 @@ static int append_edit(int argc, const char **argv, const char *prefix) PARSE_OPT_KEEP_ARGV0); if (2 < argc) { - error("too many parameters"); + error(_("too many parameters")); usage_with_options(usage, options); } if (msg.given && edit) - fprintf(stderr, "The -m/-F/-c/-C options have been deprecated " + fprintf(stderr, _("The -m/-F/-c/-C options have been deprecated " "for the 'edit' subcommand.\n" - "Please use 'git notes add -f -m/-F/-c/-C' instead.\n"); + "Please use 'git notes add -f -m/-F/-c/-C' instead.\n")); object_ref = 1 < argc ? argv[1] : "HEAD"; if (get_sha1(object_ref, object)) - die("Failed to resolve '%s' as a valid ref.", object_ref); + die(_("Failed to resolve '%s' as a valid ref."), object_ref); t = init_notes_check(argv[0]); note = get_note(t, object); @@ -712,8 +730,8 @@ static int append_edit(int argc, const char **argv, const char *prefix) if (is_null_sha1(new_note)) remove_note(t, object); - else - add_note(t, object, new_note, combine_notes_overwrite); + else if (add_note(t, object, new_note, combine_notes_overwrite)) + die("BUG: combine_notes_overwrite failed"); snprintf(logmsg, sizeof(logmsg), "Notes %s by 'git notes %s'", is_null_sha1(new_note) ? "removed" : "added", argv[0]); @@ -738,20 +756,20 @@ static int show(int argc, const char **argv, const char *prefix) 0); if (1 < argc) { - error("too many parameters"); + error(_("too many parameters")); usage_with_options(git_notes_show_usage, options); } object_ref = argc ? argv[0] : "HEAD"; if (get_sha1(object_ref, object)) - die("Failed to resolve '%s' as a valid ref.", object_ref); + die(_("Failed to resolve '%s' as a valid ref."), object_ref); t = init_notes_check("show"); note = get_note(t, object); if (!note) - retval = error("No note found for object %s.", + retval = error(_("No note found for object %s."), sha1_to_hex(object)); else { const char *show_args[3] = {"show", sha1_to_hex(note), NULL}; @@ -761,40 +779,239 @@ static int show(int argc, const char **argv, const char *prefix) return retval; } -static int remove_cmd(int argc, const char **argv, const char *prefix) +static int merge_abort(struct notes_merge_options *o) { + int ret = 0; + + /* + * Remove .git/NOTES_MERGE_PARTIAL and .git/NOTES_MERGE_REF, and call + * notes_merge_abort() to remove .git/NOTES_MERGE_WORKTREE. + */ + + if (delete_ref("NOTES_MERGE_PARTIAL", NULL, 0)) + ret += error("Failed to delete ref NOTES_MERGE_PARTIAL"); + if (delete_ref("NOTES_MERGE_REF", NULL, REF_NODEREF)) + ret += error("Failed to delete ref NOTES_MERGE_REF"); + if (notes_merge_abort(o)) + ret += error("Failed to remove 'git notes merge' worktree"); + return ret; +} + +static int merge_commit(struct notes_merge_options *o) +{ + struct strbuf msg = STRBUF_INIT; + unsigned char sha1[20], parent_sha1[20]; + struct notes_tree *t; + struct commit *partial; + struct pretty_print_context pretty_ctx; + void *local_ref_to_free; + int ret; + + /* + * Read partial merge result from .git/NOTES_MERGE_PARTIAL, + * and target notes ref from .git/NOTES_MERGE_REF. + */ + + if (get_sha1("NOTES_MERGE_PARTIAL", sha1)) + die("Failed to read ref NOTES_MERGE_PARTIAL"); + else if (!(partial = lookup_commit_reference(sha1))) + die("Could not find commit from NOTES_MERGE_PARTIAL."); + else if (parse_commit(partial)) + die("Could not parse commit from NOTES_MERGE_PARTIAL."); + + if (partial->parents) + hashcpy(parent_sha1, partial->parents->item->object.sha1); + else + hashclr(parent_sha1); + + t = xcalloc(1, sizeof(struct notes_tree)); + init_notes(t, "NOTES_MERGE_PARTIAL", combine_notes_overwrite, 0); + + o->local_ref = local_ref_to_free = + resolve_refdup("NOTES_MERGE_REF", sha1, 0, NULL); + if (!o->local_ref) + die("Failed to resolve NOTES_MERGE_REF"); + + if (notes_merge_commit(o, t, partial, sha1)) + die("Failed to finalize notes merge"); + + /* Reuse existing commit message in reflog message */ + memset(&pretty_ctx, 0, sizeof(pretty_ctx)); + format_commit_message(partial, "%s", &msg, &pretty_ctx); + strbuf_trim(&msg); + strbuf_insert(&msg, 0, "notes: ", 7); + update_ref(msg.buf, o->local_ref, sha1, + is_null_sha1(parent_sha1) ? NULL : parent_sha1, + 0, DIE_ON_ERR); + + free_notes(t); + strbuf_release(&msg); + ret = merge_abort(o); + free(local_ref_to_free); + return ret; +} + +static int merge(int argc, const char **argv, const char *prefix) +{ + struct strbuf remote_ref = STRBUF_INIT, msg = STRBUF_INIT; + unsigned char result_sha1[20]; + struct notes_tree *t; + struct notes_merge_options o; + int do_merge = 0, do_commit = 0, do_abort = 0; + int verbosity = 0, result; + const char *strategy = NULL; struct option options[] = { + OPT_GROUP("General options"), + OPT__VERBOSITY(&verbosity), + OPT_GROUP("Merge options"), + OPT_STRING('s', "strategy", &strategy, "strategy", + "resolve notes conflicts using the given strategy " + "(manual/ours/theirs/union/cat_sort_uniq)"), + OPT_GROUP("Committing unmerged notes"), + { OPTION_BOOLEAN, 0, "commit", &do_commit, NULL, + "finalize notes merge by committing unmerged notes", + PARSE_OPT_NOARG | PARSE_OPT_NONEG }, + OPT_GROUP("Aborting notes merge resolution"), + { OPTION_BOOLEAN, 0, "abort", &do_abort, NULL, + "abort notes merge", + PARSE_OPT_NOARG | PARSE_OPT_NONEG }, OPT_END() }; - const char *object_ref; - struct notes_tree *t; - unsigned char object[20]; - int retval; argc = parse_options(argc, argv, prefix, options, - git_notes_remove_usage, 0); + git_notes_merge_usage, 0); - if (1 < argc) { + if (strategy || do_commit + do_abort == 0) + do_merge = 1; + if (do_merge + do_commit + do_abort != 1) { + error("cannot mix --commit, --abort or -s/--strategy"); + usage_with_options(git_notes_merge_usage, options); + } + + if (do_merge && argc != 1) { + error("Must specify a notes ref to merge"); + usage_with_options(git_notes_merge_usage, options); + } else if (!do_merge && argc) { error("too many parameters"); - usage_with_options(git_notes_remove_usage, options); + usage_with_options(git_notes_merge_usage, options); } - object_ref = argc ? argv[0] : "HEAD"; + init_notes_merge_options(&o); + o.verbosity = verbosity + NOTES_MERGE_VERBOSITY_DEFAULT; + + if (do_abort) + return merge_abort(&o); + if (do_commit) + return merge_commit(&o); + + o.local_ref = default_notes_ref(); + strbuf_addstr(&remote_ref, argv[0]); + expand_notes_ref(&remote_ref); + o.remote_ref = remote_ref.buf; + + if (strategy) { + if (!strcmp(strategy, "manual")) + o.strategy = NOTES_MERGE_RESOLVE_MANUAL; + else if (!strcmp(strategy, "ours")) + o.strategy = NOTES_MERGE_RESOLVE_OURS; + else if (!strcmp(strategy, "theirs")) + o.strategy = NOTES_MERGE_RESOLVE_THEIRS; + else if (!strcmp(strategy, "union")) + o.strategy = NOTES_MERGE_RESOLVE_UNION; + else if (!strcmp(strategy, "cat_sort_uniq")) + o.strategy = NOTES_MERGE_RESOLVE_CAT_SORT_UNIQ; + else { + error("Unknown -s/--strategy: %s", strategy); + usage_with_options(git_notes_merge_usage, options); + } + } - if (get_sha1(object_ref, object)) - die("Failed to resolve '%s' as a valid ref.", object_ref); + t = init_notes_check("merge"); + + strbuf_addf(&msg, "notes: Merged notes from %s into %s", + remote_ref.buf, default_notes_ref()); + strbuf_add(&(o.commit_msg), msg.buf + 7, msg.len - 7); /* skip "notes: " */ + + result = notes_merge(&o, t, result_sha1); + + if (result >= 0) /* Merge resulted (trivially) in result_sha1 */ + /* Update default notes ref with new commit */ + update_ref(msg.buf, default_notes_ref(), result_sha1, NULL, + 0, DIE_ON_ERR); + else { /* Merge has unresolved conflicts */ + /* Update .git/NOTES_MERGE_PARTIAL with partial merge result */ + update_ref(msg.buf, "NOTES_MERGE_PARTIAL", result_sha1, NULL, + 0, DIE_ON_ERR); + /* Store ref-to-be-updated into .git/NOTES_MERGE_REF */ + if (create_symref("NOTES_MERGE_REF", default_notes_ref(), NULL)) + die("Failed to store link to current notes ref (%s)", + default_notes_ref()); + printf("Automatic notes merge failed. Fix conflicts in %s and " + "commit the result with 'git notes merge --commit', or " + "abort the merge with 'git notes merge --abort'.\n", + git_path(NOTES_MERGE_WORKTREE)); + } - t = init_notes_check("remove"); + free_notes(t); + strbuf_release(&remote_ref); + strbuf_release(&msg); + return result < 0; /* return non-zero on conflicts */ +} - retval = remove_note(t, object); - if (retval) - fprintf(stderr, "Object %s has no note\n", sha1_to_hex(object)); - else { - fprintf(stderr, "Removing note for object %s\n", - sha1_to_hex(object)); +#define IGNORE_MISSING 1 - commit_notes(t, "Notes removed by 'git notes remove'"); +static int remove_one_note(struct notes_tree *t, const char *name, unsigned flag) +{ + int status; + unsigned char sha1[20]; + if (get_sha1(name, sha1)) + return error(_("Failed to resolve '%s' as a valid ref."), name); + status = remove_note(t, sha1); + if (status) + fprintf(stderr, _("Object %s has no note\n"), name); + else + fprintf(stderr, _("Removing note for object %s\n"), name); + return (flag & IGNORE_MISSING) ? 0 : status; +} + +static int remove_cmd(int argc, const char **argv, const char *prefix) +{ + unsigned flag = 0; + int from_stdin = 0; + struct option options[] = { + OPT_BIT(0, "ignore-missing", &flag, + "attempt to remove non-existent note is not an error", + IGNORE_MISSING), + OPT_BOOLEAN(0, "stdin", &from_stdin, + "read object names from the standard input"), + OPT_END() + }; + struct notes_tree *t; + int retval = 0; + + argc = parse_options(argc, argv, prefix, options, + git_notes_remove_usage, 0); + + t = init_notes_check("remove"); + + if (!argc && !from_stdin) { + retval = remove_one_note(t, "HEAD", flag); + } else { + while (*argv) { + retval |= remove_one_note(t, *argv, flag); + argv++; + } + } + if (from_stdin) { + struct strbuf sb = STRBUF_INIT; + while (strbuf_getwholeline(&sb, stdin, '\n') != EOF) { + strbuf_rtrim(&sb); + retval |= remove_one_note(t, sb.buf, flag); + } + strbuf_release(&sb); } + if (!retval) + commit_notes(t, "Notes removed by 'git notes remove'"); free_notes(t); return retval; } @@ -804,9 +1021,8 @@ static int prune(int argc, const char **argv, const char *prefix) struct notes_tree *t; int show_only = 0, verbose = 0; struct option options[] = { - OPT_BOOLEAN('n', "dry-run", &show_only, - "do not remove, show only"), - OPT_BOOLEAN('v', "verbose", &verbose, "report pruned notes"), + OPT__DRY_RUN(&show_only, "do not remove, show only"), + OPT__VERBOSE(&verbose, "report pruned notes"), OPT_END() }; @@ -814,7 +1030,7 @@ static int prune(int argc, const char **argv, const char *prefix) 0); if (argc) { - error("too many parameters"); + error(_("too many parameters")); usage_with_options(git_notes_prune_usage, options); } @@ -828,6 +1044,21 @@ static int prune(int argc, const char **argv, const char *prefix) return 0; } +static int get_ref(int argc, const char **argv, const char *prefix) +{ + struct option options[] = { OPT_END() }; + argc = parse_options(argc, argv, prefix, options, + git_notes_get_ref_usage, 0); + + if (argc) { + error("too many parameters"); + usage_with_options(git_notes_get_ref_usage, options); + } + + puts(default_notes_ref()); + return 0; +} + int cmd_notes(int argc, const char **argv, const char *prefix) { int result; @@ -844,13 +1075,8 @@ int cmd_notes(int argc, const char **argv, const char *prefix) if (override_notes_ref) { struct strbuf sb = STRBUF_INIT; - if (!prefixcmp(override_notes_ref, "refs/notes/")) - /* we're happy */; - else if (!prefixcmp(override_notes_ref, "notes/")) - strbuf_addstr(&sb, "refs/"); - else - strbuf_addstr(&sb, "refs/notes/"); strbuf_addstr(&sb, override_notes_ref); + expand_notes_ref(&sb); setenv("GIT_NOTES_REF", sb.buf, 1); strbuf_release(&sb); } @@ -865,12 +1091,16 @@ int cmd_notes(int argc, const char **argv, const char *prefix) result = append_edit(argc, argv, prefix); else if (!strcmp(argv[0], "show")) result = show(argc, argv, prefix); + else if (!strcmp(argv[0], "merge")) + result = merge(argc, argv, prefix); else if (!strcmp(argv[0], "remove")) result = remove_cmd(argc, argv, prefix); else if (!strcmp(argv[0], "prune")) result = prune(argc, argv, prefix); + else if (!strcmp(argv[0], "get-ref")) + result = get_ref(argc, argv, prefix); else { - result = error("Unknown subcommand: %s", argv[0]); + result = error(_("Unknown subcommand: %s"), argv[0]); usage_with_options(git_notes_usage, options); } diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c index f8eba53c82..ccfcbad146 100644 --- a/builtin/pack-objects.c +++ b/builtin/pack-objects.c @@ -16,22 +16,13 @@ #include "list-objects.h" #include "progress.h" #include "refs.h" - -#ifndef NO_PTHREADS -#include <pthread.h> #include "thread-utils.h" -#endif -static const char pack_usage[] = - "git pack-objects [ -q | --progress | --all-progress ]\n" - " [--all-progress-implied]\n" - " [--max-pack-size=<n>] [--local] [--incremental]\n" - " [--window=<n>] [--window-memory=<n>] [--depth=<n>]\n" - " [--no-reuse-delta] [--no-reuse-object] [--delta-base-offset]\n" - " [--threads=<n>] [--non-empty] [--revs [--unpacked | --all]]\n" - " [--reflog] [--stdout | base-name] [--include-tag]\n" - " [--keep-unreachable | --unpack-unreachable]\n" - " [< ref-list | < object-list]"; +static const char *pack_usage[] = { + "git pack-objects --stdout [options...] [< ref-list | < object-list]", + "git pack-objects [options...] base-name [< ref-list | < object-list]", + NULL +}; struct object_entry { struct pack_idx_entry idx; @@ -55,6 +46,8 @@ struct object_entry { * objects against. */ unsigned char no_try_delta; + unsigned char tagged; /* near the very tip of refs */ + unsigned char filled; /* assigned write-order */ }; /* @@ -70,14 +63,16 @@ static uint32_t nr_objects, nr_alloc, nr_result, nr_written; static int non_empty; static int reuse_delta = 1, reuse_object = 1; static int keep_unreachable, unpack_unreachable, include_tag; +static unsigned long unpack_unreachable_expiration; static int local; static int incremental; static int ignore_packed_keep; static int allow_ofs_delta; +static struct pack_idx_option pack_idx_opts; static const char *base_name; static int progress = 1; static int window = 10; -static unsigned long pack_size_limit, pack_size_limit_cfg; +static unsigned long pack_size_limit; static int depth = 50; static int delta_search_threads; static int pack_to_stdout; @@ -99,6 +94,7 @@ static unsigned long window_memory_limit = 0; */ static int *object_ix; static int object_ix_hashsz; +static struct object_entry *locate_object_entry(const unsigned char *sha1); /* * stats @@ -130,13 +126,13 @@ static void *get_delta(struct object_entry *entry) static unsigned long do_compress(void **pptr, unsigned long size) { - z_stream stream; + git_zstream stream; void *in, *out; unsigned long maxsize; memset(&stream, 0, sizeof(stream)); - deflateInit(&stream, pack_compression_level); - maxsize = deflateBound(&stream, size); + git_deflate_init(&stream, pack_compression_level); + maxsize = git_deflate_bound(&stream, size); in = *pptr; out = xmalloc(maxsize); @@ -146,9 +142,9 @@ static unsigned long do_compress(void **pptr, unsigned long size) stream.avail_in = size; stream.next_out = out; stream.avail_out = maxsize; - while (deflate(&stream, Z_FINISH) == Z_OK) + while (git_deflate(&stream, Z_FINISH) == Z_OK) ; /* nothing */ - deflateEnd(&stream); + git_deflate_end(&stream); free(in); return stream.total_out; @@ -164,7 +160,7 @@ static int check_pack_inflate(struct packed_git *p, off_t len, unsigned long expect) { - z_stream stream; + git_zstream stream; unsigned char fakebuf[4096], *in; int st; @@ -191,34 +187,191 @@ static void copy_pack_data(struct sha1file *f, off_t len) { unsigned char *in; - unsigned int avail; + unsigned long avail; while (len) { in = use_pack(p, w_curs, offset, &avail); if (avail > len) - avail = (unsigned int)len; + avail = (unsigned long)len; sha1write(f, in, avail); offset += avail; len -= avail; } } -static unsigned long write_object(struct sha1file *f, - struct object_entry *entry, - off_t write_offset) +/* Return 0 if we will bust the pack-size limit */ +static unsigned long write_no_reuse_object(struct sha1file *f, struct object_entry *entry, + unsigned long limit, int usable_delta) { - unsigned long size, limit, datalen; - void *buf; + unsigned long size, datalen; unsigned char header[10], dheader[10]; unsigned hdrlen; enum object_type type; + void *buf; + + if (!usable_delta) { + buf = read_sha1_file(entry->idx.sha1, &type, &size); + if (!buf) + die("unable to read %s", sha1_to_hex(entry->idx.sha1)); + /* + * make sure no cached delta data remains from a + * previous attempt before a pack split occurred. + */ + free(entry->delta_data); + entry->delta_data = NULL; + entry->z_delta_size = 0; + } else if (entry->delta_data) { + size = entry->delta_size; + buf = entry->delta_data; + entry->delta_data = NULL; + type = (allow_ofs_delta && entry->delta->idx.offset) ? + OBJ_OFS_DELTA : OBJ_REF_DELTA; + } else { + buf = get_delta(entry); + size = entry->delta_size; + type = (allow_ofs_delta && entry->delta->idx.offset) ? + OBJ_OFS_DELTA : OBJ_REF_DELTA; + } + + if (entry->z_delta_size) + datalen = entry->z_delta_size; + else + datalen = do_compress(&buf, size); + + /* + * The object header is a byte of 'type' followed by zero or + * more bytes of length. + */ + hdrlen = encode_in_pack_object_header(type, size, header); + + if (type == OBJ_OFS_DELTA) { + /* + * Deltas with relative base contain an additional + * encoding of the relative offset for the delta + * base from this object's position in the pack. + */ + off_t ofs = entry->idx.offset - entry->delta->idx.offset; + unsigned pos = sizeof(dheader) - 1; + dheader[pos] = ofs & 127; + while (ofs >>= 7) + dheader[--pos] = 128 | (--ofs & 127); + if (limit && hdrlen + sizeof(dheader) - pos + datalen + 20 >= limit) { + free(buf); + return 0; + } + sha1write(f, header, hdrlen); + sha1write(f, dheader + pos, sizeof(dheader) - pos); + hdrlen += sizeof(dheader) - pos; + } else if (type == OBJ_REF_DELTA) { + /* + * Deltas with a base reference contain + * an additional 20 bytes for the base sha1. + */ + if (limit && hdrlen + 20 + datalen + 20 >= limit) { + free(buf); + return 0; + } + sha1write(f, header, hdrlen); + sha1write(f, entry->delta->idx.sha1, 20); + hdrlen += 20; + } else { + if (limit && hdrlen + datalen + 20 >= limit) { + free(buf); + return 0; + } + sha1write(f, header, hdrlen); + } + sha1write(f, buf, datalen); + free(buf); + + return hdrlen + datalen; +} + +/* Return 0 if we will bust the pack-size limit */ +static unsigned long write_reuse_object(struct sha1file *f, struct object_entry *entry, + unsigned long limit, int usable_delta) +{ + struct packed_git *p = entry->in_pack; + struct pack_window *w_curs = NULL; + struct revindex_entry *revidx; + off_t offset; + enum object_type type = entry->type; + unsigned long datalen; + unsigned char header[10], dheader[10]; + unsigned hdrlen; + + if (entry->delta) + type = (allow_ofs_delta && entry->delta->idx.offset) ? + OBJ_OFS_DELTA : OBJ_REF_DELTA; + hdrlen = encode_in_pack_object_header(type, entry->size, header); + + offset = entry->in_pack_offset; + revidx = find_pack_revindex(p, offset); + datalen = revidx[1].offset - offset; + if (!pack_to_stdout && p->index_version > 1 && + check_pack_crc(p, &w_curs, offset, datalen, revidx->nr)) { + error("bad packed object CRC for %s", sha1_to_hex(entry->idx.sha1)); + unuse_pack(&w_curs); + return write_no_reuse_object(f, entry, limit, usable_delta); + } + + offset += entry->in_pack_header_size; + datalen -= entry->in_pack_header_size; + + if (!pack_to_stdout && p->index_version == 1 && + check_pack_inflate(p, &w_curs, offset, datalen, entry->size)) { + error("corrupt packed object for %s", sha1_to_hex(entry->idx.sha1)); + unuse_pack(&w_curs); + return write_no_reuse_object(f, entry, limit, usable_delta); + } + + if (type == OBJ_OFS_DELTA) { + off_t ofs = entry->idx.offset - entry->delta->idx.offset; + unsigned pos = sizeof(dheader) - 1; + dheader[pos] = ofs & 127; + while (ofs >>= 7) + dheader[--pos] = 128 | (--ofs & 127); + if (limit && hdrlen + sizeof(dheader) - pos + datalen + 20 >= limit) { + unuse_pack(&w_curs); + return 0; + } + sha1write(f, header, hdrlen); + sha1write(f, dheader + pos, sizeof(dheader) - pos); + hdrlen += sizeof(dheader) - pos; + reused_delta++; + } else if (type == OBJ_REF_DELTA) { + if (limit && hdrlen + 20 + datalen + 20 >= limit) { + unuse_pack(&w_curs); + return 0; + } + sha1write(f, header, hdrlen); + sha1write(f, entry->delta->idx.sha1, 20); + hdrlen += 20; + reused_delta++; + } else { + if (limit && hdrlen + datalen + 20 >= limit) { + unuse_pack(&w_curs); + return 0; + } + sha1write(f, header, hdrlen); + } + copy_pack_data(f, p, &w_curs, offset, datalen); + unuse_pack(&w_curs); + reused++; + return hdrlen + datalen; +} + +/* Return 0 if we will bust the pack-size limit */ +static unsigned long write_object(struct sha1file *f, + struct object_entry *entry, + off_t write_offset) +{ + unsigned long limit, len; int usable_delta, to_reuse; if (!pack_to_stdout) crc32_begin(f); - type = entry->type; - /* apply size limit if limited packsize and not first object */ if (!pack_size_limit || !nr_written) limit = 0; @@ -246,11 +399,11 @@ static unsigned long write_object(struct sha1file *f, to_reuse = 0; /* explicit */ else if (!entry->in_pack) to_reuse = 0; /* can't reuse what we don't have */ - else if (type == OBJ_REF_DELTA || type == OBJ_OFS_DELTA) + else if (entry->type == OBJ_REF_DELTA || entry->type == OBJ_OFS_DELTA) /* check_object() decided it for us ... */ to_reuse = usable_delta; /* ... but pack split may override that */ - else if (type != entry->in_pack_type) + else if (entry->type != entry->in_pack_type) to_reuse = 0; /* pack has delta which is unusable */ else if (entry->delta) to_reuse = 0; /* we want to pack afresh */ @@ -259,174 +412,71 @@ static unsigned long write_object(struct sha1file *f, * and we do not need to deltify it. */ - if (!to_reuse) { - no_reuse: - if (!usable_delta) { - buf = read_sha1_file(entry->idx.sha1, &type, &size); - if (!buf) - die("unable to read %s", sha1_to_hex(entry->idx.sha1)); - /* - * make sure no cached delta data remains from a - * previous attempt before a pack split occurred. - */ - free(entry->delta_data); - entry->delta_data = NULL; - entry->z_delta_size = 0; - } else if (entry->delta_data) { - size = entry->delta_size; - buf = entry->delta_data; - entry->delta_data = NULL; - type = (allow_ofs_delta && entry->delta->idx.offset) ? - OBJ_OFS_DELTA : OBJ_REF_DELTA; - } else { - buf = get_delta(entry); - size = entry->delta_size; - type = (allow_ofs_delta && entry->delta->idx.offset) ? - OBJ_OFS_DELTA : OBJ_REF_DELTA; - } - - if (entry->z_delta_size) - datalen = entry->z_delta_size; - else - datalen = do_compress(&buf, size); - - /* - * The object header is a byte of 'type' followed by zero or - * more bytes of length. - */ - hdrlen = encode_in_pack_object_header(type, size, header); - - if (type == OBJ_OFS_DELTA) { - /* - * Deltas with relative base contain an additional - * encoding of the relative offset for the delta - * base from this object's position in the pack. - */ - off_t ofs = entry->idx.offset - entry->delta->idx.offset; - unsigned pos = sizeof(dheader) - 1; - dheader[pos] = ofs & 127; - while (ofs >>= 7) - dheader[--pos] = 128 | (--ofs & 127); - if (limit && hdrlen + sizeof(dheader) - pos + datalen + 20 >= limit) { - free(buf); - return 0; - } - sha1write(f, header, hdrlen); - sha1write(f, dheader + pos, sizeof(dheader) - pos); - hdrlen += sizeof(dheader) - pos; - } else if (type == OBJ_REF_DELTA) { - /* - * Deltas with a base reference contain - * an additional 20 bytes for the base sha1. - */ - if (limit && hdrlen + 20 + datalen + 20 >= limit) { - free(buf); - return 0; - } - sha1write(f, header, hdrlen); - sha1write(f, entry->delta->idx.sha1, 20); - hdrlen += 20; - } else { - if (limit && hdrlen + datalen + 20 >= limit) { - free(buf); - return 0; - } - sha1write(f, header, hdrlen); - } - sha1write(f, buf, datalen); - free(buf); - } - else { - struct packed_git *p = entry->in_pack; - struct pack_window *w_curs = NULL; - struct revindex_entry *revidx; - off_t offset; - - if (entry->delta) - type = (allow_ofs_delta && entry->delta->idx.offset) ? - OBJ_OFS_DELTA : OBJ_REF_DELTA; - hdrlen = encode_in_pack_object_header(type, entry->size, header); - - offset = entry->in_pack_offset; - revidx = find_pack_revindex(p, offset); - datalen = revidx[1].offset - offset; - if (!pack_to_stdout && p->index_version > 1 && - check_pack_crc(p, &w_curs, offset, datalen, revidx->nr)) { - error("bad packed object CRC for %s", sha1_to_hex(entry->idx.sha1)); - unuse_pack(&w_curs); - goto no_reuse; - } - - offset += entry->in_pack_header_size; - datalen -= entry->in_pack_header_size; - if (!pack_to_stdout && p->index_version == 1 && - check_pack_inflate(p, &w_curs, offset, datalen, entry->size)) { - error("corrupt packed object for %s", sha1_to_hex(entry->idx.sha1)); - unuse_pack(&w_curs); - goto no_reuse; - } + if (!to_reuse) + len = write_no_reuse_object(f, entry, limit, usable_delta); + else + len = write_reuse_object(f, entry, limit, usable_delta); + if (!len) + return 0; - if (type == OBJ_OFS_DELTA) { - off_t ofs = entry->idx.offset - entry->delta->idx.offset; - unsigned pos = sizeof(dheader) - 1; - dheader[pos] = ofs & 127; - while (ofs >>= 7) - dheader[--pos] = 128 | (--ofs & 127); - if (limit && hdrlen + sizeof(dheader) - pos + datalen + 20 >= limit) { - unuse_pack(&w_curs); - return 0; - } - sha1write(f, header, hdrlen); - sha1write(f, dheader + pos, sizeof(dheader) - pos); - hdrlen += sizeof(dheader) - pos; - reused_delta++; - } else if (type == OBJ_REF_DELTA) { - if (limit && hdrlen + 20 + datalen + 20 >= limit) { - unuse_pack(&w_curs); - return 0; - } - sha1write(f, header, hdrlen); - sha1write(f, entry->delta->idx.sha1, 20); - hdrlen += 20; - reused_delta++; - } else { - if (limit && hdrlen + datalen + 20 >= limit) { - unuse_pack(&w_curs); - return 0; - } - sha1write(f, header, hdrlen); - } - copy_pack_data(f, p, &w_curs, offset, datalen); - unuse_pack(&w_curs); - reused++; - } if (usable_delta) written_delta++; written++; if (!pack_to_stdout) entry->idx.crc32 = crc32_end(f); - return hdrlen + datalen; + return len; } -static int write_one(struct sha1file *f, - struct object_entry *e, - off_t *offset) +enum write_one_status { + WRITE_ONE_SKIP = -1, /* already written */ + WRITE_ONE_BREAK = 0, /* writing this will bust the limit; not written */ + WRITE_ONE_WRITTEN = 1, /* normal */ + WRITE_ONE_RECURSIVE = 2 /* already scheduled to be written */ +}; + +static enum write_one_status write_one(struct sha1file *f, + struct object_entry *e, + off_t *offset) { unsigned long size; + int recursing; - /* offset is non zero if object is written already. */ - if (e->idx.offset || e->preferred_base) - return -1; + /* + * we set offset to 1 (which is an impossible value) to mark + * the fact that this object is involved in "write its base + * first before writing a deltified object" recursion. + */ + recursing = (e->idx.offset == 1); + if (recursing) { + warning("recursive delta detected for object %s", + sha1_to_hex(e->idx.sha1)); + return WRITE_ONE_RECURSIVE; + } else if (e->idx.offset || e->preferred_base) { + /* offset is non zero if object is written already. */ + return WRITE_ONE_SKIP; + } /* if we are deltified, write out base object first. */ - if (e->delta && !write_one(f, e->delta, offset)) - return 0; + if (e->delta) { + e->idx.offset = 1; /* now recurse */ + switch (write_one(f, e->delta, offset)) { + case WRITE_ONE_RECURSIVE: + /* we cannot depend on this one */ + e->delta = NULL; + break; + default: + break; + case WRITE_ONE_BREAK: + e->idx.offset = recursing; + return WRITE_ONE_BREAK; + } + } e->idx.offset = *offset; size = write_object(f, e, *offset); if (!size) { - e->idx.offset = 0; - return 0; + e->idx.offset = recursing; + return WRITE_ONE_BREAK; } written_list[nr_written++] = &e->idx; @@ -434,7 +484,171 @@ static int write_one(struct sha1file *f, if (signed_add_overflows(*offset, size)) die("pack too large for current definition of off_t"); *offset += size; - return 1; + return WRITE_ONE_WRITTEN; +} + +static int mark_tagged(const char *path, const unsigned char *sha1, int flag, + void *cb_data) +{ + unsigned char peeled[20]; + struct object_entry *entry = locate_object_entry(sha1); + + if (entry) + entry->tagged = 1; + if (!peel_ref(path, peeled)) { + entry = locate_object_entry(peeled); + if (entry) + entry->tagged = 1; + } + return 0; +} + +static inline void add_to_write_order(struct object_entry **wo, + unsigned int *endp, + struct object_entry *e) +{ + if (e->filled) + return; + wo[(*endp)++] = e; + e->filled = 1; +} + +static void add_descendants_to_write_order(struct object_entry **wo, + unsigned int *endp, + struct object_entry *e) +{ + int add_to_order = 1; + while (e) { + if (add_to_order) { + struct object_entry *s; + /* add this node... */ + add_to_write_order(wo, endp, e); + /* all its siblings... */ + for (s = e->delta_sibling; s; s = s->delta_sibling) { + add_to_write_order(wo, endp, s); + } + } + /* drop down a level to add left subtree nodes if possible */ + if (e->delta_child) { + add_to_order = 1; + e = e->delta_child; + } else { + add_to_order = 0; + /* our sibling might have some children, it is next */ + if (e->delta_sibling) { + e = e->delta_sibling; + continue; + } + /* go back to our parent node */ + e = e->delta; + while (e && !e->delta_sibling) { + /* we're on the right side of a subtree, keep + * going up until we can go right again */ + e = e->delta; + } + if (!e) { + /* done- we hit our original root node */ + return; + } + /* pass it off to sibling at this level */ + e = e->delta_sibling; + } + }; +} + +static void add_family_to_write_order(struct object_entry **wo, + unsigned int *endp, + struct object_entry *e) +{ + struct object_entry *root; + + for (root = e; root->delta; root = root->delta) + ; /* nothing */ + add_descendants_to_write_order(wo, endp, root); +} + +static struct object_entry **compute_write_order(void) +{ + unsigned int i, wo_end, last_untagged; + + struct object_entry **wo = xmalloc(nr_objects * sizeof(*wo)); + + for (i = 0; i < nr_objects; i++) { + objects[i].tagged = 0; + objects[i].filled = 0; + objects[i].delta_child = NULL; + objects[i].delta_sibling = NULL; + } + + /* + * Fully connect delta_child/delta_sibling network. + * Make sure delta_sibling is sorted in the original + * recency order. + */ + for (i = nr_objects; i > 0;) { + struct object_entry *e = &objects[--i]; + if (!e->delta) + continue; + /* Mark me as the first child */ + e->delta_sibling = e->delta->delta_child; + e->delta->delta_child = e; + } + + /* + * Mark objects that are at the tip of tags. + */ + for_each_tag_ref(mark_tagged, NULL); + + /* + * Give the objects in the original recency order until + * we see a tagged tip. + */ + for (i = wo_end = 0; i < nr_objects; i++) { + if (objects[i].tagged) + break; + add_to_write_order(wo, &wo_end, &objects[i]); + } + last_untagged = i; + + /* + * Then fill all the tagged tips. + */ + for (; i < nr_objects; i++) { + if (objects[i].tagged) + add_to_write_order(wo, &wo_end, &objects[i]); + } + + /* + * And then all remaining commits and tags. + */ + for (i = last_untagged; i < nr_objects; i++) { + if (objects[i].type != OBJ_COMMIT && + objects[i].type != OBJ_TAG) + continue; + add_to_write_order(wo, &wo_end, &objects[i]); + } + + /* + * And then all the trees. + */ + for (i = last_untagged; i < nr_objects; i++) { + if (objects[i].type != OBJ_TREE) + continue; + add_to_write_order(wo, &wo_end, &objects[i]); + } + + /* + * Finally all the rest in really tight order + */ + for (i = last_untagged; i < nr_objects; i++) { + if (!objects[i].filled) + add_family_to_write_order(wo, &wo_end, &objects[i]); + } + + if (wo_end != nr_objects) + die("ordered %u objects, expected %"PRIu32, wo_end, nr_objects); + + return wo; } static void write_pack_file(void) @@ -442,37 +656,31 @@ static void write_pack_file(void) uint32_t i = 0, j; struct sha1file *f; off_t offset; - struct pack_header hdr; uint32_t nr_remaining = nr_result; time_t last_mtime = 0; + struct object_entry **write_order; if (progress > pack_to_stdout) progress_state = start_progress("Writing objects", nr_result); written_list = xmalloc(nr_objects * sizeof(*written_list)); + write_order = compute_write_order(); do { unsigned char sha1[20]; char *pack_tmp_name = NULL; - if (pack_to_stdout) { + if (pack_to_stdout) f = sha1fd_throughput(1, "<stdout>", progress_state); - } else { - char tmpname[PATH_MAX]; - int fd; - fd = odb_mkstemp(tmpname, sizeof(tmpname), - "pack/tmp_pack_XXXXXX"); - pack_tmp_name = xstrdup(tmpname); - f = sha1fd(fd, pack_tmp_name); - } + else + f = create_tmp_packfile(&pack_tmp_name); - hdr.hdr_signature = htonl(PACK_SIGNATURE); - hdr.hdr_version = htonl(PACK_VERSION); - hdr.hdr_entries = htonl(nr_remaining); - sha1write(f, &hdr, sizeof(hdr)); - offset = sizeof(hdr); + offset = write_pack_header(f, nr_remaining); + if (!offset) + die_errno("unable to write pack header"); nr_written = 0; for (; i < nr_objects; i++) { - if (!write_one(f, objects + i, &offset)) + struct object_entry *e = write_order[i]; + if (write_one(f, e, &offset) == WRITE_ONE_BREAK) break; display_progress(progress_state, written); } @@ -494,20 +702,8 @@ static void write_pack_file(void) if (!pack_to_stdout) { struct stat st; - const char *idx_tmp_name; char tmpname[PATH_MAX]; - idx_tmp_name = write_idx_file(NULL, written_list, - nr_written, sha1); - - snprintf(tmpname, sizeof(tmpname), "%s-%s.pack", - base_name, sha1_to_hex(sha1)); - free_pack_by_name(tmpname); - if (adjust_shared_perm(pack_tmp_name)) - die_errno("unable to make temporary pack file readable"); - if (rename(pack_tmp_name, tmpname)) - die_errno("unable to rename temporary pack file"); - /* * Packs are runtime accessed in their mtime * order since newer packs are more likely to contain @@ -515,28 +711,27 @@ static void write_pack_file(void) * packs then we should modify the mtime of later ones * to preserve this property. */ - if (stat(tmpname, &st) < 0) { + if (stat(pack_tmp_name, &st) < 0) { warning("failed to stat %s: %s", - tmpname, strerror(errno)); + pack_tmp_name, strerror(errno)); } else if (!last_mtime) { last_mtime = st.st_mtime; } else { struct utimbuf utb; utb.actime = st.st_atime; utb.modtime = --last_mtime; - if (utime(tmpname, &utb) < 0) + if (utime(pack_tmp_name, &utb) < 0) warning("failed utime() on %s: %s", tmpname, strerror(errno)); } - snprintf(tmpname, sizeof(tmpname), "%s-%s.idx", - base_name, sha1_to_hex(sha1)); - if (adjust_shared_perm(idx_tmp_name)) - die_errno("unable to make temporary index file readable"); - if (rename(idx_tmp_name, tmpname)) - die_errno("unable to rename temporary index file"); - - free((void *) idx_tmp_name); + /* Enough space for "-<sha-1>.pack"? */ + if (sizeof(tmpname) <= strlen(base_name) + 50) + die("pack base name '%s' too long", base_name); + snprintf(tmpname, sizeof(tmpname), "%s-", base_name); + finish_tmp_packfile(tmpname, pack_tmp_name, + written_list, nr_written, + &pack_idx_opts, sha1); free(pack_tmp_name); puts(sha1_to_hex(sha1)); } @@ -549,6 +744,7 @@ static void write_pack_file(void) } while (nr_remaining && i < nr_objects); free(written_list); + free(write_order); stop_progress(&progress_state); if (written != nr_result) die("wrote %"PRIu32" objects while expecting %"PRIu32, @@ -637,7 +833,7 @@ static int no_try_delta(const char *path) struct git_attr_check check[1]; setup_delta_attr_check(check); - if (git_checkattr(path, ARRAY_SIZE(check), check)) + if (git_check_attr(path, ARRAY_SIZE(check), check)) return 0; if (ATTR_FALSE(check->value)) return 1; @@ -671,6 +867,10 @@ static int add_object_entry(const unsigned char *sha1, enum object_type type, off_t offset = find_pack_entry_one(sha1, p); if (offset) { if (!found_pack) { + if (!is_pack_valid(p)) { + warning("packfile %s cannot be accessed", p->pack_name); + continue; + } found_offset = offset; found_pack = p; } @@ -842,7 +1042,7 @@ static void add_pbase_object(struct tree_desc *tree, while (tree_entry(tree,&entry)) { if (S_ISGITLINK(entry.mode)) continue; - cmp = tree_entry_len(entry.path, entry.sha1) != cmplen ? 1 : + cmp = tree_entry_len(&entry) != cmplen ? 1 : memcmp(name, entry.path, cmplen); if (cmp > 0) continue; @@ -998,7 +1198,7 @@ static void check_object(struct object_entry *entry) const unsigned char *base_ref = NULL; struct object_entry *base_entry; unsigned long used, used_0; - unsigned int avail; + unsigned long avail; off_t ofs; unsigned char *buf, c; @@ -1146,8 +1346,12 @@ static void get_object_details(void) sorted_by_offset[i] = objects + i; qsort(sorted_by_offset, nr_objects, sizeof(*sorted_by_offset), pack_offset_sort); - for (i = 0; i < nr_objects; i++) - check_object(sorted_by_offset[i]); + for (i = 0; i < nr_objects; i++) { + struct object_entry *entry = sorted_by_offset[i]; + check_object(entry); + if (big_file_threshold < entry->size) + entry->no_try_delta = 1; + } free(sorted_by_offset); } @@ -1248,11 +1452,16 @@ static int try_delta(struct unpacked *trg, struct unpacked *src, return -1; /* - * We do not bother to try a delta that we discarded - * on an earlier try, but only when reusing delta data. + * We do not bother to try a delta that we discarded on an + * earlier try, but only when reusing delta data. Note that + * src_entry that is marked as the preferred_base should always + * be considered, as even if we produce a suboptimal delta against + * it, we will still save the transfer cost, as we already know + * the other side has it and we won't send src_entry at all. */ if (reuse_delta && trg_entry->in_pack && trg_entry->in_pack == src_entry->in_pack && + !src_entry->preferred_base && trg_entry->in_pack_type != OBJ_REF_DELTA && trg_entry->in_pack_type != OBJ_OFS_DELTA) return 0; @@ -1298,9 +1507,23 @@ static int try_delta(struct unpacked *trg, struct unpacked *src, read_lock(); src->data = read_sha1_file(src_entry->idx.sha1, &type, &sz); read_unlock(); - if (!src->data) + if (!src->data) { + if (src_entry->preferred_base) { + static int warned = 0; + if (!warned++) + warning("object %s cannot be read", + sha1_to_hex(src_entry->idx.sha1)); + /* + * Those objects are not included in the + * resulting pack. Be resilient and ignore + * them if they can't be read, in case the + * pack could be created nevertheless. + */ + return 0; + } die("object %s cannot be read", sha1_to_hex(src_entry->idx.sha1)); + } if (sz != src_size) die("object %s inconsistent object length (%lu vs %lu)", sha1_to_hex(src_entry->idx.sha1), sz, src_size); @@ -1529,7 +1752,7 @@ static void try_to_free_from_threads(size_t size) read_unlock(); } -try_to_free_t old_try_to_free_routine; +static try_to_free_t old_try_to_free_routine; /* * The main thread waits on the condition that (at least) one of the workers @@ -1870,14 +2093,10 @@ static int git_pack_config(const char *k, const char *v, void *cb) return 0; } if (!strcmp(k, "pack.indexversion")) { - pack_idx_default_version = git_config_int(k, v); - if (pack_idx_default_version > 2) + pack_idx_opts.version = git_config_int(k, v); + if (pack_idx_opts.version > 2) die("bad pack.indexversion=%"PRIu32, - pack_idx_default_version); - return 0; - } - if (!strcmp(k, "pack.packsizelimit")) { - pack_size_limit_cfg = git_config_ulong(k, v); + pack_idx_opts.version); return 0; } return git_default_config(k, v, cb); @@ -1922,7 +2141,9 @@ static void show_commit(struct commit *commit, void *data) commit->object.flags |= OBJECT_ADDED; } -static void show_object(struct object *obj, const struct name_path *path, const char *last) +static void show_object(struct object *obj, + const struct name_path *path, const char *last, + void *data) { char *name = path_name(path, last); @@ -2051,6 +2272,10 @@ static void loosen_unused_packed_objects(struct rev_info *revs) if (!p->pack_local || p->pack_keep) continue; + if (unpack_unreachable_expiration && + p->mtime < unpack_unreachable_expiration) + continue; + if (open_pack_index(p)) die("cannot open pack index"); @@ -2102,203 +2327,175 @@ static void get_object_list(int ac, const char **av) loosen_unused_packed_objects(&revs); } +static int option_parse_index_version(const struct option *opt, + const char *arg, int unset) +{ + char *c; + const char *val = arg; + pack_idx_opts.version = strtoul(val, &c, 10); + if (pack_idx_opts.version > 2) + die(_("unsupported index version %s"), val); + if (*c == ',' && c[1]) + pack_idx_opts.off32_limit = strtoul(c+1, &c, 0); + if (*c || pack_idx_opts.off32_limit & 0x80000000) + die(_("bad index version '%s'"), val); + return 0; +} + +static int option_parse_unpack_unreachable(const struct option *opt, + const char *arg, int unset) +{ + if (unset) { + unpack_unreachable = 0; + unpack_unreachable_expiration = 0; + } + else { + unpack_unreachable = 1; + if (arg) + unpack_unreachable_expiration = approxidate(arg); + } + return 0; +} + +static int option_parse_ulong(const struct option *opt, + const char *arg, int unset) +{ + if (unset) + die(_("option %s does not accept negative form"), + opt->long_name); + + if (!git_parse_ulong(arg, opt->value)) + die(_("unable to parse value '%s' for option %s"), + arg, opt->long_name); + return 0; +} + +#define OPT_ULONG(s, l, v, h) \ + { OPTION_CALLBACK, (s), (l), (v), "n", (h), \ + PARSE_OPT_NONEG, option_parse_ulong } + int cmd_pack_objects(int argc, const char **argv, const char *prefix) { int use_internal_rev_list = 0; int thin = 0; int all_progress_implied = 0; - uint32_t i; - const char **rp_av; - int rp_ac_alloc = 64; - int rp_ac; + const char *rp_av[6]; + int rp_ac = 0; + int rev_list_unpacked = 0, rev_list_all = 0, rev_list_reflog = 0; + struct option pack_objects_options[] = { + OPT_SET_INT('q', "quiet", &progress, + "do not show progress meter", 0), + OPT_SET_INT(0, "progress", &progress, + "show progress meter", 1), + OPT_SET_INT(0, "all-progress", &progress, + "show progress meter during object writing phase", 2), + OPT_BOOL(0, "all-progress-implied", + &all_progress_implied, + "similar to --all-progress when progress meter is shown"), + { OPTION_CALLBACK, 0, "index-version", NULL, "version[,offset]", + "write the pack index file in the specified idx format version", + 0, option_parse_index_version }, + OPT_ULONG(0, "max-pack-size", &pack_size_limit, + "maximum size of each output pack file"), + OPT_BOOL(0, "local", &local, + "ignore borrowed objects from alternate object store"), + OPT_BOOL(0, "incremental", &incremental, + "ignore packed objects"), + OPT_INTEGER(0, "window", &window, + "limit pack window by objects"), + OPT_ULONG(0, "window-memory", &window_memory_limit, + "limit pack window by memory in addition to object limit"), + OPT_INTEGER(0, "depth", &depth, + "maximum length of delta chain allowed in the resulting pack"), + OPT_BOOL(0, "reuse-delta", &reuse_delta, + "reuse existing deltas"), + OPT_BOOL(0, "reuse-object", &reuse_object, + "reuse existing objects"), + OPT_BOOL(0, "delta-base-offset", &allow_ofs_delta, + "use OFS_DELTA objects"), + OPT_INTEGER(0, "threads", &delta_search_threads, + "use threads when searching for best delta matches"), + OPT_BOOL(0, "non-empty", &non_empty, + "do not create an empty pack output"), + OPT_BOOL(0, "revs", &use_internal_rev_list, + "read revision arguments from standard input"), + { OPTION_SET_INT, 0, "unpacked", &rev_list_unpacked, NULL, + "limit the objects to those that are not yet packed", + PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, 1 }, + { OPTION_SET_INT, 0, "all", &rev_list_all, NULL, + "include objects reachable from any reference", + PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, 1 }, + { OPTION_SET_INT, 0, "reflog", &rev_list_reflog, NULL, + "include objects referred by reflog entries", + PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, 1 }, + OPT_BOOL(0, "stdout", &pack_to_stdout, + "output pack to stdout"), + OPT_BOOL(0, "include-tag", &include_tag, + "include tag objects that refer to objects to be packed"), + OPT_BOOL(0, "keep-unreachable", &keep_unreachable, + "keep unreachable objects"), + { OPTION_CALLBACK, 0, "unpack-unreachable", NULL, "time", + "unpack unreachable objects newer than <time>", + PARSE_OPT_OPTARG, option_parse_unpack_unreachable }, + OPT_BOOL(0, "thin", &thin, + "create thin packs"), + OPT_BOOL(0, "honor-pack-keep", &ignore_packed_keep, + "ignore packs that have companion .keep file"), + OPT_INTEGER(0, "compression", &pack_compression_level, + "pack compression level"), + OPT_SET_INT(0, "keep-true-parents", &grafts_replace_parents, + "do not hide commits by grafts", 0), + OPT_END(), + }; read_replace_refs = 0; - rp_av = xcalloc(rp_ac_alloc, sizeof(*rp_av)); - - rp_av[0] = "pack-objects"; - rp_av[1] = "--objects"; /* --thin will make it --objects-edge */ - rp_ac = 2; - + reset_pack_idx_option(&pack_idx_opts); git_config(git_pack_config, NULL); if (!pack_compression_seen && core_compression_seen) pack_compression_level = core_compression_level; progress = isatty(2); - for (i = 1; i < argc; i++) { - const char *arg = argv[i]; + argc = parse_options(argc, argv, prefix, pack_objects_options, + pack_usage, 0); - if (*arg != '-') - break; + if (argc) { + base_name = argv[0]; + argc--; + } + if (pack_to_stdout != !base_name || argc) + usage_with_options(pack_usage, pack_objects_options); + + rp_av[rp_ac++] = "pack-objects"; + if (thin) { + use_internal_rev_list = 1; + rp_av[rp_ac++] = "--objects-edge"; + } else + rp_av[rp_ac++] = "--objects"; + + if (rev_list_all) { + use_internal_rev_list = 1; + rp_av[rp_ac++] = "--all"; + } + if (rev_list_reflog) { + use_internal_rev_list = 1; + rp_av[rp_ac++] = "--reflog"; + } + if (rev_list_unpacked) { + use_internal_rev_list = 1; + rp_av[rp_ac++] = "--unpacked"; + } - if (!strcmp("--non-empty", arg)) { - non_empty = 1; - continue; - } - if (!strcmp("--local", arg)) { - local = 1; - continue; - } - if (!strcmp("--incremental", arg)) { - incremental = 1; - continue; - } - if (!strcmp("--honor-pack-keep", arg)) { - ignore_packed_keep = 1; - continue; - } - if (!prefixcmp(arg, "--compression=")) { - char *end; - int level = strtoul(arg+14, &end, 0); - if (!arg[14] || *end) - usage(pack_usage); - if (level == -1) - level = Z_DEFAULT_COMPRESSION; - else if (level < 0 || level > Z_BEST_COMPRESSION) - die("bad pack compression level %d", level); - pack_compression_level = level; - continue; - } - if (!prefixcmp(arg, "--max-pack-size=")) { - pack_size_limit_cfg = 0; - if (!git_parse_ulong(arg+16, &pack_size_limit)) - usage(pack_usage); - continue; - } - if (!prefixcmp(arg, "--window=")) { - char *end; - window = strtoul(arg+9, &end, 0); - if (!arg[9] || *end) - usage(pack_usage); - continue; - } - if (!prefixcmp(arg, "--window-memory=")) { - if (!git_parse_ulong(arg+16, &window_memory_limit)) - usage(pack_usage); - continue; - } - if (!prefixcmp(arg, "--threads=")) { - char *end; - delta_search_threads = strtoul(arg+10, &end, 0); - if (!arg[10] || *end || delta_search_threads < 0) - usage(pack_usage); + if (!reuse_object) + reuse_delta = 0; + if (pack_compression_level == -1) + pack_compression_level = Z_DEFAULT_COMPRESSION; + else if (pack_compression_level < 0 || pack_compression_level > Z_BEST_COMPRESSION) + die("bad pack compression level %d", pack_compression_level); #ifdef NO_PTHREADS - if (delta_search_threads != 1) - warning("no threads support, " - "ignoring %s", arg); + if (delta_search_threads != 1) + warning("no threads support, ignoring --threads"); #endif - continue; - } - if (!prefixcmp(arg, "--depth=")) { - char *end; - depth = strtoul(arg+8, &end, 0); - if (!arg[8] || *end) - usage(pack_usage); - continue; - } - if (!strcmp("--progress", arg)) { - progress = 1; - continue; - } - if (!strcmp("--all-progress", arg)) { - progress = 2; - continue; - } - if (!strcmp("--all-progress-implied", arg)) { - all_progress_implied = 1; - continue; - } - if (!strcmp("-q", arg)) { - progress = 0; - continue; - } - if (!strcmp("--no-reuse-delta", arg)) { - reuse_delta = 0; - continue; - } - if (!strcmp("--no-reuse-object", arg)) { - reuse_object = reuse_delta = 0; - continue; - } - if (!strcmp("--delta-base-offset", arg)) { - allow_ofs_delta = 1; - continue; - } - if (!strcmp("--stdout", arg)) { - pack_to_stdout = 1; - continue; - } - if (!strcmp("--revs", arg)) { - use_internal_rev_list = 1; - continue; - } - if (!strcmp("--keep-unreachable", arg)) { - keep_unreachable = 1; - continue; - } - if (!strcmp("--unpack-unreachable", arg)) { - unpack_unreachable = 1; - continue; - } - if (!strcmp("--include-tag", arg)) { - include_tag = 1; - continue; - } - if (!strcmp("--unpacked", arg) || - !strcmp("--reflog", arg) || - !strcmp("--all", arg)) { - use_internal_rev_list = 1; - if (rp_ac >= rp_ac_alloc - 1) { - rp_ac_alloc = alloc_nr(rp_ac_alloc); - rp_av = xrealloc(rp_av, - rp_ac_alloc * sizeof(*rp_av)); - } - rp_av[rp_ac++] = arg; - continue; - } - if (!strcmp("--thin", arg)) { - use_internal_rev_list = 1; - thin = 1; - rp_av[1] = "--objects-edge"; - continue; - } - if (!prefixcmp(arg, "--index-version=")) { - char *c; - pack_idx_default_version = strtoul(arg + 16, &c, 10); - if (pack_idx_default_version > 2) - die("bad %s", arg); - if (*c == ',') - pack_idx_off32_limit = strtoul(c+1, &c, 0); - if (*c || pack_idx_off32_limit & 0x80000000) - die("bad %s", arg); - continue; - } - if (!strcmp(arg, "--keep-true-parents")) { - grafts_replace_parents = 0; - continue; - } - usage(pack_usage); - } - - /* Traditionally "pack-objects [options] base extra" failed; - * we would however want to take refs parameter that would - * have been given to upstream rev-list ourselves, which means - * we somehow want to say what the base name is. So the - * syntax would be: - * - * pack-objects [options] base <refs...> - * - * in other words, we would treat the first non-option as the - * base_name and send everything else to the internal revision - * walker. - */ - - if (!pack_to_stdout) - base_name = argv[i++]; - - if (pack_to_stdout != !base_name) - usage(pack_usage); - if (!pack_to_stdout && !pack_size_limit) pack_size_limit = pack_size_limit_cfg; if (pack_to_stdout && pack_size_limit) diff --git a/builtin/pack-redundant.c b/builtin/pack-redundant.c index 41e1615a28..f5c6afc5dd 100644 --- a/builtin/pack-redundant.c +++ b/builtin/pack-redundant.c @@ -6,8 +6,7 @@ * */ -#include "cache.h" -#include "exec_cmd.h" +#include "builtin.h" #define BLKSIZE 512 diff --git a/builtin/pack-refs.c b/builtin/pack-refs.c index 091860b2e3..39a9d89fbd 100644 --- a/builtin/pack-refs.c +++ b/builtin/pack-refs.c @@ -1,4 +1,4 @@ -#include "cache.h" +#include "builtin.h" #include "parse-options.h" #include "pack-refs.h" diff --git a/builtin/patch-id.c b/builtin/patch-id.c index 512530022e..3cfe02d5a5 100644 --- a/builtin/patch-id.c +++ b/builtin/patch-id.c @@ -1,5 +1,4 @@ -#include "cache.h" -#include "exec_cmd.h" +#include "builtin.h" static void flush_current_id(int patchlen, unsigned char *id, git_SHA_CTX *c) { @@ -57,13 +56,13 @@ static int scan_hunk_header(const char *p, int *p_before, int *p_after) return 1; } -int get_one_patchid(unsigned char *next_sha1, git_SHA_CTX *ctx) +static int get_one_patchid(unsigned char *next_sha1, git_SHA_CTX *ctx, struct strbuf *line_buf) { - static char line[1000]; int patchlen = 0, found_next = 0; int before = -1, after = -1; - while (fgets(line, sizeof(line), stdin) != NULL) { + while (strbuf_getwholeline(line_buf, stdin, '\n') != EOF) { + char *line = line_buf->buf; char *p = line; int len; @@ -73,6 +72,8 @@ int get_one_patchid(unsigned char *next_sha1, git_SHA_CTX *ctx) p += 7; else if (!memcmp(line, "From ", 5)) p += 5; + else if (!memcmp(line, "\\ ", 2) && 12 < strlen(line)) + continue; if (!get_sha1_hex(p, next_sha1)) { found_next = 1; @@ -132,14 +133,16 @@ static void generate_id_list(void) unsigned char sha1[20], n[20]; git_SHA_CTX ctx; int patchlen; + struct strbuf line_buf = STRBUF_INIT; git_SHA1_Init(&ctx); hashclr(sha1); while (!feof(stdin)) { - patchlen = get_one_patchid(n, &ctx); + patchlen = get_one_patchid(n, &ctx, &line_buf); flush_current_id(patchlen, sha1, &ctx); hashcpy(sha1, n); } + strbuf_release(&line_buf); } static const char patch_id_usage[] = "git patch-id < patch"; diff --git a/builtin/prune-packed.c b/builtin/prune-packed.c index f9463deec2..b58a2e1eb2 100644 --- a/builtin/prune-packed.c +++ b/builtin/prune-packed.c @@ -35,8 +35,6 @@ static void prune_dir(int i, DIR *dir, char *pathname, int len, int opts) unlink_or_warn(pathname); display_progress(progress, i + 1); } - pathname[len] = 0; - rmdir(pathname); } void prune_packed_objects(int opts) @@ -65,6 +63,8 @@ void prune_packed_objects(int opts) continue; prune_dir(i, d, pathname, len + 3, opts); closedir(d); + pathname[len + 2] = '\0'; + rmdir(pathname); } stop_progress(&progress); } diff --git a/builtin/prune.c b/builtin/prune.c index 99218ba49e..b99b635e44 100644 --- a/builtin/prune.c +++ b/builtin/prune.c @@ -5,6 +5,7 @@ #include "builtin.h" #include "reachable.h" #include "parse-options.h" +#include "progress.h" #include "dir.h" static const char * const prune_usage[] = { @@ -14,6 +15,7 @@ static const char * const prune_usage[] = { static int show_only; static int verbose; static unsigned long expire; +static int show_progress = -1; static int prune_tmp_object(const char *path, const char *filename) { @@ -83,9 +85,9 @@ static int prune_dir(int i, char *path) } fprintf(stderr, "bad sha1 file: %s/%s\n", path, de->d_name); } + closedir(dir); if (!show_only) rmdir(path); - closedir(dir); return 0; } @@ -124,10 +126,11 @@ static void remove_temporary_files(const char *path) int cmd_prune(int argc, const char **argv, const char *prefix) { struct rev_info revs; + struct progress *progress = NULL; const struct option options[] = { - OPT_BOOLEAN('n', "dry-run", &show_only, - "do not remove, show only"), - OPT_BOOLEAN('v', "verbose", &verbose, "report pruned objects"), + OPT__DRY_RUN(&show_only, "do not remove, show only"), + OPT__VERBOSE(&verbose, "report pruned objects"), + OPT_BOOL(0, "progress", &show_progress, "show progress"), OPT_DATE(0, "expire", &expire, "expire objects older than <time>"), OPT_END() @@ -153,7 +156,14 @@ int cmd_prune(int argc, const char **argv, const char *prefix) else die("unrecognized argument: %s", name); } - mark_reachable_objects(&revs, 1); + + if (show_progress == -1) + show_progress = isatty(2); + if (show_progress) + progress = start_progress_delay("Checking connectivity", 0, 0, 2); + + mark_reachable_objects(&revs, 1, progress); + stop_progress(&progress); prune_object_dir(get_object_directory()); prune_packed_objects(show_only); diff --git a/builtin/push.c b/builtin/push.c index e655eb7695..fdfcc6c716 100644 --- a/builtin/push.c +++ b/builtin/push.c @@ -8,6 +8,7 @@ #include "remote.h" #include "transport.h" #include "parse-options.h" +#include "submodule.h" static const char * const push_usage[] = { "git push [<options>] [<repository> [<refspec>...]]", @@ -18,11 +19,12 @@ static int thin; static int deleterefs; static const char *receivepack; static int verbosity; -static int progress; +static int progress = -1; static const char **refspec; static int refspec_nr; static int refspec_alloc; +static int default_matching_used; static void add_refspec(const char *ref) { @@ -40,7 +42,7 @@ static void set_refspecs(const char **refs, int nr) char *tag; int len; if (nr <= ++i) - die("tag shorthand without <tag>"); + die(_("tag shorthand without <tag>")); len = strlen(refs[i]) + 11; if (deleterefs) { tag = xmalloc(len+1); @@ -59,37 +61,109 @@ static void set_refspecs(const char **refs, int nr) strcat(delref, ref); ref = delref; } else if (deleterefs) - die("--delete only accepts plain target ref names"); + die(_("--delete only accepts plain target ref names")); add_refspec(ref); } } -static void setup_push_tracking(void) +static int push_url_of_remote(struct remote *remote, const char ***url_p) +{ + if (remote->pushurl_nr) { + *url_p = remote->pushurl; + return remote->pushurl_nr; + } + *url_p = remote->url; + return remote->url_nr; +} + +static NORETURN int die_push_simple(struct branch *branch, struct remote *remote) { + /* + * There's no point in using shorten_unambiguous_ref here, + * as the ambiguity would be on the remote side, not what + * we have locally. Plus, this is supposed to be the simple + * mode. If the user is doing something crazy like setting + * upstream to a non-branch, we should probably be showing + * them the big ugly fully qualified ref. + */ + const char *advice_maybe = ""; + const char *short_upstream = + skip_prefix(branch->merge[0]->src, "refs/heads/"); + + if (!short_upstream) + short_upstream = branch->merge[0]->src; + /* + * Don't show advice for people who explicitely set + * push.default. + */ + if (push_default == PUSH_DEFAULT_UNSPECIFIED) + advice_maybe = _("\n" + "To choose either option permanently, " + "see push.default in 'git help config'."); + die(_("The upstream branch of your current branch does not match\n" + "the name of your current branch. To push to the upstream branch\n" + "on the remote, use\n" + "\n" + " git push %s HEAD:%s\n" + "\n" + "To push to the branch of the same name on the remote, use\n" + "\n" + " git push %s %s\n" + "%s"), + remote->name, short_upstream, + remote->name, branch->name, advice_maybe); +} + +static void setup_push_upstream(struct remote *remote, int simple) { struct strbuf refspec = STRBUF_INIT; struct branch *branch = branch_get(NULL); if (!branch) - die("You are not currently on a branch."); - if (!branch->merge_nr || !branch->merge) - die("The current branch %s is not tracking anything.", + die(_("You are not currently on a branch.\n" + "To push the history leading to the current (detached HEAD)\n" + "state now, use\n" + "\n" + " git push %s HEAD:<name-of-remote-branch>\n"), + remote->name); + if (!branch->merge_nr || !branch->merge || !branch->remote_name) + die(_("The current branch %s has no upstream branch.\n" + "To push the current branch and set the remote as upstream, use\n" + "\n" + " git push --set-upstream %s %s\n"), + branch->name, + remote->name, branch->name); if (branch->merge_nr != 1) - die("The current branch %s is tracking multiple branches, " - "refusing to push.", branch->name); + die(_("The current branch %s has multiple upstream branches, " + "refusing to push."), branch->name); + if (strcmp(branch->remote_name, remote->name)) + die(_("You are pushing to remote '%s', which is not the upstream of\n" + "your current branch '%s', without telling me what to push\n" + "to update which remote branch."), + remote->name, branch->name); + if (simple && strcmp(branch->refname, branch->merge[0]->src)) + die_push_simple(branch, remote); + strbuf_addf(&refspec, "%s:%s", branch->name, branch->merge[0]->src); add_refspec(refspec.buf); } -static void setup_default_push_refspecs(void) +static void setup_default_push_refspecs(struct remote *remote) { switch (push_default) { default: + case PUSH_DEFAULT_UNSPECIFIED: + default_matching_used = 1; + /* fallthru */ case PUSH_DEFAULT_MATCHING: add_refspec(":"); break; - case PUSH_DEFAULT_TRACKING: - setup_push_tracking(); + case PUSH_DEFAULT_SIMPLE: + setup_push_upstream(remote, 1); + break; + + case PUSH_DEFAULT_UPSTREAM: + setup_push_upstream(remote, 0); break; case PUSH_DEFAULT_CURRENT: @@ -97,12 +171,51 @@ static void setup_default_push_refspecs(void) break; case PUSH_DEFAULT_NOTHING: - die("You didn't specify any refspecs to push, and " - "push.default is \"nothing\"."); + die(_("You didn't specify any refspecs to push, and " + "push.default is \"nothing\".")); break; } } +static const char message_advice_pull_before_push[] = + N_("Updates were rejected because the tip of your current branch is behind\n" + "its remote counterpart. Merge the remote changes (e.g. 'git pull')\n" + "before pushing again.\n" + "See the 'Note about fast-forwards' in 'git push --help' for details."); + +static const char message_advice_use_upstream[] = + N_("Updates were rejected because a pushed branch tip is behind its remote\n" + "counterpart. If you did not intend to push that branch, you may want to\n" + "specify branches to push or set the 'push.default' configuration\n" + "variable to 'current' or 'upstream' to push only the current branch."); + +static const char message_advice_checkout_pull_push[] = + N_("Updates were rejected because a pushed branch tip is behind its remote\n" + "counterpart. Check out this branch and merge the remote changes\n" + "(e.g. 'git pull') before pushing again.\n" + "See the 'Note about fast-forwards' in 'git push --help' for details."); + +static void advise_pull_before_push(void) +{ + if (!advice_push_non_ff_current || !advice_push_nonfastforward) + return; + advise(_(message_advice_pull_before_push)); +} + +static void advise_use_upstream(void) +{ + if (!advice_push_non_ff_default || !advice_push_nonfastforward) + return; + advise(_(message_advice_use_upstream)); +} + +static void advise_checkout_pull_push(void) +{ + if (!advice_push_non_ff_matching || !advice_push_nonfastforward) + return; + advise(_(message_advice_checkout_pull_push)); +} + static int push_with_options(struct transport *transport, int flags) { int err; @@ -117,21 +230,28 @@ static int push_with_options(struct transport *transport, int flags) transport_set_option(transport, TRANS_OPT_THIN, "yes"); if (verbosity > 0) - fprintf(stderr, "Pushing to %s\n", transport->url); + fprintf(stderr, _("Pushing to %s\n"), transport->url); err = transport_push(transport, refspec_nr, refspec, flags, &nonfastforward); if (err != 0) - error("failed to push some refs to '%s'", transport->url); + error(_("failed to push some refs to '%s'"), transport->url); err |= transport_disconnect(transport); - if (!err) return 0; - if (nonfastforward && advice_push_nonfastforward) { - fprintf(stderr, "To prevent you from losing history, non-fast-forward updates were rejected\n" - "Merge the remote changes (e.g. 'git pull') before pushing again. See the\n" - "'Note about fast-forwards' section of 'git push --help' for details.\n"); + switch (nonfastforward) { + default: + break; + case NON_FF_HEAD: + advise_pull_before_push(); + break; + case NON_FF_OTHER: + if (default_matching_used) + advise_use_upstream(); + else + advise_checkout_pull_push(); + break; } return 1; @@ -146,8 +266,15 @@ static int do_push(const char *repo, int flags) if (!remote) { if (repo) - die("bad repository '%s'", repo); - die("No destination configured to push to."); + die(_("bad repository '%s'"), repo); + die(_("No configured push destination.\n" + "Either specify the URL from the command-line or configure a remote repository using\n" + "\n" + " git remote add <name> <url>\n" + "\n" + "and then push using the remote name\n" + "\n" + " git push <name>\n")); } if (remote->mirror) @@ -155,19 +282,19 @@ static int do_push(const char *repo, int flags) if ((flags & TRANSPORT_PUSH_ALL) && refspec) { if (!strcmp(*refspec, "refs/tags/*")) - return error("--all and --tags are incompatible"); - return error("--all can't be combined with refspecs"); + return error(_("--all and --tags are incompatible")); + return error(_("--all can't be combined with refspecs")); } if ((flags & TRANSPORT_PUSH_MIRROR) && refspec) { if (!strcmp(*refspec, "refs/tags/*")) - return error("--mirror and --tags are incompatible"); - return error("--mirror can't be combined with refspecs"); + return error(_("--mirror and --tags are incompatible")); + return error(_("--mirror can't be combined with refspecs")); } if ((flags & (TRANSPORT_PUSH_ALL|TRANSPORT_PUSH_MIRROR)) == (TRANSPORT_PUSH_ALL|TRANSPORT_PUSH_MIRROR)) { - return error("--all and --mirror are incompatible"); + return error(_("--all and --mirror are incompatible")); } if (!refspec && !(flags & TRANSPORT_PUSH_ALL)) { @@ -175,16 +302,10 @@ static int do_push(const char *repo, int flags) refspec = remote->push_refspec; refspec_nr = remote->push_refspec_nr; } else if (!(flags & TRANSPORT_PUSH_MIRROR)) - setup_default_push_refspecs(); + setup_default_push_refspecs(remote); } errs = 0; - if (remote->pushurl_nr) { - url = remote->pushurl; - url_nr = remote->pushurl_nr; - } else { - url = remote->url; - url_nr = remote->url_nr; - } + url_nr = push_url_of_remote(remote, &url); if (url_nr) { for (i = 0; i < url_nr; i++) { struct transport *transport = @@ -202,6 +323,29 @@ static int do_push(const char *repo, int flags) return !!errs; } +static int option_parse_recurse_submodules(const struct option *opt, + const char *arg, int unset) +{ + int *flags = opt->value; + + if (*flags & (TRANSPORT_RECURSE_SUBMODULES_CHECK | + TRANSPORT_RECURSE_SUBMODULES_ON_DEMAND)) + die("%s can only be used once.", opt->long_name); + + if (arg) { + if (!strcmp(arg, "check")) + *flags |= TRANSPORT_RECURSE_SUBMODULES_CHECK; + else if (!strcmp(arg, "on-demand")) + *flags |= TRANSPORT_RECURSE_SUBMODULES_ON_DEMAND; + else + die("bad %s argument: %s", opt->long_name, arg); + } else + die("option %s needs an argument (check|on-demand)", + opt->long_name); + + return 0; +} + int cmd_push(int argc, const char **argv, const char *prefix) { int flags = 0; @@ -219,22 +363,28 @@ int cmd_push(int argc, const char **argv, const char *prefix) OPT_BIT('n' , "dry-run", &flags, "dry run", TRANSPORT_PUSH_DRY_RUN), OPT_BIT( 0, "porcelain", &flags, "machine-readable output", TRANSPORT_PUSH_PORCELAIN), OPT_BIT('f', "force", &flags, "force updates", TRANSPORT_PUSH_FORCE), + { OPTION_CALLBACK, 0, "recurse-submodules", &flags, "check", + "controls recursive pushing of submodules", + PARSE_OPT_OPTARG, option_parse_recurse_submodules }, OPT_BOOLEAN( 0 , "thin", &thin, "use thin pack"), OPT_STRING( 0 , "receive-pack", &receivepack, "receive-pack", "receive pack program"), OPT_STRING( 0 , "exec", &receivepack, "receive-pack", "receive pack program"), OPT_BIT('u', "set-upstream", &flags, "set upstream for git pull/status", TRANSPORT_PUSH_SET_UPSTREAM), - OPT_BOOLEAN(0, "progress", &progress, "force progress reporting"), + OPT_BOOL(0, "progress", &progress, "force progress reporting"), + OPT_BIT(0, "prune", &flags, "prune locally removed refs", + TRANSPORT_PUSH_PRUNE), OPT_END() }; + packet_trace_identity("push"); git_config(git_default_config, NULL); argc = parse_options(argc, argv, prefix, options, push_usage, 0); if (deleterefs && (tags || (flags & (TRANSPORT_PUSH_ALL | TRANSPORT_PUSH_MIRROR)))) - die("--delete is incompatible with --all, --mirror and --tags"); + die(_("--delete is incompatible with --all, --mirror and --tags")); if (deleterefs && argc < 2) - die("--delete doesn't make sense without any refs"); + die(_("--delete doesn't make sense without any refs")); if (tags) add_refspec("refs/tags/*"); diff --git a/builtin/read-tree.c b/builtin/read-tree.c index eb1e3e7467..df6c4c8819 100644 --- a/builtin/read-tree.c +++ b/builtin/read-tree.c @@ -104,12 +104,12 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix) struct unpack_trees_options opts; int prefix_set = 0; const struct option read_tree_options[] = { - { OPTION_CALLBACK, 0, "index-output", NULL, "FILE", - "write resulting index to <FILE>", + { OPTION_CALLBACK, 0, "index-output", NULL, "file", + "write resulting index to <file>", PARSE_OPT_NONEG, index_output_cb }, OPT_SET_INT(0, "empty", &read_empty, "only empty the index", 1), - OPT__VERBOSE(&opts.verbose_update), + OPT__VERBOSE(&opts.verbose_update, "be verbose"), OPT_GROUP("Merging"), OPT_SET_INT('m', NULL, &opts.merge, "perform a merge in addition to a read", 1), @@ -130,6 +130,7 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix) PARSE_OPT_NONEG, exclude_per_directory_cb }, OPT_SET_INT('i', NULL, &opts.index_only, "don't check the working tree after merging", 1), + OPT__DRY_RUN(&opts.dry_run, "don't update the index or the work tree"), OPT_SET_INT(0, "no-sparse-checkout", &opts.skip_sparse_checkout, "skip applying sparse checkout filter", 1), OPT_SET_INT(0, "debug-unpack", &opts.debug_unpack, @@ -219,7 +220,7 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix) if (unpack_trees(nr_trees, t, &opts)) return 128; - if (opts.debug_unpack) + if (opts.debug_unpack || opts.dry_run) return 0; /* do not write the index out */ /* diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c index 760817dbd7..0afb8b2896 100644 --- a/builtin/receive-pack.c +++ b/builtin/receive-pack.c @@ -1,4 +1,4 @@ -#include "cache.h" +#include "builtin.h" #include "pack.h" #include "refs.h" #include "pkt-line.h" @@ -10,6 +10,8 @@ #include "remote.h" #include "transport.h" #include "string-list.h" +#include "sha1-array.h" +#include "connected.h" static const char receive_pack_usage[] = "git receive-pack <git-dir>"; @@ -24,16 +26,19 @@ static int deny_deletes; static int deny_non_fast_forwards; static enum deny_action deny_current_branch = DENY_UNCONFIGURED; static enum deny_action deny_delete_current = DENY_UNCONFIGURED; -static int receive_fsck_objects; +static int receive_fsck_objects = -1; +static int transfer_fsck_objects = -1; static int receive_unpack_limit = -1; static int transfer_unpack_limit = -1; static int unpack_limit = 100; static int report_status; static int use_sideband; +static int quiet; static int prefer_ofs_delta = 1; static int auto_update_server_info; static int auto_gc = 1; static const char *head_name; +static void *head_name_to_free; static int sent_capabilities; static enum deny_action parse_deny_action(const char *var, const char *value) @@ -78,6 +83,11 @@ static int receive_pack_config(const char *var, const char *value, void *cb) return 0; } + if (strcmp(var, "transfer.fsckobjects") == 0) { + transfer_fsck_objects = git_config_bool(var, value); + return 0; + } + if (!strcmp(var, "receive.denycurrentbranch")) { deny_current_branch = parse_deny_action(var, value); return 0; @@ -106,31 +116,65 @@ static int receive_pack_config(const char *var, const char *value, void *cb) return git_default_config(var, value, cb); } -static int show_ref(const char *path, const unsigned char *sha1, int flag, void *cb_data) +static void show_ref(const char *path, const unsigned char *sha1) { if (sent_capabilities) packet_write(1, "%s %s\n", sha1_to_hex(sha1), path); else packet_write(1, "%s %s%c%s%s\n", sha1_to_hex(sha1), path, 0, - " report-status delete-refs side-band-64k", + " report-status delete-refs side-band-64k quiet", prefer_ofs_delta ? " ofs-delta" : ""); sent_capabilities = 1; +} + +static int show_ref_cb(const char *path, const unsigned char *sha1, int flag, void *unused) +{ + path = strip_namespace(path); + /* + * Advertise refs outside our current namespace as ".have" + * refs, so that the client can use them to minimize data + * transfer but will otherwise ignore them. This happens to + * cover ".have" that are thrown in by add_one_alternate_ref() + * to mark histories that are complete in our alternates as + * well. + */ + if (!path) + path = ".have"; + show_ref(path, sha1); return 0; } +static void show_one_alternate_sha1(const unsigned char sha1[20], void *unused) +{ + show_ref(".have", sha1); +} + +static void collect_one_alternate_ref(const struct ref *ref, void *data) +{ + struct sha1_array *sa = data; + sha1_array_append(sa, ref->old_sha1); +} + static void write_head_info(void) { - for_each_ref(show_ref, NULL); + struct sha1_array sa = SHA1_ARRAY_INIT; + for_each_alternate_ref(collect_one_alternate_ref, &sa); + sha1_array_for_each_unique(&sa, show_one_alternate_sha1, NULL); + sha1_array_clear(&sa); + for_each_ref(show_ref_cb, NULL); if (!sent_capabilities) - show_ref("capabilities^{}", null_sha1, 0, NULL); + show_ref("capabilities^{}", null_sha1); + /* EOF */ + packet_flush(1); } struct command { struct command *next; const char *error_string; - unsigned int skip_update; + unsigned int skip_update:1, + did_not_exist:1; unsigned char old_sha1[20]; unsigned char new_sha1[20]; char ref_name[FLEX_ARRAY]; /* more */ @@ -188,21 +232,15 @@ static int copy_to_sideband(int in, int out, void *arg) return 0; } -static int run_receive_hook(struct command *commands, const char *hook_name) +typedef int (*feed_fn)(void *, const char **, size_t *); +static int run_and_feed_hook(const char *hook_name, feed_fn feed, void *feed_state) { - static char buf[sizeof(commands->old_sha1) * 2 + PATH_MAX + 4]; - struct command *cmd; struct child_process proc; struct async muxer; const char *argv[2]; - int have_input = 0, code; - - for (cmd = commands; !have_input && cmd; cmd = cmd->next) { - if (!cmd->error_string) - have_input = 1; - } + int code; - if (!have_input || access(hook_name, X_OK) < 0) + if (access(hook_name, X_OK) < 0) return 0; argv[0] = hook_name; @@ -230,15 +268,13 @@ static int run_receive_hook(struct command *commands, const char *hook_name) return code; } - for (cmd = commands; cmd; cmd = cmd->next) { - if (!cmd->error_string) { - size_t n = snprintf(buf, sizeof(buf), "%s %s %s\n", - sha1_to_hex(cmd->old_sha1), - sha1_to_hex(cmd->new_sha1), - cmd->ref_name); - if (write_in_full(proc.in, buf, n) != n) - break; - } + while (1) { + const char *buf; + size_t n; + if (feed(feed_state, &buf, &n)) + break; + if (write_in_full(proc.in, buf, n) != n) + break; } close(proc.in); if (use_sideband) @@ -246,6 +282,51 @@ static int run_receive_hook(struct command *commands, const char *hook_name) return finish_command(&proc); } +struct receive_hook_feed_state { + struct command *cmd; + int skip_broken; + struct strbuf buf; +}; + +static int feed_receive_hook(void *state_, const char **bufp, size_t *sizep) +{ + struct receive_hook_feed_state *state = state_; + struct command *cmd = state->cmd; + + while (cmd && + state->skip_broken && (cmd->error_string || cmd->did_not_exist)) + cmd = cmd->next; + if (!cmd) + return -1; /* EOF */ + strbuf_reset(&state->buf); + strbuf_addf(&state->buf, "%s %s %s\n", + sha1_to_hex(cmd->old_sha1), sha1_to_hex(cmd->new_sha1), + cmd->ref_name); + state->cmd = cmd->next; + if (bufp) { + *bufp = state->buf.buf; + *sizep = state->buf.len; + } + return 0; +} + +static int run_receive_hook(struct command *commands, const char *hook_name, + int skip_broken) +{ + struct receive_hook_feed_state state; + int status; + + strbuf_init(&state.buf, 0); + state.cmd = commands; + state.skip_broken = skip_broken; + if (feed_receive_hook(&state, NULL, NULL)) + return 0; + state.cmd = commands; + status = run_and_feed_hook(hook_name, feed_receive_hook, &state); + strbuf_release(&state.buf); + return status; +} + static int run_update_hook(struct command *cmd) { static const char update_hook[] = "hooks/update"; @@ -332,17 +413,22 @@ static void refuse_unconfigured_deny_delete_current(void) static const char *update(struct command *cmd) { const char *name = cmd->ref_name; + struct strbuf namespaced_name_buf = STRBUF_INIT; + const char *namespaced_name; unsigned char *old_sha1 = cmd->old_sha1; unsigned char *new_sha1 = cmd->new_sha1; struct ref_lock *lock; /* only refs/... are allowed */ - if (prefixcmp(name, "refs/") || check_ref_format(name + 5)) { + if (prefixcmp(name, "refs/") || check_refname_format(name + 5, 0)) { rp_error("refusing to create funny ref '%s' remotely", name); return "funny refname"; } - if (is_ref_checked_out(name)) { + strbuf_addf(&namespaced_name_buf, "%s%s", get_git_namespace(), name); + namespaced_name = strbuf_detach(&namespaced_name_buf, NULL); + + if (is_ref_checked_out(namespaced_name)) { switch (deny_current_branch) { case DENY_IGNORE: break; @@ -370,7 +456,7 @@ static const char *update(struct command *cmd) return "deletion prohibited"; } - if (!strcmp(name, head_name)) { + if (!strcmp(namespaced_name, head_name)) { switch (deny_delete_current) { case DENY_IGNORE: break; @@ -423,17 +509,22 @@ static const char *update(struct command *cmd) if (is_null_sha1(new_sha1)) { if (!parse_object(old_sha1)) { - rp_warning("Allowing deletion of corrupt ref."); old_sha1 = NULL; + if (ref_exists(name)) { + rp_warning("Allowing deletion of corrupt ref."); + } else { + rp_warning("Deleting a non-existent ref."); + cmd->did_not_exist = 1; + } } - if (delete_ref(name, old_sha1, 0)) { + if (delete_ref(namespaced_name, old_sha1, 0)) { rp_error("failed to delete %s", name); return "failed to delete"; } return NULL; /* good */ } else { - lock = lock_any_ref_for_update(name, old_sha1, 0); + lock = lock_any_ref_for_update(namespaced_name, old_sha1, 0); if (!lock) { rp_error("failed to lock %s", name); return "failed to lock"; @@ -455,7 +546,7 @@ static void run_update_post_hook(struct command *commands) struct child_process proc; for (argc = 0, cmd = commands; cmd; cmd = cmd->next) { - if (cmd->error_string) + if (cmd->error_string || cmd->did_not_exist) continue; argc++; } @@ -466,7 +557,7 @@ static void run_update_post_hook(struct command *commands) for (argc = 1, cmd = commands; cmd; cmd = cmd->next) { char *p; - if (cmd->error_string) + if (cmd->error_string || cmd->did_not_exist) continue; p = xmalloc(strlen(cmd->ref_name) + 1); strcpy(p, cmd->ref_name); @@ -490,17 +581,29 @@ static void run_update_post_hook(struct command *commands) static void check_aliased_update(struct command *cmd, struct string_list *list) { + struct strbuf buf = STRBUF_INIT; + const char *dst_name; struct string_list_item *item; struct command *dst_cmd; unsigned char sha1[20]; char cmd_oldh[41], cmd_newh[41], dst_oldh[41], dst_newh[41]; int flag; - const char *dst_name = resolve_ref(cmd->ref_name, sha1, 0, &flag); + strbuf_addf(&buf, "%s%s", get_git_namespace(), cmd->ref_name); + dst_name = resolve_ref_unsafe(buf.buf, sha1, 0, &flag); + strbuf_release(&buf); if (!(flag & REF_ISSYMREF)) return; + dst_name = strip_namespace(dst_name); + if (!dst_name) { + rp_error("refusing update to broken symref '%s'", cmd->ref_name); + cmd->skip_update = 1; + cmd->error_string = "broken symref"; + return; + } + if ((item = string_list_lookup(list, dst_name)) == NULL) return; @@ -539,12 +642,56 @@ static void check_aliased_updates(struct command *commands) } sort_string_list(&ref_list); - for (cmd = commands; cmd; cmd = cmd->next) - check_aliased_update(cmd, &ref_list); + for (cmd = commands; cmd; cmd = cmd->next) { + if (!cmd->error_string) + check_aliased_update(cmd, &ref_list); + } string_list_clear(&ref_list, 0); } +static int command_singleton_iterator(void *cb_data, unsigned char sha1[20]) +{ + struct command **cmd_list = cb_data; + struct command *cmd = *cmd_list; + + if (!cmd || is_null_sha1(cmd->new_sha1)) + return -1; /* end of list */ + *cmd_list = NULL; /* this returns only one */ + hashcpy(sha1, cmd->new_sha1); + return 0; +} + +static void set_connectivity_errors(struct command *commands) +{ + struct command *cmd; + + for (cmd = commands; cmd; cmd = cmd->next) { + struct command *singleton = cmd; + if (!check_everything_connected(command_singleton_iterator, + 0, &singleton)) + continue; + cmd->error_string = "missing necessary objects"; + } +} + +static int iterate_receive_command_list(void *cb_data, unsigned char sha1[20]) +{ + struct command **cmd_list = cb_data; + struct command *cmd = *cmd_list; + + while (cmd) { + if (!is_null_sha1(cmd->new_sha1)) { + hashcpy(sha1, cmd->new_sha1); + *cmd_list = cmd->next; + return 0; + } + cmd = cmd->next; + } + *cmd_list = NULL; + return -1; /* end of list */ +} + static void execute_commands(struct command *commands, const char *unpacker_error) { struct command *cmd; @@ -556,19 +703,33 @@ static void execute_commands(struct command *commands, const char *unpacker_erro return; } - if (run_receive_hook(commands, pre_receive_hook)) { - for (cmd = commands; cmd; cmd = cmd->next) - cmd->error_string = "pre-receive hook declined"; + cmd = commands; + if (check_everything_connected(iterate_receive_command_list, + 0, &cmd)) + set_connectivity_errors(commands); + + if (run_receive_hook(commands, pre_receive_hook, 0)) { + for (cmd = commands; cmd; cmd = cmd->next) { + if (!cmd->error_string) + cmd->error_string = "pre-receive hook declined"; + } return; } check_aliased_updates(commands); - head_name = resolve_ref("HEAD", sha1, 0, NULL); + free(head_name_to_free); + head_name = head_name_to_free = resolve_refdup("HEAD", sha1, 0, NULL); - for (cmd = commands; cmd; cmd = cmd->next) - if (!cmd->skip_update) - cmd->error_string = update(cmd); + for (cmd = commands; cmd; cmd = cmd->next) { + if (cmd->error_string) + continue; + + if (cmd->skip_update) + continue; + + cmd->error_string = update(cmd); + } } static struct command *read_head_info(void) @@ -598,10 +759,13 @@ static struct command *read_head_info(void) refname = line + 82; reflen = strlen(refname); if (reflen + 82 < len) { - if (strstr(refname + reflen + 1, "report-status")) + const char *feature_list = refname + reflen + 1; + if (parse_feature_request(feature_list, "report-status")) report_status = 1; - if (strstr(refname + reflen + 1, "side-band-64k")) + if (parse_feature_request(feature_list, "side-band-64k")) use_sideband = LARGE_PACKET_MAX; + if (parse_feature_request(feature_list, "quiet")) + quiet = 1; } cmd = xcalloc(1, sizeof(struct command) + len - 80); hashcpy(cmd->old_sha1, old_sha1); @@ -640,6 +804,11 @@ static const char *unpack(void) struct pack_header hdr; const char *hdr_err; char hdr_arg[38]; + int fsck_objects = (receive_fsck_objects >= 0 + ? receive_fsck_objects + : transfer_fsck_objects >= 0 + ? transfer_fsck_objects + : 0); hdr_err = parse_pack_header(&hdr); if (hdr_err) @@ -650,9 +819,11 @@ static const char *unpack(void) if (ntohl(hdr.hdr_entries) < unpack_limit) { int code, i = 0; - const char *unpacker[4]; + const char *unpacker[5]; unpacker[i++] = "unpack-objects"; - if (receive_fsck_objects) + if (quiet) + unpacker[i++] = "-q"; + if (fsck_objects) unpacker[i++] = "--strict"; unpacker[i++] = hdr_arg; unpacker[i++] = NULL; @@ -672,7 +843,7 @@ static const char *unpack(void) keeper[i++] = "index-pack"; keeper[i++] = "--stdin"; - if (receive_fsck_objects) + if (fsck_objects) keeper[i++] = "--strict"; keeper[i++] = "--fix-thin"; keeper[i++] = hdr_arg; @@ -731,45 +902,6 @@ static int delete_only(struct command *commands) return 1; } -static int add_refs_from_alternate(struct alternate_object_database *e, void *unused) -{ - char *other; - size_t len; - struct remote *remote; - struct transport *transport; - const struct ref *extra; - - e->name[-1] = '\0'; - other = xstrdup(make_absolute_path(e->base)); - e->name[-1] = '/'; - len = strlen(other); - - while (other[len-1] == '/') - other[--len] = '\0'; - if (len < 8 || memcmp(other + len - 8, "/objects", 8)) - return 0; - /* Is this a git repository with refs? */ - memcpy(other + len - 8, "/refs", 6); - if (!is_directory(other)) - return 0; - other[len - 8] = '\0'; - remote = remote_get(other); - transport = transport_get(remote, other); - for (extra = transport_get_remote_refs(transport); - extra; - extra = extra->next) { - add_extra_ref(".have", extra->old_sha1, 0); - } - transport_disconnect(transport); - free(other); - return 0; -} - -static void add_alternate_refs(void) -{ - foreach_alt_odb(add_refs_from_alternate, NULL); -} - int cmd_receive_pack(int argc, const char **argv, const char *prefix) { int advertise_refs = 0; @@ -778,11 +910,18 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix) char *dir = NULL; struct command *commands; + packet_trace_identity("receive-pack"); + argv++; for (i = 1; i < argc; i++) { const char *arg = *argv++; if (*arg == '-') { + if (!strcmp(arg, "--quiet")) { + quiet = 1; + continue; + } + if (!strcmp(arg, "--advertise-refs")) { advertise_refs = 1; continue; @@ -817,12 +956,7 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix) unpack_limit = receive_unpack_limit; if (advertise_refs || !stateless_rpc) { - add_alternate_refs(); write_head_info(); - clear_extra_refs(); - - /* EOF */ - packet_flush(1); } if (advertise_refs) return 0; @@ -837,7 +971,7 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix) unlink_or_warn(pack_lockfile); if (report_status) report(commands, unpack_status); - run_receive_hook(commands, post_receive_hook); + run_receive_hook(commands, post_receive_hook, 1); run_update_post_hook(commands); if (auto_gc) { const char *argv_gc_auto[] = { diff --git a/builtin/reflog.c b/builtin/reflog.c index ebf610e64a..b3c9e27bde 100644 --- a/builtin/reflog.c +++ b/builtin/reflog.c @@ -330,8 +330,10 @@ static int expire_reflog_ent(unsigned char *osha1, unsigned char *nsha1, printf("keep %s", message); return 0; prune: - if (!cb->newlog || cb->cmd->verbose) - printf("%sprune %s", cb->newlog ? "" : "would ", message); + if (!cb->newlog) + printf("would prune %s", message); + else if (cb->cmd->verbose) + printf("prune %s", message); return 0; } @@ -647,7 +649,7 @@ static int cmd_reflog_expire(int argc, const char **argv, const char *prefix) init_revisions(&cb.revs, prefix); if (cb.verbose) printf("Marking reachable objects..."); - mark_reachable_objects(&cb.revs, 0); + mark_reachable_objects(&cb.revs, 0, NULL); if (cb.verbose) putchar('\n'); } @@ -777,6 +779,5 @@ int cmd_reflog(int argc, const char **argv, const char *prefix) if (!strcmp(argv[1], "delete")) return cmd_reflog_delete(argc - 1, argv + 1, prefix); - /* Not a recognized reflog command..*/ - usage(reflog_usage); + return cmd_log_reflog(argc, argv, prefix); } diff --git a/builtin/remote-ext.c b/builtin/remote-ext.c new file mode 100644 index 0000000000..692c834d9d --- /dev/null +++ b/builtin/remote-ext.c @@ -0,0 +1,242 @@ +#include "builtin.h" +#include "transport.h" +#include "run-command.h" + +/* + * URL syntax: + * 'command [arg1 [arg2 [...]]]' Invoke command with given arguments. + * Special characters: + * '% ': Literal space in argument. + * '%%': Literal percent sign. + * '%S': Name of service (git-upload-pack/git-upload-archive/ + * git-receive-pack. + * '%s': Same as \s, but with possible git- prefix stripped. + * '%G': Only allowed as first 'character' of argument. Do not pass this + * Argument to command, instead send this as name of repository + * in in-line git://-style request (also activates sending this + * style of request). + * '%V': Only allowed as first 'character' of argument. Used in + * conjunction with '%G': Do not pass this argument to command, + * instead send this as vhost in git://-style request (note: does + * not activate sending git:// style request). + */ + +static char *git_req; +static char *git_req_vhost; + +static char *strip_escapes(const char *str, const char *service, + const char **next) +{ + size_t rpos = 0; + int escape = 0; + char special = 0; + size_t psoff = 0; + struct strbuf ret = STRBUF_INIT; + + /* Calculate prefix length for \s and lengths for \s and \S */ + if (!strncmp(service, "git-", 4)) + psoff = 4; + + /* Pass the service to command. */ + setenv("GIT_EXT_SERVICE", service, 1); + setenv("GIT_EXT_SERVICE_NOPREFIX", service + psoff, 1); + + /* Scan the length of argument. */ + while (str[rpos] && (escape || str[rpos] != ' ')) { + if (escape) { + switch (str[rpos]) { + case ' ': + case '%': + case 's': + case 'S': + break; + case 'G': + case 'V': + special = str[rpos]; + if (rpos == 1) + break; + /* Fall-through to error. */ + default: + die("Bad remote-ext placeholder '%%%c'.", + str[rpos]); + } + escape = 0; + } else + escape = (str[rpos] == '%'); + rpos++; + } + if (escape && !str[rpos]) + die("remote-ext command has incomplete placeholder"); + *next = str + rpos; + if (**next == ' ') + ++*next; /* Skip over space */ + + /* + * Do the actual placeholder substitution. The string will be short + * enough not to overflow integers. + */ + rpos = special ? 2 : 0; /* Skip first 2 bytes in specials. */ + escape = 0; + while (str[rpos] && (escape || str[rpos] != ' ')) { + if (escape) { + switch (str[rpos]) { + case ' ': + case '%': + strbuf_addch(&ret, str[rpos]); + break; + case 's': + strbuf_addstr(&ret, service + psoff); + break; + case 'S': + strbuf_addstr(&ret, service); + break; + } + escape = 0; + } else + switch (str[rpos]) { + case '%': + escape = 1; + break; + default: + strbuf_addch(&ret, str[rpos]); + break; + } + rpos++; + } + switch (special) { + case 'G': + git_req = strbuf_detach(&ret, NULL); + return NULL; + case 'V': + git_req_vhost = strbuf_detach(&ret, NULL); + return NULL; + default: + return strbuf_detach(&ret, NULL); + } +} + +/* Should be enough... */ +#define MAXARGUMENTS 256 + +static const char **parse_argv(const char *arg, const char *service) +{ + int arguments = 0; + int i; + const char **ret; + char *temparray[MAXARGUMENTS + 1]; + + while (*arg) { + char *expanded; + if (arguments == MAXARGUMENTS) + die("remote-ext command has too many arguments"); + expanded = strip_escapes(arg, service, &arg); + if (expanded) + temparray[arguments++] = expanded; + } + + ret = xmalloc((arguments + 1) * sizeof(char *)); + for (i = 0; i < arguments; i++) + ret[i] = temparray[i]; + ret[arguments] = NULL; + return ret; +} + +static void send_git_request(int stdin_fd, const char *serv, const char *repo, + const char *vhost) +{ + size_t bufferspace; + size_t wpos = 0; + char *buffer; + + /* + * Request needs 12 bytes extra if there is vhost (xxxx \0host=\0) and + * 6 bytes extra (xxxx \0) if there is no vhost. + */ + if (vhost) + bufferspace = strlen(serv) + strlen(repo) + strlen(vhost) + 12; + else + bufferspace = strlen(serv) + strlen(repo) + 6; + + if (bufferspace > 0xFFFF) + die("Request too large to send"); + buffer = xmalloc(bufferspace); + + /* Make the packet. */ + wpos = sprintf(buffer, "%04x%s %s%c", (unsigned)bufferspace, + serv, repo, 0); + + /* Add vhost if any. */ + if (vhost) + sprintf(buffer + wpos, "host=%s%c", vhost, 0); + + /* Send the request */ + if (write_in_full(stdin_fd, buffer, bufferspace) < 0) + die_errno("Failed to send request"); + + free(buffer); +} + +static int run_child(const char *arg, const char *service) +{ + int r; + struct child_process child; + + memset(&child, 0, sizeof(child)); + child.in = -1; + child.out = -1; + child.err = 0; + child.argv = parse_argv(arg, service); + + if (start_command(&child) < 0) + die("Can't run specified command"); + + if (git_req) + send_git_request(child.in, service, git_req, git_req_vhost); + + r = bidirectional_transfer_loop(child.out, child.in); + if (!r) + r = finish_command(&child); + else + finish_command(&child); + return r; +} + +#define MAXCOMMAND 4096 + +static int command_loop(const char *child) +{ + char buffer[MAXCOMMAND]; + + while (1) { + size_t i; + if (!fgets(buffer, MAXCOMMAND - 1, stdin)) { + if (ferror(stdin)) + die("Comammand input error"); + exit(0); + } + /* Strip end of line characters. */ + i = strlen(buffer); + while (i > 0 && isspace(buffer[i - 1])) + buffer[--i] = 0; + + if (!strcmp(buffer, "capabilities")) { + printf("*connect\n\n"); + fflush(stdout); + } else if (!strncmp(buffer, "connect ", 8)) { + printf("\n"); + fflush(stdout); + return run_child(child, buffer + 8); + } else { + fprintf(stderr, "Bad command"); + return 1; + } + } +} + +int cmd_remote_ext(int argc, const char **argv, const char *prefix) +{ + if (argc != 3) + die("Expected two arguments"); + + return command_loop(argv[2]); +} diff --git a/builtin/remote-fd.c b/builtin/remote-fd.c new file mode 100644 index 0000000000..08d7121b6d --- /dev/null +++ b/builtin/remote-fd.c @@ -0,0 +1,79 @@ +#include "builtin.h" +#include "transport.h" + +/* + * URL syntax: + * 'fd::<inoutfd>[/<anything>]' Read/write socket pair + * <inoutfd>. + * 'fd::<infd>,<outfd>[/<anything>]' Read pipe <infd> and write + * pipe <outfd>. + * [foo] indicates 'foo' is optional. <anything> is any string. + * + * The data output to <outfd>/<inoutfd> should be passed unmolested to + * git-receive-pack/git-upload-pack/git-upload-archive and output of + * git-receive-pack/git-upload-pack/git-upload-archive should be passed + * unmolested to <infd>/<inoutfd>. + * + */ + +#define MAXCOMMAND 4096 + +static void command_loop(int input_fd, int output_fd) +{ + char buffer[MAXCOMMAND]; + + while (1) { + size_t i; + if (!fgets(buffer, MAXCOMMAND - 1, stdin)) { + if (ferror(stdin)) + die("Input error"); + return; + } + /* Strip end of line characters. */ + i = strlen(buffer); + while (i > 0 && isspace(buffer[i - 1])) + buffer[--i] = 0; + + if (!strcmp(buffer, "capabilities")) { + printf("*connect\n\n"); + fflush(stdout); + } else if (!strncmp(buffer, "connect ", 8)) { + printf("\n"); + fflush(stdout); + if (bidirectional_transfer_loop(input_fd, + output_fd)) + die("Copying data between file descriptors failed"); + return; + } else { + die("Bad command: %s", buffer); + } + } +} + +int cmd_remote_fd(int argc, const char **argv, const char *prefix) +{ + int input_fd = -1; + int output_fd = -1; + char *end; + + if (argc != 3) + die("Expected two arguments"); + + input_fd = (int)strtoul(argv[2], &end, 10); + + if ((end == argv[2]) || (*end != ',' && *end != '/' && *end)) + die("Bad URL syntax"); + + if (*end == '/' || !*end) { + output_fd = input_fd; + } else { + char *end2; + output_fd = (int)strtoul(end + 1, &end2, 10); + + if ((end2 == end + 1) || (*end2 != '/' && *end2)) + die("Bad URL syntax"); + } + + command_loop(input_fd, output_fd); + return 0; +} diff --git a/builtin/remote.c b/builtin/remote.c index e9a6e09257..920262d76e 100644 --- a/builtin/remote.c +++ b/builtin/remote.c @@ -1,4 +1,4 @@ -#include "cache.h" +#include "builtin.h" #include "parse-options.h" #include "transport.h" #include "remote.h" @@ -9,15 +9,15 @@ static const char * const builtin_remote_usage[] = { "git remote [-v | --verbose]", - "git remote add [-t <branch>] [-m <master>] [-f] [--mirror] <name> <url>", + "git remote add [-t <branch>] [-m <master>] [-f] [--tags|--no-tags] [--mirror=<fetch|push>] <name> <url>", "git remote rename <old> <new>", "git remote rm <name>", "git remote set-head <name> (-a | -d | <branch>)", "git remote [-v | --verbose] show [-n] <name>", "git remote prune [-n | --dry-run] <name>", "git remote [-v | --verbose] update [-p | --prune] [(<group> | <remote>)...]", - "git remote set-branches <name> [--add] <branch>...", - "git remote set-url <name> <newurl> [<oldurl>]", + "git remote set-branches [--add] <name> <branch>...", + "git remote set-url [--push] <name> <newurl> [<oldurl>]", "git remote set-url --add <name> <newurl>", "git remote set-url --delete <name> <url>", NULL @@ -88,16 +88,6 @@ static inline int postfixcmp(const char *string, const char *postfix) return strcmp(string + len1 - len2, postfix); } -static int opt_parse_track(const struct option *opt, const char *arg, int not) -{ - struct string_list *list = opt->value; - if (not) - string_list_clear(list, 0); - else - string_list_append(list, arg); - return 0; -} - static int fetch_remote(const char *name) { const char *argv[] = { "fetch", name, NULL, NULL }; @@ -105,9 +95,9 @@ static int fetch_remote(const char *name) argv[1] = "-v"; argv[2] = name; } - printf("Updating %s\n", name); + printf_ln(_("Updating %s"), name); if (run_command_v_opt(argv, RUN_GIT_CMD)) - return error("Could not fetch %s", name); + return error(_("Could not fetch %s"), name); return 0; } @@ -117,6 +107,11 @@ enum { TAGS_SET = 2 }; +#define MIRROR_NONE 0 +#define MIRROR_FETCH 1 +#define MIRROR_PUSH 2 +#define MIRROR_BOTH (MIRROR_FETCH|MIRROR_PUSH) + static int add_branch(const char *key, const char *branchname, const char *remotename, int mirror, struct strbuf *tmp) { @@ -131,9 +126,32 @@ static int add_branch(const char *key, const char *branchname, return git_config_set_multivar(key, tmp->buf, "^$", 0); } +static const char mirror_advice[] = +N_("--mirror is dangerous and deprecated; please\n" + "\t use --mirror=fetch or --mirror=push instead"); + +static int parse_mirror_opt(const struct option *opt, const char *arg, int not) +{ + unsigned *mirror = opt->value; + if (not) + *mirror = MIRROR_NONE; + else if (!arg) { + warning("%s", _(mirror_advice)); + *mirror = MIRROR_BOTH; + } + else if (!strcmp(arg, "fetch")) + *mirror = MIRROR_FETCH; + else if (!strcmp(arg, "push")) + *mirror = MIRROR_PUSH; + else + return error(_("unknown mirror argument: %s"), arg); + return 0; +} + static int add(int argc, const char **argv) { - int fetch = 0, mirror = 0, fetch_tags = TAGS_DEFAULT; + int fetch = 0, fetch_tags = TAGS_DEFAULT; + unsigned mirror = MIRROR_NONE; struct string_list track = STRING_LIST_INIT_NODUP; const char *master = NULL; struct remote *remote; @@ -148,10 +166,12 @@ static int add(int argc, const char **argv) TAGS_SET), OPT_SET_INT(0, NULL, &fetch_tags, "or do not fetch any tag at all (--no-tags)", TAGS_UNSET), - OPT_CALLBACK('t', "track", &track, "branch", - "branch(es) to track", opt_parse_track), + OPT_STRING_LIST('t', "track", &track, "branch", + "branch(es) to track"), OPT_STRING('m', "master", &master, "branch", "master branch"), - OPT_BOOLEAN(0, "mirror", &mirror, "no separate remotes"), + { OPTION_CALLBACK, 0, "mirror", &mirror, "push|fetch", + "set up remote as a mirror to push to or fetch from", + PARSE_OPT_OPTARG, parse_mirror_opt }, OPT_END() }; @@ -161,34 +181,40 @@ static int add(int argc, const char **argv) if (argc < 2) usage_with_options(builtin_remote_add_usage, options); + if (mirror && master) + die(_("specifying a master branch makes no sense with --mirror")); + if (mirror && !(mirror & MIRROR_FETCH) && track.nr) + die(_("specifying branches to track makes sense only with fetch mirrors")); + name = argv[0]; url = argv[1]; remote = remote_get(name); if (remote && (remote->url_nr > 1 || strcmp(name, remote->url[0]) || remote->fetch_refspec_nr)) - die("remote %s already exists.", name); + die(_("remote %s already exists."), name); strbuf_addf(&buf2, "refs/heads/test:refs/remotes/%s/test", name); if (!valid_fetch_refspec(buf2.buf)) - die("'%s' is not a valid remote name", name); + die(_("'%s' is not a valid remote name"), name); strbuf_addf(&buf, "remote.%s.url", name); if (git_config_set(buf.buf, url)) return 1; - strbuf_reset(&buf); - strbuf_addf(&buf, "remote.%s.fetch", name); - - if (track.nr == 0) - string_list_append(&track, "*"); - for (i = 0; i < track.nr; i++) { - if (add_branch(buf.buf, track.items[i].string, - name, mirror, &buf2)) - return 1; + if (!mirror || mirror & MIRROR_FETCH) { + strbuf_reset(&buf); + strbuf_addf(&buf, "remote.%s.fetch", name); + if (track.nr == 0) + string_list_append(&track, "*"); + for (i = 0; i < track.nr; i++) { + if (add_branch(buf.buf, track.items[i].string, + name, mirror, &buf2)) + return 1; + } } - if (mirror) { + if (mirror & MIRROR_PUSH) { strbuf_reset(&buf); strbuf_addf(&buf, "remote.%s.mirror", name); if (git_config_set(buf.buf, "true")) @@ -214,7 +240,7 @@ static int add(int argc, const char **argv) strbuf_addf(&buf2, "refs/remotes/%s/%s", name, master); if (create_symref(buf.buf, buf2.buf, "remote add")) - return error("Could not setup master '%s'", master); + return error(_("Could not setup master '%s'"), master); } strbuf_release(&buf); @@ -270,7 +296,7 @@ static int config_read_branches(const char *key, const char *value, void *cb) info = item->util; if (type == REMOTE) { if (info->remote_name) - warning("more than one %s", orig_key); + warning(_("more than one %s"), orig_key); info->remote_name = xstrdup(value); } else if (type == MERGE) { char *space = strchr(value, ' '); @@ -310,20 +336,20 @@ static int get_ref_states(const struct ref *remote_refs, struct ref_states *stat for (i = 0; i < states->remote->fetch_refspec_nr; i++) if (get_fetch_map(remote_refs, states->remote->fetch + i, &tail, 1)) - die("Could not get fetch map for refspec %s", + die(_("Could not get fetch map for refspec %s"), states->remote->fetch_refspec[i]); states->new.strdup_strings = 1; states->tracked.strdup_strings = 1; states->stale.strdup_strings = 1; for (ref = fetch_map; ref; ref = ref->next) { - unsigned char sha1[20]; - if (!ref->peer_ref || read_ref(ref->peer_ref->name, sha1)) + if (!ref->peer_ref || !ref_exists(ref->peer_ref->name)) string_list_append(&states->new, abbrev_branch(ref->name)); else string_list_append(&states->tracked, abbrev_branch(ref->name)); } - stale_refs = get_stale_heads(states->remote, fetch_map); + stale_refs = get_stale_heads(states->remote->fetch, + states->remote->fetch_refspec_nr, fetch_map); for (ref = stale_refs; ref; ref = ref->next) { struct string_list_item *item = string_list_append(&states->stale, abbrev_branch(ref->name)); @@ -363,8 +389,8 @@ static int get_push_ref_states(const struct ref *remote_refs, local_refs = get_local_heads(); push_map = copy_ref_list(remote_refs); - match_refs(local_refs, &push_map, remote->push_refspec_nr, - remote->push_refspec, MATCH_REFS_NONE); + match_push_refs(local_refs, &push_map, remote->push_refspec_nr, + remote->push_refspec, MATCH_REFS_NONE); states->push.strdup_strings = 1; for (ref = push_map; ref; ref = ref->next) { @@ -411,7 +437,7 @@ static int get_push_ref_states_noquery(struct ref_states *states) states->push.strdup_strings = 1; if (!remote->push_refspec_nr) { - item = string_list_append(&states->push, "(matching)"); + item = string_list_append(&states->push, _("(matching)")); info = item->util = xcalloc(sizeof(struct push_info), 1); info->status = PUSH_STATUS_NOTQUERIED; info->dest = xstrdup(item->string); @@ -419,11 +445,11 @@ static int get_push_ref_states_noquery(struct ref_states *states) for (i = 0; i < remote->push_refspec_nr; i++) { struct refspec *spec = remote->push + i; if (spec->matching) - item = string_list_append(&states->push, "(matching)"); + item = string_list_append(&states->push, _("(matching)")); else if (strlen(spec->src)) item = string_list_append(&states->push, spec->src); else - item = string_list_append(&states->push, "(delete)"); + item = string_list_append(&states->push, _("(delete)")); info = item->util = xcalloc(sizeof(struct push_info), 1); info->forced = spec->force; @@ -507,8 +533,8 @@ static int add_branch_for_removal(const char *refname, return 0; } - /* don't delete non-remote refs */ - if (prefixcmp(refname, "refs/remotes")) { + /* don't delete non-remote-tracking refs */ + if (prefixcmp(refname, "refs/remotes/")) { /* advise user how to delete local branches */ if (!prefixcmp(refname, "refs/heads/")) string_list_append(branches->skipped, @@ -544,10 +570,10 @@ static int read_remote_branches(const char *refname, unsigned char orig_sha1[20]; const char *symref; - strbuf_addf(&buf, "refs/remotes/%s", rename->old); + strbuf_addf(&buf, "refs/remotes/%s/", rename->old); if (!prefixcmp(refname, buf.buf)) { item = string_list_append(rename->remote_branches, xstrdup(refname)); - symref = resolve_ref(refname, orig_sha1, 1, &flag); + symref = resolve_ref_unsafe(refname, orig_sha1, 1, &flag); if (flag & REF_ISSYMREF) item->util = xstrdup(symref); else @@ -566,19 +592,19 @@ static int migrate_file(struct remote *remote) strbuf_addf(&buf, "remote.%s.url", remote->name); for (i = 0; i < remote->url_nr; i++) if (git_config_set_multivar(buf.buf, remote->url[i], "^$", 0)) - return error("Could not append '%s' to '%s'", + return error(_("Could not append '%s' to '%s'"), remote->url[i], buf.buf); strbuf_reset(&buf); strbuf_addf(&buf, "remote.%s.push", remote->name); for (i = 0; i < remote->push_refspec_nr; i++) if (git_config_set_multivar(buf.buf, remote->push_refspec[i], "^$", 0)) - return error("Could not append '%s' to '%s'", + return error(_("Could not append '%s' to '%s'"), remote->push_refspec[i], buf.buf); strbuf_reset(&buf); strbuf_addf(&buf, "remote.%s.fetch", remote->name); for (i = 0; i < remote->fetch_refspec_nr; i++) if (git_config_set_multivar(buf.buf, remote->fetch_refspec[i], "^$", 0)) - return error("Could not append '%s' to '%s'", + return error(_("Could not append '%s' to '%s'"), remote->fetch_refspec[i], buf.buf); if (remote->origin == REMOTE_REMOTES) path = git_path("remotes/%s", remote->name); @@ -595,10 +621,11 @@ static int mv(int argc, const char **argv) OPT_END() }; struct remote *oldremote, *newremote; - struct strbuf buf = STRBUF_INIT, buf2 = STRBUF_INIT, buf3 = STRBUF_INIT; + struct strbuf buf = STRBUF_INIT, buf2 = STRBUF_INIT, buf3 = STRBUF_INIT, + old_remote_context = STRBUF_INIT; struct string_list remote_branches = STRING_LIST_INIT_NODUP; struct rename_info rename; - int i; + int i, refspec_updated = 0; if (argc != 3) usage_with_options(builtin_remote_rename_usage, options); @@ -609,41 +636,51 @@ static int mv(int argc, const char **argv) oldremote = remote_get(rename.old); if (!oldremote) - die("No such remote: %s", rename.old); + die(_("No such remote: %s"), rename.old); if (!strcmp(rename.old, rename.new) && oldremote->origin != REMOTE_CONFIG) return migrate_file(oldremote); newremote = remote_get(rename.new); if (newremote && (newremote->url_nr > 1 || newremote->fetch_refspec_nr)) - die("remote %s already exists.", rename.new); + die(_("remote %s already exists."), rename.new); strbuf_addf(&buf, "refs/heads/test:refs/remotes/%s/test", rename.new); if (!valid_fetch_refspec(buf.buf)) - die("'%s' is not a valid remote name", rename.new); + die(_("'%s' is not a valid remote name"), rename.new); strbuf_reset(&buf); strbuf_addf(&buf, "remote.%s", rename.old); strbuf_addf(&buf2, "remote.%s", rename.new); if (git_config_rename_section(buf.buf, buf2.buf) < 1) - return error("Could not rename config section '%s' to '%s'", + return error(_("Could not rename config section '%s' to '%s'"), buf.buf, buf2.buf); strbuf_reset(&buf); strbuf_addf(&buf, "remote.%s.fetch", rename.new); if (git_config_set_multivar(buf.buf, NULL, NULL, 1)) - return error("Could not remove config section '%s'", buf.buf); + return error(_("Could not remove config section '%s'"), buf.buf); + strbuf_addf(&old_remote_context, ":refs/remotes/%s/", rename.old); for (i = 0; i < oldremote->fetch_refspec_nr; i++) { char *ptr; strbuf_reset(&buf2); strbuf_addstr(&buf2, oldremote->fetch_refspec[i]); - ptr = strstr(buf2.buf, rename.old); - if (ptr) - strbuf_splice(&buf2, ptr-buf2.buf, strlen(rename.old), - rename.new, strlen(rename.new)); + ptr = strstr(buf2.buf, old_remote_context.buf); + if (ptr) { + refspec_updated = 1; + strbuf_splice(&buf2, + ptr-buf2.buf + strlen(":refs/remotes/"), + strlen(rename.old), rename.new, + strlen(rename.new)); + } else + warning(_("Not updating non-default fetch refspec\n" + "\t%s\n" + "\tPlease update the configuration manually if necessary."), + buf2.buf); + if (git_config_set_multivar(buf.buf, buf2.buf, "^$", 0)) - return error("Could not append '%s'", buf.buf); + return error(_("Could not append '%s'"), buf.buf); } read_branches(); @@ -654,11 +691,14 @@ static int mv(int argc, const char **argv) strbuf_reset(&buf); strbuf_addf(&buf, "branch.%s.remote", item->string); if (git_config_set(buf.buf, rename.new)) { - return error("Could not set '%s'", buf.buf); + return error(_("Could not set '%s'"), buf.buf); } } } + if (!refspec_updated) + return 0; + /* * First remove symrefs, then rename the rest, finally create * the new symrefs. @@ -669,11 +709,11 @@ static int mv(int argc, const char **argv) int flag = 0; unsigned char sha1[20]; - resolve_ref(item->string, sha1, 1, &flag); + read_ref_full(item->string, sha1, 1, &flag); if (!(flag & REF_ISSYMREF)) continue; if (delete_ref(item->string, NULL, REF_NODEREF)) - die("deleting '%s' failed", item->string); + die(_("deleting '%s' failed"), item->string); } for (i = 0; i < remote_branches.nr; i++) { struct string_list_item *item = remote_branches.items + i; @@ -688,7 +728,7 @@ static int mv(int argc, const char **argv) strbuf_addf(&buf2, "remote: renamed %s to %s", item->string, buf.buf); if (rename_ref(item->string, buf.buf, buf2.buf)) - die("renaming '%s' failed", item->string); + die(_("renaming '%s' failed"), item->string); } for (i = 0; i < remote_branches.nr; i++) { struct string_list_item *item = remote_branches.items + i; @@ -707,7 +747,7 @@ static int mv(int argc, const char **argv) strbuf_addf(&buf3, "remote: renamed %s to %s", item->string, buf.buf); if (create_symref(buf.buf, buf2.buf, buf3.buf)) - die("creating '%s' failed", buf.buf); + die(_("creating '%s' failed"), buf.buf); } return 0; } @@ -721,7 +761,7 @@ static int remove_branches(struct string_list *branches) unsigned char *sha1 = item->util; if (delete_ref(refname, sha1, 0)) - result |= error("Could not remove branch %s", refname); + result |= error(_("Could not remove branch %s"), refname); } return result; } @@ -749,14 +789,14 @@ static int rm(int argc, const char **argv) remote = remote_get(argv[1]); if (!remote) - die("No such remote: %s", argv[1]); + die(_("No such remote: %s"), argv[1]); known_remotes.to_delete = remote; for_each_remote(add_known_remote, &known_remotes); strbuf_addf(&buf, "remote.%s", remote->name); if (git_config_rename_section(buf.buf, NULL) < 1) - return error("Could not remove config section '%s'", buf.buf); + return error(_("Could not remove config section '%s'"), buf.buf); read_branches(); for (i = 0; i < branch_list.nr; i++) { @@ -790,11 +830,12 @@ static int rm(int argc, const char **argv) string_list_clear(&branches, 1); if (skipped.nr) { - fprintf(stderr, skipped.nr == 1 ? - "Note: A non-remote branch was not removed; " - "to delete it, use:\n" : - "Note: Non-remote branches were not removed; " - "to delete them, use:\n"); + fprintf_ln(stderr, + Q_("Note: A branch outside the refs/remotes/ hierarchy was not removed;\n" + "to delete it, use:", + "Note: Some branches outside the refs/remotes/ hierarchy were not removed;\n" + "to delete them, use:", + skipped.nr)); for (i = 0; i < skipped.nr; i++) fprintf(stderr, " git branch -d %s\n", skipped.items[i].string); @@ -846,7 +887,7 @@ static int get_remote_ref_states(const char *name, states->remote = remote_get(name); if (!states->remote) - return error("No such remote: %s", name); + return error(_("No such remote: %s"), name); read_branches(); @@ -899,14 +940,14 @@ static int show_remote_info_item(struct string_list_item *item, void *cb_data) const char *fmt = "%s"; const char *arg = ""; if (string_list_has_string(&states->new, name)) { - fmt = " new (next fetch will store in remotes/%s)"; + fmt = _(" new (next fetch will store in remotes/%s)"); arg = states->remote->name; } else if (string_list_has_string(&states->tracked, name)) - arg = " tracked"; + arg = _(" tracked"); else if (string_list_has_string(&states->stale, name)) - arg = " stale (use 'git remote prune' to remove)"; + arg = _(" stale (use 'git remote prune' to remove)"); else - arg = " ???"; + arg = _(" ???"); printf(" %-*s", info->width, name); printf(fmt, arg); printf("\n"); @@ -947,21 +988,21 @@ static int show_local_info_item(struct string_list_item *item, void *cb_data) int i; if (branch_info->rebase && branch_info->merge.nr > 1) { - error("invalid branch.%s.merge; cannot rebase onto > 1 branch", + error(_("invalid branch.%s.merge; cannot rebase onto > 1 branch"), item->string); return 0; } printf(" %-*s ", show_info->width, item->string); if (branch_info->rebase) { - printf("rebases onto remote %s\n", merge->items[0].string); + printf_ln(_("rebases onto remote %s"), merge->items[0].string); return 0; } else if (show_info->any_rebase) { - printf(" merges with remote %s\n", merge->items[0].string); - also = " and with remote"; + printf_ln(_(" merges with remote %s"), merge->items[0].string); + also = _(" and with remote"); } else { - printf("merges with remote %s\n", merge->items[0].string); - also = " and with remote"; + printf_ln(_("merges with remote %s"), merge->items[0].string); + also = _(" and with remote"); } for (i = 1; i < merge->nr; i++) printf(" %-*s %s %s\n", show_info->width, "", also, @@ -1003,36 +1044,43 @@ static int show_push_info_item(struct string_list_item *item, void *cb_data) { struct show_info *show_info = cb_data; struct push_info *push_info = item->util; - char *src = item->string, *status = NULL; + const char *src = item->string, *status = NULL; switch (push_info->status) { case PUSH_STATUS_CREATE: - status = "create"; + status = _("create"); break; case PUSH_STATUS_DELETE: - status = "delete"; - src = "(none)"; + status = _("delete"); + src = _("(none)"); break; case PUSH_STATUS_UPTODATE: - status = "up to date"; + status = _("up to date"); break; case PUSH_STATUS_FASTFORWARD: - status = "fast-forwardable"; + status = _("fast-forwardable"); break; case PUSH_STATUS_OUTOFDATE: - status = "local out of date"; + status = _("local out of date"); break; case PUSH_STATUS_NOTQUERIED: break; } - if (status) - printf(" %-*s %s to %-*s (%s)\n", show_info->width, src, - push_info->forced ? "forces" : "pushes", - show_info->width2, push_info->dest, status); - else - printf(" %-*s %s to %s\n", show_info->width, src, - push_info->forced ? "forces" : "pushes", - push_info->dest); + if (status) { + if (push_info->forced) + printf_ln(_(" %-*s forces to %-*s (%s)"), show_info->width, src, + show_info->width2, push_info->dest, status); + else + printf_ln(_(" %-*s pushes to %-*s (%s)"), show_info->width, src, + show_info->width2, push_info->dest, status); + } else { + if (push_info->forced) + printf_ln(_(" %-*s forces to %s"), show_info->width, src, + push_info->dest); + else + printf_ln(_(" %-*s pushes to %s"), show_info->width, src, + push_info->dest); + } return 0; } @@ -1067,9 +1115,9 @@ static int show(int argc, const char **argv) get_remote_ref_states(*argv, &states, query_flag); - printf("* remote %s\n", *argv); - printf(" Fetch URL: %s\n", states.remote->url_nr > 0 ? - states.remote->url[0] : "(no URL)"); + printf_ln(_("* remote %s"), *argv); + printf_ln(_(" Fetch URL: %s"), states.remote->url_nr > 0 ? + states.remote->url[0] : _("(no URL)")); if (states.remote->pushurl_nr) { url = states.remote->pushurl; url_nr = states.remote->pushurl_nr; @@ -1077,19 +1125,19 @@ static int show(int argc, const char **argv) url = states.remote->url; url_nr = states.remote->url_nr; } - for (i=0; i < url_nr; i++) - printf(" Push URL: %s\n", url[i]); + for (i = 0; i < url_nr; i++) + printf_ln(_(" Push URL: %s"), url[i]); if (!i) - printf(" Push URL: %s\n", "(no URL)"); + printf_ln(_(" Push URL: %s"), "(no URL)"); if (no_query) - printf(" HEAD branch: (not queried)\n"); + printf_ln(_(" HEAD branch: %s"), "(not queried)"); else if (!states.heads.nr) - printf(" HEAD branch: (unknown)\n"); + printf_ln(_(" HEAD branch: %s"), "(unknown)"); else if (states.heads.nr == 1) - printf(" HEAD branch: %s\n", states.heads.items[0].string); + printf_ln(_(" HEAD branch: %s"), states.heads.items[0].string); else { - printf(" HEAD branch (remote HEAD is ambiguous," - " may be one of the following):\n"); + printf(_(" HEAD branch (remote HEAD is ambiguous," + " may be one of the following):\n")); for (i = 0; i < states.heads.nr; i++) printf(" %s\n", states.heads.items[i].string); } @@ -1100,9 +1148,10 @@ static int show(int argc, const char **argv) for_each_string_list(&states.tracked, add_remote_to_show_info, &info); for_each_string_list(&states.stale, add_remote_to_show_info, &info); if (info.list->nr) - printf(" Remote branch%s:%s\n", - info.list->nr > 1 ? "es" : "", - no_query ? " (status not queried)" : ""); + printf_ln(Q_(" Remote branch:%s", + " Remote branches:%s", + info.list->nr), + no_query ? _(" (status not queried)") : ""); for_each_string_list(info.list, show_remote_info_item, &info); string_list_clear(info.list, 0); @@ -1111,23 +1160,25 @@ static int show(int argc, const char **argv) info.any_rebase = 0; for_each_string_list(&branch_list, add_local_to_show_info, &info); if (info.list->nr) - printf(" Local branch%s configured for 'git pull':\n", - info.list->nr > 1 ? "es" : ""); + printf_ln(Q_(" Local branch configured for 'git pull':", + " Local branches configured for 'git pull':", + info.list->nr)); for_each_string_list(info.list, show_local_info_item, &info); string_list_clear(info.list, 0); /* git push info */ if (states.remote->mirror) - printf(" Local refs will be mirrored by 'git push'\n"); + printf_ln(_(" Local refs will be mirrored by 'git push'")); info.width = info.width2 = 0; for_each_string_list(&states.push, add_push_to_show_info, &info); qsort(info.list->items, info.list->nr, sizeof(*info.list->items), cmp_string_with_push); if (info.list->nr) - printf(" Local ref%s configured for 'git push'%s:\n", - info.list->nr > 1 ? "s" : "", - no_query ? " (status not queried)" : ""); + printf_ln(Q_(" Local ref configured for 'git push'%s:", + " Local refs configured for 'git push'%s:", + info.list->nr), + no_query ? _(" (status not queried)") : ""); for_each_string_list(info.list, show_push_info_item, &info); string_list_clear(info.list, 0); @@ -1162,10 +1213,10 @@ static int set_head(int argc, const char **argv) memset(&states, 0, sizeof(states)); get_remote_ref_states(argv[0], &states, GET_HEAD_NAMES); if (!states.heads.nr) - result |= error("Cannot determine remote HEAD"); + result |= error(_("Cannot determine remote HEAD")); else if (states.heads.nr > 1) { - result |= error("Multiple remote HEAD branches. " - "Please choose one explicitly with:"); + result |= error(_("Multiple remote HEAD branches. " + "Please choose one explicitly with:")); for (i = 0; i < states.heads.nr; i++) fprintf(stderr, " git remote set-head %s %s\n", argv[0], states.heads.items[i].string); @@ -1174,18 +1225,17 @@ static int set_head(int argc, const char **argv) free_remote_ref_states(&states); } else if (opt_d && !opt_a && argc == 1) { if (delete_ref(buf.buf, NULL, REF_NODEREF)) - result |= error("Could not delete %s", buf.buf); + result |= error(_("Could not delete %s"), buf.buf); } else usage_with_options(builtin_remote_sethead_usage, options); if (head_name) { - unsigned char sha1[20]; strbuf_addf(&buf2, "refs/remotes/%s/%s", argv[0], head_name); /* make sure it's valid */ - if (!resolve_ref(buf2.buf, sha1, 1, NULL)) - result |= error("Not a valid ref: %s", buf2.buf); + if (!ref_exists(buf2.buf)) + result |= error(_("Not a valid ref: %s"), buf2.buf); else if (create_symref(buf.buf, buf2.buf, "remote set-head")) - result |= error("Could not setup %s", buf.buf); + result |= error(_("Could not setup %s"), buf.buf); if (opt_a) printf("%s/HEAD set to %s\n", argv[0], head_name); free(head_name); @@ -1200,7 +1250,7 @@ static int prune(int argc, const char **argv) { int dry_run = 0, result = 0; struct option options[] = { - OPT__DRY_RUN(&dry_run), + OPT__DRY_RUN(&dry_run, "dry run"), OPT_END() }; @@ -1221,18 +1271,18 @@ static int prune_remote(const char *remote, int dry_run) int result = 0, i; struct ref_states states; const char *dangling_msg = dry_run - ? " %s will become dangling!\n" - : " %s has become dangling!\n"; + ? _(" %s will become dangling!") + : _(" %s has become dangling!"); memset(&states, 0, sizeof(states)); get_remote_ref_states(remote, &states, GET_REF_STATES); if (states.stale.nr) { - printf("Pruning %s\n", remote); - printf("URL: %s\n", + printf_ln(_("Pruning %s"), remote); + printf_ln(_("URL: %s"), states.remote->url_nr ? states.remote->url[0] - : "(no URL)"); + : _("(no URL)")); } for (i = 0; i < states.stale.nr; i++) { @@ -1241,8 +1291,12 @@ static int prune_remote(const char *remote, int dry_run) if (!dry_run) result |= delete_ref(refname, NULL, 0); - printf(" * [%s] %s\n", dry_run ? "would prune" : "pruned", - abbrev_ref(refname, "refs/remotes/")); + if (dry_run) + printf_ln(_(" * [would prune] %s"), + abbrev_ref(refname, "refs/remotes/")); + else + printf_ln(_(" * [pruned] %s"), + abbrev_ref(refname, "refs/remotes/")); warn_dangling_symref(stdout, dangling_msg, refname); } @@ -1330,7 +1384,7 @@ static int set_remote_branches(const char *remotename, const char **branches, strbuf_addf(&key, "remote.%s.fetch", remotename); if (!remote_is_configured(remotename)) - die("No such remote '%s'", remotename); + die(_("No such remote '%s'"), remotename); remote = remote_get(remotename); if (!add_mode && remove_all_fetch_refspecs(remotename, key.buf)) { @@ -1357,8 +1411,8 @@ static int set_branches(int argc, const char **argv) argc = parse_options(argc, argv, NULL, options, builtin_remote_setbranches_usage, 0); if (argc == 0) { - error("no remote specified"); - usage_with_options(builtin_remote_seturl_usage, options); + error(_("no remote specified")); + usage_with_options(builtin_remote_setbranches_usage, options); } argv[argc] = NULL; @@ -1386,11 +1440,11 @@ static int set_url(int argc, const char **argv) "delete URLs"), OPT_END() }; - argc = parse_options(argc, argv, NULL, options, builtin_remote_update_usage, + argc = parse_options(argc, argv, NULL, options, builtin_remote_seturl_usage, PARSE_OPT_KEEP_ARGV0); if (add_mode && delete_mode) - die("--add --delete doesn't make sense"); + die(_("--add --delete doesn't make sense")); if (argc < 3 || argc > 4 || ((add_mode || delete_mode) && argc != 3)) usage_with_options(builtin_remote_seturl_usage, options); @@ -1404,7 +1458,7 @@ static int set_url(int argc, const char **argv) oldurl = newurl; if (!remote_is_configured(remotename)) - die("No such remote '%s'", remotename); + die(_("No such remote '%s'"), remotename); remote = remote_get(remotename); if (push_mode) { @@ -1430,7 +1484,7 @@ static int set_url(int argc, const char **argv) /* Old URL specified. Demand that one matches. */ if (regcomp(&old_regex, oldurl, REG_EXTENDED)) - die("Invalid old URL pattern: %s", oldurl); + die(_("Invalid old URL pattern: %s"), oldurl); for (i = 0; i < urlset_nr; i++) if (!regexec(&old_regex, urlset[i], 0, NULL, 0)) @@ -1438,9 +1492,9 @@ static int set_url(int argc, const char **argv) else negative_matches++; if (!delete_mode && !matches) - die("No such URL found: %s", oldurl); + die(_("No such URL found: %s"), oldurl); if (delete_mode && !negative_matches && !push_mode) - die("Will not delete all non-push URLs"); + die(_("Will not delete all non-push URLs")); regfree(&old_regex); @@ -1512,7 +1566,7 @@ static int show_all(void) int cmd_remote(int argc, const char **argv, const char *prefix) { struct option options[] = { - OPT_BOOLEAN('v', "verbose", &verbose, "be verbose; must be placed before a subcommand"), + OPT__VERBOSE(&verbose, "be verbose; must be placed before a subcommand"), OPT_END() }; int result; @@ -1541,7 +1595,7 @@ int cmd_remote(int argc, const char **argv, const char *prefix) else if (!strcmp(argv[0], "update")) result = update(argc, argv); else { - error("Unknown subcommand: %s", argv[0]); + error(_("Unknown subcommand: %s"), argv[0]); usage_with_options(builtin_remote_usage, options); } diff --git a/builtin/replace.c b/builtin/replace.c index fe3a647a36..4a8970e9c9 100644 --- a/builtin/replace.c +++ b/builtin/replace.c @@ -58,7 +58,7 @@ static int for_each_replace_name(const char **argv, each_replace_name_fn fn) had_error = 1; continue; } - if (!resolve_ref(ref, sha1, 1, NULL)) { + if (read_ref(ref, sha1)) { error("replace ref '%s' not found.", *p); had_error = 1; continue; @@ -94,10 +94,10 @@ static int replace_object(const char *object_ref, const char *replace_ref, "refs/replace/%s", sha1_to_hex(object)) > sizeof(ref) - 1) die("replace ref name too long: %.*s...", 50, ref); - if (check_ref_format(ref)) + if (check_refname_format(ref, 0)) die("'%s' is not a valid ref name.", ref); - if (!resolve_ref(ref, prev, 1, NULL)) + if (read_ref(ref, prev)) hashclr(prev); else if (!force) die("replace ref '%s' already exists", ref); diff --git a/builtin/rerere.c b/builtin/rerere.c index 642bf35587..08213c7c0b 100644 --- a/builtin/rerere.c +++ b/builtin/rerere.c @@ -8,78 +8,10 @@ #include "xdiff-interface.h" static const char * const rerere_usage[] = { - "git rerere [clear | status | diff | gc]", + "git rerere [clear | forget path... | status | remaining | diff | gc]", NULL, }; -/* these values are days */ -static int cutoff_noresolve = 15; -static int cutoff_resolve = 60; - -static time_t rerere_created_at(const char *name) -{ - struct stat st; - return stat(rerere_path(name, "preimage"), &st) ? (time_t) 0 : st.st_mtime; -} - -static time_t rerere_last_used_at(const char *name) -{ - struct stat st; - return stat(rerere_path(name, "postimage"), &st) ? (time_t) 0 : st.st_mtime; -} - -static void unlink_rr_item(const char *name) -{ - unlink(rerere_path(name, "thisimage")); - unlink(rerere_path(name, "preimage")); - unlink(rerere_path(name, "postimage")); - rmdir(git_path("rr-cache/%s", name)); -} - -static int git_rerere_gc_config(const char *var, const char *value, void *cb) -{ - if (!strcmp(var, "gc.rerereresolved")) - cutoff_resolve = git_config_int(var, value); - else if (!strcmp(var, "gc.rerereunresolved")) - cutoff_noresolve = git_config_int(var, value); - else - return git_default_config(var, value, cb); - return 0; -} - -static void garbage_collect(struct string_list *rr) -{ - struct string_list to_remove = STRING_LIST_INIT_DUP; - DIR *dir; - struct dirent *e; - int i, cutoff; - time_t now = time(NULL), then; - - git_config(git_rerere_gc_config, NULL); - dir = opendir(git_path("rr-cache")); - if (!dir) - die_errno("unable to open rr-cache directory"); - while ((e = readdir(dir))) { - if (is_dot_or_dotdot(e->d_name)) - continue; - - then = rerere_last_used_at(e->d_name); - if (then) { - cutoff = cutoff_resolve; - } else { - then = rerere_created_at(e->d_name); - if (!then) - continue; - cutoff = cutoff_noresolve; - } - if (then < now - cutoff * 86400) - string_list_append(&to_remove, e->d_name); - } - for (i = 0; i < to_remove.nr; i++) - unlink_rr_item(to_remove.items[i].string); - string_list_clear(&to_remove, 0); -} - static int outf(void *dummy, mmbuffer_t *ptr, int nbuf) { int i; @@ -136,7 +68,10 @@ int cmd_rerere(int argc, const char **argv, const char *prefix) return rerere(flags); if (!strcmp(argv[0], "forget")) { - const char **pathspec = get_pathspec(prefix, argv + 1); + const char **pathspec; + if (argc < 2) + warning("'git rerere forget' without paths is deprecated"); + pathspec = get_pathspec(prefix, argv + 1); return rerere_forget(pathspec); } @@ -145,18 +80,23 @@ int cmd_rerere(int argc, const char **argv, const char *prefix) return 0; if (!strcmp(argv[0], "clear")) { - for (i = 0; i < merge_rr.nr; i++) { - const char *name = (const char *)merge_rr.items[i].util; - if (!has_rerere_resolution(name)) - unlink_rr_item(name); - } - unlink_or_warn(git_path("MERGE_RR")); + rerere_clear(&merge_rr); } else if (!strcmp(argv[0], "gc")) - garbage_collect(&merge_rr); + rerere_gc(&merge_rr); else if (!strcmp(argv[0], "status")) for (i = 0; i < merge_rr.nr; i++) printf("%s\n", merge_rr.items[i].string); - else if (!strcmp(argv[0], "diff")) + else if (!strcmp(argv[0], "remaining")) { + rerere_remaining(&merge_rr); + for (i = 0; i < merge_rr.nr; i++) { + if (merge_rr.items[i].util != RERERE_RESOLVED) + printf("%s\n", merge_rr.items[i].string); + else + /* prepare for later call to + * string_list_clear() */ + merge_rr.items[i].util = NULL; + } + } else if (!strcmp(argv[0], "diff")) for (i = 0; i < merge_rr.nr; i++) { const char *path = merge_rr.items[i].string; const char *name = (const char *)merge_rr.items[i].util; diff --git a/builtin/reset.c b/builtin/reset.c index 0037be4693..8c2c1d52a2 100644 --- a/builtin/reset.c +++ b/builtin/reset.c @@ -7,7 +7,7 @@ * * Copyright (c) 2005, 2006 Linus Torvalds and Junio C Hamano */ -#include "cache.h" +#include "builtin.h" #include "tag.h" #include "object.h" #include "commit.h" @@ -30,28 +30,9 @@ static const char * const git_reset_usage[] = { enum reset_type { MIXED, SOFT, HARD, MERGE, KEEP, NONE }; static const char *reset_type_names[] = { - "mixed", "soft", "hard", "merge", "keep", NULL + N_("mixed"), N_("soft"), N_("hard"), N_("merge"), N_("keep"), NULL }; -static char *args_to_str(const char **argv) -{ - char *buf = NULL; - unsigned long len, space = 0, nr = 0; - - for (; *argv; argv++) { - len = strlen(*argv); - ALLOC_GROW(buf, nr + 1 + len, space); - if (nr) - buf[nr++] = ' '; - memcpy(buf + nr, *argv, len); - nr += len; - } - ALLOC_GROW(buf, nr + 1, space); - buf[nr] = '\0'; - - return buf; -} - static inline int is_merge(void) { return !access(git_path("MERGE_HEAD"), F_OK); @@ -62,6 +43,7 @@ static int reset_index_file(const unsigned char *sha1, int reset_type, int quiet int nr = 1; int newfd; struct tree_desc desc[2]; + struct tree *tree; struct unpack_trees_options opts; struct lock_file *lock = xcalloc(1, sizeof(struct lock_file)); @@ -92,20 +74,26 @@ static int reset_index_file(const unsigned char *sha1, int reset_type, int quiet if (reset_type == KEEP) { unsigned char head_sha1[20]; if (get_sha1("HEAD", head_sha1)) - return error("You do not have a valid HEAD."); + return error(_("You do not have a valid HEAD.")); if (!fill_tree_descriptor(desc, head_sha1)) - return error("Failed to find tree of HEAD."); + return error(_("Failed to find tree of HEAD.")); nr++; opts.fn = twoway_merge; } if (!fill_tree_descriptor(desc + nr - 1, sha1)) - return error("Failed to find tree of %s.", sha1_to_hex(sha1)); + return error(_("Failed to find tree of %s."), sha1_to_hex(sha1)); if (unpack_trees(nr, desc, &opts)) return -1; + + if (reset_type == MIXED || reset_type == HARD) { + tree = parse_tree_indirect(sha1); + prime_cache_tree(&active_cache_tree, tree); + } + if (write_cache(newfd, active_cache, active_nr) || commit_locked_index(lock)) - return error("Could not write new index file."); + return error(_("Could not write new index file.")); return 0; } @@ -115,7 +103,7 @@ static void print_new_head_line(struct commit *commit) const char *hex, *body; hex = find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV); - printf("HEAD is now at %s", hex); + printf(_("HEAD is now at %s"), hex); body = strstr(commit->buffer, "\n\n"); if (body) { const char *eol; @@ -139,10 +127,10 @@ static int update_index_refresh(int fd, struct lock_file *index_lock, int flags) } if (read_cache() < 0) - return error("Could not read index"); + return error(_("Could not read index")); result = refresh_index(&the_index, (flags), NULL, NULL, - "Unstaged changes after reset:") ? 1 : 0; + _("Unstaged changes after reset:")) ? 1 : 0; if (write_cache(fd, active_cache, active_nr) || commit_locked_index(index_lock)) return error ("Could not refresh index"); @@ -162,12 +150,12 @@ static void update_index_from_diff(struct diff_queue_struct *q, for (i = 0; i < q->nr; i++) { struct diff_filespec *one = q->queue[i]->one; - if (one->mode) { + if (one->mode && !is_null_sha1(one->sha1)) { struct cache_entry *ce; ce = make_cache_entry(one->mode, one->sha1, one->path, 0, 0); if (!ce) - die("make_cache_entry failed for path '%s'", + die(_("make_cache_entry failed for path '%s'"), one->path); add_cache_entry(ce, ADD_CACHE_OK_TO_ADD | ADD_CACHE_OK_TO_REPLACE); @@ -215,21 +203,25 @@ static int read_from_tree(const char *prefix, const char **argv, return update_index_refresh(index_fd, lock, refresh_flags); } -static void prepend_reflog_action(const char *action, char *buf, size_t size) +static void set_reflog_message(struct strbuf *sb, const char *action, + const char *rev) { - const char *sep = ": "; const char *rla = getenv("GIT_REFLOG_ACTION"); - if (!rla) - rla = sep = ""; - if (snprintf(buf, size, "%s%s%s", rla, sep, action) >= size) - warning("Reflog action message too long: %.*s...", 50, buf); + + strbuf_reset(sb); + if (rla) + strbuf_addf(sb, "%s: %s", rla, action); + else if (rev) + strbuf_addf(sb, "reset: moving to %s", rev); + else + strbuf_addf(sb, "reset: %s", action); } static void die_if_unmerged_cache(int reset_type) { if (is_merge() || read_cache() < 0 || unmerged_cache()) - die("Cannot do a %s reset in the middle of a merge.", - reset_type_names[reset_type]); + die(_("Cannot do a %s reset in the middle of a merge."), + _(reset_type_names[reset_type])); } @@ -241,9 +233,9 @@ int cmd_reset(int argc, const char **argv, const char *prefix) unsigned char sha1[20], *orig = NULL, sha1_orig[20], *old_orig = NULL, sha1_old_orig[20]; struct commit *commit; - char *reflog_action, msg[1024]; + struct strbuf msg = STRBUF_INIT; const struct option options[] = { - OPT__QUIET(&quiet), + OPT__QUIET(&quiet, "be quiet, only report errors"), OPT_SET_INT(0, "mixed", &reset_type, "reset HEAD and index", MIXED), OPT_SET_INT(0, "soft", &reset_type, "reset only HEAD", SOFT), @@ -261,8 +253,6 @@ int cmd_reset(int argc, const char **argv, const char *prefix) argc = parse_options(argc, argv, prefix, options, git_reset_usage, PARSE_OPT_KEEP_DASHDASH); - reflog_action = args_to_str(argv); - setenv("GIT_REFLOG_ACTION", reflog_action, 0); /* * Possible arguments are: @@ -300,16 +290,16 @@ int cmd_reset(int argc, const char **argv, const char *prefix) } if (get_sha1(rev, sha1)) - die("Failed to resolve '%s' as a valid ref.", rev); + die(_("Failed to resolve '%s' as a valid ref."), rev); commit = lookup_commit_reference(sha1); if (!commit) - die("Could not parse object '%s'.", rev); + die(_("Could not parse object '%s'."), rev); hashcpy(sha1, commit->object.sha1); if (patch_mode) { if (reset_type != NONE) - die("--patch is incompatible with --{hard,mixed,soft}"); + die(_("--patch is incompatible with --{hard,mixed,soft}")); return interactive_reset(rev, argv + i, prefix); } @@ -318,10 +308,10 @@ int cmd_reset(int argc, const char **argv, const char *prefix) * affecting the working tree nor HEAD. */ if (i < argc) { if (reset_type == MIXED) - warning("--mixed with paths is deprecated; use 'git reset -- <paths>' instead."); + warning(_("--mixed with paths is deprecated; use 'git reset -- <paths>' instead.")); else if (reset_type != NONE) - die("Cannot do %s reset with paths.", - reset_type_names[reset_type]); + die(_("Cannot do %s reset with paths."), + _(reset_type_names[reset_type])); return read_from_tree(prefix, argv + i, sha1, quiet ? REFRESH_QUIET : REFRESH_IN_PORCELAIN); } @@ -332,8 +322,8 @@ int cmd_reset(int argc, const char **argv, const char *prefix) setup_work_tree(); if (reset_type == MIXED && is_bare_repository()) - die("%s reset is not allowed in a bare repository", - reset_type_names[reset_type]); + die(_("%s reset is not allowed in a bare repository"), + _(reset_type_names[reset_type])); /* Soft reset does not touch the index file nor the working tree * at all, but requires them in a good order. Other resets reset @@ -348,7 +338,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix) if (reset_type == KEEP) err = err || reset_index_file(sha1, MIXED, quiet); if (err) - die("Could not reset index file to revision '%s'.", rev); + die(_("Could not reset index file to revision '%s'."), rev); } /* Any resets update HEAD to the head being switched to, @@ -357,13 +347,13 @@ int cmd_reset(int argc, const char **argv, const char *prefix) old_orig = sha1_old_orig; if (!get_sha1("HEAD", sha1_orig)) { orig = sha1_orig; - prepend_reflog_action("updating ORIG_HEAD", msg, sizeof(msg)); - update_ref(msg, "ORIG_HEAD", orig, old_orig, 0, MSG_ON_ERR); + set_reflog_message(&msg, "updating ORIG_HEAD", NULL); + update_ref(msg.buf, "ORIG_HEAD", orig, old_orig, 0, MSG_ON_ERR); } else if (old_orig) delete_ref("ORIG_HEAD", old_orig, 0); - prepend_reflog_action("updating HEAD", msg, sizeof(msg)); - update_ref_status = update_ref(msg, "HEAD", sha1, orig, 0, MSG_ON_ERR); + set_reflog_message(&msg, "updating HEAD", rev); + update_ref_status = update_ref(msg.buf, "HEAD", sha1, orig, 0, MSG_ON_ERR); switch (reset_type) { case HARD: @@ -380,7 +370,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix) remove_branch_state(); - free(reflog_action); + strbuf_release(&msg); return update_ref_status; } diff --git a/builtin/rev-list.c b/builtin/rev-list.c index 158ce1111a..ff5a38372d 100644 --- a/builtin/rev-list.c +++ b/builtin/rev-list.c @@ -16,6 +16,10 @@ static const char rev_list_usage[] = " --min-age=<epoch>\n" " --sparse\n" " --no-merges\n" +" --min-parents=<n>\n" +" --no-min-parents\n" +" --max-parents=<n>\n" +" --no-max-parents\n" " --remove-empty\n" " --all\n" " --branches\n" @@ -48,10 +52,17 @@ static void show_commit(struct commit *commit, void *data) struct rev_list_info *info = data; struct rev_info *revs = info->revs; + if (info->flags & REV_LIST_QUIET) { + finish_commit(commit, data); + return; + } + graph_show_commit(revs->graph); if (revs->count) { - if (commit->object.flags & SYMMETRIC_LEFT) + if (commit->object.flags & PATCHSAME) + revs->count_same++; + else if (commit->object.flags & SYMMETRIC_LEFT) revs->count_left++; else revs->count_right++; @@ -64,18 +75,8 @@ static void show_commit(struct commit *commit, void *data) if (info->header_prefix) fputs(info->header_prefix, stdout); - if (!revs->graph) { - if (commit->object.flags & BOUNDARY) - putchar('-'); - else if (commit->object.flags & UNINTERESTING) - putchar('^'); - else if (revs->left_right) { - if (commit->object.flags & SYMMETRIC_LEFT) - putchar('<'); - else - putchar('>'); - } - } + if (!revs->graph) + fputs(get_revision_mark(revs, commit), stdout); if (revs->abbrev_commit && revs->abbrev) fputs(find_unique_abbrev(commit->object.sha1, revs->abbrev), stdout); @@ -108,7 +109,9 @@ static void show_commit(struct commit *commit, void *data) struct pretty_print_context ctx = {0}; ctx.abbrev = revs->abbrev; ctx.date_mode = revs->date_mode; - pretty_print_commit(revs->commit_format, commit, &buf, &ctx); + ctx.date_mode_explicit = revs->date_mode_explicit; + ctx.fmt = revs->commit_format; + pretty_print_commit(&ctx, commit, &buf); if (revs->graph) { if (buf.len) { if (revs->commit_format != CMIT_FMT_ONELINE) @@ -147,8 +150,10 @@ static void show_commit(struct commit *commit, void *data) } } else { if (revs->commit_format != CMIT_FMT_USERFORMAT || - buf.len) - printf("%s%c", buf.buf, info->hdr_termination); + buf.len) { + fwrite(buf.buf, 1, buf.len, stdout); + putchar(info->hdr_termination); + } } strbuf_release(&buf); } else { @@ -169,29 +174,26 @@ static void finish_commit(struct commit *commit, void *data) commit->buffer = NULL; } -static void finish_object(struct object *obj, const struct name_path *path, const char *name) +static void finish_object(struct object *obj, + const struct name_path *path, const char *name, + void *cb_data) { + struct rev_list_info *info = cb_data; if (obj->type == OBJ_BLOB && !has_sha1_file(obj->sha1)) die("missing blob object '%s'", sha1_to_hex(obj->sha1)); + if (info->revs->verify_objects && !obj->parsed && obj->type != OBJ_COMMIT) + parse_object(obj->sha1); } -static void show_object(struct object *obj, const struct name_path *path, const char *component) +static void show_object(struct object *obj, + const struct name_path *path, const char *component, + void *cb_data) { - char *name = path_name(path, component); - /* An object with name "foo\n0000000..." can be used to - * confuse downstream "git pack-objects" very badly. - */ - const char *ep = strchr(name, '\n'); - - finish_object(obj, path, name); - if (ep) { - printf("%s %.*s\n", sha1_to_hex(obj->sha1), - (int) (ep - name), - name); - } - else - printf("%s %s\n", sha1_to_hex(obj->sha1), name); - free(name); + struct rev_list_info *info = cb_data; + finish_object(obj, path, component, cb_data); + if (info->flags & REV_LIST_QUIET) + return; + show_object_with_name(stdout, obj, path, component); } static void show_edge(struct commit *commit) @@ -248,13 +250,6 @@ void print_commit_list(struct commit_list *list, } } -static void show_tried_revs(struct commit_list *tried) -{ - printf("bisect_tried='"); - print_commit_list(tried, "%s|", "%s"); - printf("'\n"); -} - static void print_var_str(const char *var, const char *val) { printf("%s='%s'\n", var, val); @@ -267,12 +262,12 @@ static void print_var_int(const char *var, int val) static int show_bisect_vars(struct rev_list_info *info, int reaches, int all) { - int cnt, flags = info->bisect_show_flags; + int cnt, flags = info->flags; char hex[41] = ""; struct commit_list *tried; struct rev_info *revs = info->revs; - if (!revs->commits && !(flags & BISECT_SHOW_TRIED)) + if (!revs->commits) return 1; revs->commits = filter_skipped(revs->commits, &tried, @@ -300,9 +295,6 @@ static int show_bisect_vars(struct rev_list_info *info, int reaches, int all) printf("------\n"); } - if (flags & BISECT_SHOW_TRIED) - show_tried_revs(tried); - print_var_str("bisect_rev", hex); print_var_int("bisect_nr", cnt - 1); print_var_int("bisect_good", all - reaches - 1); @@ -321,7 +313,6 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) int bisect_list = 0; int bisect_show_vars = 0; int bisect_find_all = 0; - int quiet = 0; git_config(git_default_config, NULL); init_revisions(&revs, prefix); @@ -334,7 +325,8 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) if (revs.bisect) bisect_list = 1; - quiet = DIFF_OPT_TST(&revs.diffopt, QUICK); + if (DIFF_OPT_TST(&revs.diffopt, QUICK)) + info.flags |= REV_LIST_QUIET; for (i = 1 ; i < argc; i++) { const char *arg = argv[i]; @@ -353,7 +345,7 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) if (!strcmp(arg, "--bisect-all")) { bisect_list = 1; bisect_find_all = 1; - info.bisect_show_flags = BISECT_SHOW_ALL; + info.flags |= BISECT_SHOW_ALL; revs.show_decorations = 1; continue; } @@ -404,14 +396,15 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) return show_bisect_vars(&info, reaches, all); } - traverse_commit_list(&revs, - quiet ? finish_commit : show_commit, - quiet ? finish_object : show_object, - &info); + traverse_commit_list(&revs, show_commit, show_object, &info); if (revs.count) { - if (revs.left_right) + if (revs.left_right && revs.cherry_mark) + printf("%d\t%d\t%d\n", revs.count_left, revs.count_right, revs.count_same); + else if (revs.left_right) printf("%d\t%d\n", revs.count_left, revs.count_right); + else if (revs.cherry_mark) + printf("%d\t%d\n", revs.count_left + revs.count_right, revs.count_same); else printf("%d\n", revs.count_left + revs.count_right); } diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c index a5a1c86e92..733f626f6c 100644 --- a/builtin/rev-parse.c +++ b/builtin/rev-parse.c @@ -44,10 +44,15 @@ static int is_rev_argument(const char *arg) "--branches=", "--branches", "--header", + "--ignore-missing", "--max-age=", "--max-count=", "--min-age=", "--no-merges", + "--min-parents=", + "--no-min-parents", + "--max-parents=", + "--no-max-parents", "--objects", "--objects-edge", "--parents", @@ -463,6 +468,14 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) return 0; } + if (argc > 2 && !strcmp(argv[1], "--resolve-git-dir")) { + const char *gitdir = resolve_gitdir(argv[2]); + if (!gitdir) + die("not a gitdir '%s'", argv[2]); + puts(gitdir); + return 0; + } + if (argc > 1 && !strcmp("-h", argv[1])) usage(builtin_rev_parse_usage); @@ -621,6 +634,8 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) if (!strcmp(arg, "--show-prefix")) { if (prefix) puts(prefix); + else + putchar('\n'); continue; } if (!strcmp(arg, "--show-cdup")) { diff --git a/builtin/revert.c b/builtin/revert.c index 57b51e4a0e..82d1bf844b 100644 --- a/builtin/revert.c +++ b/builtin/revert.c @@ -1,19 +1,11 @@ #include "cache.h" #include "builtin.h" -#include "object.h" -#include "commit.h" -#include "tag.h" -#include "wt-status.h" -#include "run-command.h" -#include "exec_cmd.h" -#include "utf8.h" #include "parse-options.h" -#include "cache-tree.h" #include "diff.h" #include "revision.h" #include "rerere.h" -#include "merge-recursive.h" -#include "refs.h" +#include "dir.h" +#include "sequencer.h" /* * This implements the builtins revert and cherry-pick. @@ -28,569 +20,219 @@ static const char * const revert_usage[] = { "git revert [options] <commit-ish>", + "git revert <subcommand>", NULL }; static const char * const cherry_pick_usage[] = { "git cherry-pick [options] <commit-ish>", + "git cherry-pick <subcommand>", NULL }; -static int edit, no_replay, no_commit, mainline, signoff, allow_ff; -static enum { REVERT, CHERRY_PICK } action; -static struct commit *commit; -static int commit_argc; -static const char **commit_argv; -static int allow_rerere_auto; - -static const char *me; -static const char *strategy; - -#define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION" - -static char *get_encoding(const char *message); - -static const char * const *revert_or_cherry_pick_usage(void) +static const char *action_name(const struct replay_opts *opts) { - return action == REVERT ? revert_usage : cherry_pick_usage; + return opts->action == REPLAY_REVERT ? "revert" : "cherry-pick"; } -static void parse_args(int argc, const char **argv) +static const char * const *revert_or_cherry_pick_usage(struct replay_opts *opts) { - const char * const * usage_str = revert_or_cherry_pick_usage(); - int noop; - struct option options[] = { - OPT_BOOLEAN('n', "no-commit", &no_commit, "don't automatically commit"), - OPT_BOOLEAN('e', "edit", &edit, "edit the commit message"), - OPT_BOOLEAN('r', NULL, &noop, "no-op (backward compatibility)"), - OPT_BOOLEAN('s', "signoff", &signoff, "add Signed-off-by:"), - OPT_INTEGER('m', "mainline", &mainline, "parent number"), - OPT_RERERE_AUTOUPDATE(&allow_rerere_auto), - OPT_STRING(0, "strategy", &strategy, "strategy", "merge strategy"), - OPT_END(), - OPT_END(), - OPT_END(), - }; - - if (action == CHERRY_PICK) { - struct option cp_extra[] = { - OPT_BOOLEAN('x', NULL, &no_replay, "append commit name"), - OPT_BOOLEAN(0, "ff", &allow_ff, "allow fast-forward"), - OPT_END(), - }; - if (parse_options_concat(options, ARRAY_SIZE(options), cp_extra)) - die("program error"); - } - - commit_argc = parse_options(argc, argv, NULL, options, usage_str, - PARSE_OPT_KEEP_ARGV0 | - PARSE_OPT_KEEP_UNKNOWN); - if (commit_argc < 2) - usage_with_options(usage_str, options); - - commit_argv = argv; + return opts->action == REPLAY_REVERT ? revert_usage : cherry_pick_usage; } -struct commit_message { - char *parent_label; - const char *label; - const char *subject; - char *reencoded_message; - const char *message; -}; - -static int get_message(const char *raw_message, struct commit_message *out) +static int option_parse_x(const struct option *opt, + const char *arg, int unset) { - const char *encoding; - const char *abbrev, *subject; - int abbrev_len, subject_len; - char *q; - - if (!raw_message) - return -1; - encoding = get_encoding(raw_message); - if (!encoding) - encoding = "UTF-8"; - if (!git_commit_encoding) - git_commit_encoding = "UTF-8"; - - out->reencoded_message = NULL; - out->message = raw_message; - if (strcmp(encoding, git_commit_encoding)) - out->reencoded_message = reencode_string(raw_message, - git_commit_encoding, encoding); - if (out->reencoded_message) - out->message = out->reencoded_message; - - abbrev = find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV); - abbrev_len = strlen(abbrev); + struct replay_opts **opts_ptr = opt->value; + struct replay_opts *opts = *opts_ptr; - subject_len = find_commit_subject(out->message, &subject); + if (unset) + return 0; - out->parent_label = xmalloc(strlen("parent of ") + abbrev_len + - strlen("... ") + subject_len + 1); - q = out->parent_label; - q = mempcpy(q, "parent of ", strlen("parent of ")); - out->label = q; - q = mempcpy(q, abbrev, abbrev_len); - q = mempcpy(q, "... ", strlen("... ")); - out->subject = q; - q = mempcpy(q, subject, subject_len); - *q = '\0'; + ALLOC_GROW(opts->xopts, opts->xopts_nr + 1, opts->xopts_alloc); + opts->xopts[opts->xopts_nr++] = xstrdup(arg); return 0; } -static void free_message(struct commit_message *msg) -{ - free(msg->parent_label); - free(msg->reencoded_message); -} - -static char *get_encoding(const char *message) -{ - const char *p = message, *eol; - - if (!p) - die ("Could not read commit message of %s", - sha1_to_hex(commit->object.sha1)); - while (*p && *p != '\n') { - for (eol = p + 1; *eol && *eol != '\n'; eol++) - ; /* do nothing */ - if (!prefixcmp(p, "encoding ")) { - char *result = xmalloc(eol - 8 - p); - strlcpy(result, p + 9, eol - 8 - p); - return result; - } - p = eol; - if (*p == '\n') - p++; - } - return NULL; -} - -static void add_message_to_msg(struct strbuf *msgbuf, const char *message) -{ - const char *p = message; - while (*p && (*p != '\n' || p[1] != '\n')) - p++; - - if (!*p) - strbuf_addstr(msgbuf, sha1_to_hex(commit->object.sha1)); - - p += 2; - strbuf_addstr(msgbuf, p); -} - -static void set_author_ident_env(const char *message) +static void verify_opt_compatible(const char *me, const char *base_opt, ...) { - const char *p = message; - if (!p) - die ("Could not read commit message of %s", - sha1_to_hex(commit->object.sha1)); - while (*p && *p != '\n') { - const char *eol; - - for (eol = p; *eol && *eol != '\n'; eol++) - ; /* do nothing */ - if (!prefixcmp(p, "author ")) { - char *line, *pend, *email, *timestamp; + const char *this_opt; + va_list ap; - p += 7; - line = xmemdupz(p, eol - p); - email = strchr(line, '<'); - if (!email) - die ("Could not extract author email from %s", - sha1_to_hex(commit->object.sha1)); - if (email == line) - pend = line; - else - for (pend = email; pend != line + 1 && - isspace(pend[-1]); pend--); - ; /* do nothing */ - *pend = '\0'; - email++; - timestamp = strchr(email, '>'); - if (!timestamp) - die ("Could not extract author time from %s", - sha1_to_hex(commit->object.sha1)); - *timestamp = '\0'; - for (timestamp++; *timestamp && isspace(*timestamp); - timestamp++) - ; /* do nothing */ - setenv("GIT_AUTHOR_NAME", line, 1); - setenv("GIT_AUTHOR_EMAIL", email, 1); - setenv("GIT_AUTHOR_DATE", timestamp, 1); - free(line); - return; - } - p = eol; - if (*p == '\n') - p++; + va_start(ap, base_opt); + while ((this_opt = va_arg(ap, const char *))) { + if (va_arg(ap, int)) + break; } - die ("No author information found in %s", - sha1_to_hex(commit->object.sha1)); -} + va_end(ap); -static void advise(const char *advice, ...) -{ - va_list params; - - va_start(params, advice); - vreportf("hint: ", advice, params); - va_end(params); + if (this_opt) + die(_("%s: %s cannot be used with %s"), me, this_opt, base_opt); } -static void print_advice(void) +static void verify_opt_mutually_compatible(const char *me, ...) { - char *msg = getenv("GIT_CHERRY_PICK_HELP"); + const char *opt1, *opt2 = NULL; + va_list ap; - if (msg) { - fprintf(stderr, "%s\n", msg); - return; + va_start(ap, me); + while ((opt1 = va_arg(ap, const char *))) { + if (va_arg(ap, int)) + break; } - - advise("after resolving the conflicts, mark the corrected paths"); - advise("with 'git add <paths>' or 'git rm <paths>'"); - - if (action == CHERRY_PICK) - advise("and commit the result with 'git commit -c %s'", - find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV)); -} - -static void write_message(struct strbuf *msgbuf, const char *filename) -{ - static struct lock_file msg_file; - - int msg_fd = hold_lock_file_for_update(&msg_file, filename, - LOCK_DIE_ON_ERROR); - if (write_in_full(msg_fd, msgbuf->buf, msgbuf->len) < 0) - die_errno("Could not write to %s.", filename); - strbuf_release(msgbuf); - if (commit_lock_file(&msg_file) < 0) - die("Error wrapping up %s", filename); -} - -static struct tree *empty_tree(void) -{ - struct tree *tree = xcalloc(1, sizeof(struct tree)); - - tree->object.parsed = 1; - tree->object.type = OBJ_TREE; - pretend_sha1_file(NULL, 0, OBJ_TREE, tree->object.sha1); - return tree; -} - -static NORETURN void die_dirty_index(const char *me) -{ - if (read_cache_unmerged()) { - die_resolve_conflict(me); - } else { - if (advice_commit_before_merge) - die("Your local changes would be overwritten by %s.\n" - "Please, commit your changes or stash them to proceed.", me); - else - die("Your local changes would be overwritten by %s.\n", me); - } -} - -static int fast_forward_to(const unsigned char *to, const unsigned char *from) -{ - struct ref_lock *ref_lock; - - read_cache(); - if (checkout_fast_forward(from, to)) - exit(1); /* the callee should have complained already */ - ref_lock = lock_any_ref_for_update("HEAD", from, 0); - return write_ref_sha1(ref_lock, to, "cherry-pick"); -} - -static int do_recursive_merge(struct commit *base, struct commit *next, - const char *base_label, const char *next_label, - unsigned char *head, struct strbuf *msgbuf) -{ - struct merge_options o; - struct tree *result, *next_tree, *base_tree, *head_tree; - int clean, index_fd; - static struct lock_file index_lock; - - index_fd = hold_locked_index(&index_lock, 1); - - read_cache(); - - /* - * NEEDSWORK: cherry-picking between branches with - * different end-of-line normalization is a pain; - * plumb in an option to set o.renormalize? - * (or better: arbitrary -X options) - */ - init_merge_options(&o); - o.ancestor = base ? base_label : "(empty tree)"; - o.branch1 = "HEAD"; - o.branch2 = next ? next_label : "(empty tree)"; - - head_tree = parse_tree_indirect(head); - next_tree = next ? next->tree : empty_tree(); - base_tree = base ? base->tree : empty_tree(); - - clean = merge_trees(&o, - head_tree, - next_tree, base_tree, &result); - - if (active_cache_changed && - (write_cache(index_fd, active_cache, active_nr) || - commit_locked_index(&index_lock))) - die("%s: Unable to write new index file", me); - rollback_lock_file(&index_lock); - - if (!clean) { - int i; - strbuf_addstr(msgbuf, "\nConflicts:\n\n"); - for (i = 0; i < active_nr;) { - struct cache_entry *ce = active_cache[i++]; - if (ce_stage(ce)) { - strbuf_addch(msgbuf, '\t'); - strbuf_addstr(msgbuf, ce->name); - strbuf_addch(msgbuf, '\n'); - while (i < active_nr && !strcmp(ce->name, - active_cache[i]->name)) - i++; - } + if (opt1) { + while ((opt2 = va_arg(ap, const char *))) { + if (va_arg(ap, int)) + break; } } + va_end(ap); - return !clean; -} - -/* - * If we are cherry-pick, and if the merge did not result in - * hand-editing, we will hit this commit and inherit the original - * author date and name. - * If we are revert, or if our cherry-pick results in a hand merge, - * we had better say that the current user is responsible for that. - */ -static int run_git_commit(const char *defmsg) -{ - /* 6 is max possible length of our args array including NULL */ - const char *args[6]; - int i = 0; - - args[i++] = "commit"; - args[i++] = "-n"; - if (signoff) - args[i++] = "-s"; - if (!edit) { - args[i++] = "-F"; - args[i++] = defmsg; - } - args[i] = NULL; - - return run_command_v_opt(args, RUN_GIT_CMD); + if (opt1 && opt2) + die(_("%s: %s cannot be used with %s"), me, opt1, opt2); } -static int do_pick_commit(void) +static void parse_args(int argc, const char **argv, struct replay_opts *opts) { - unsigned char head[20]; - struct commit *base, *next, *parent; - const char *base_label, *next_label; - struct commit_message msg = { NULL, NULL, NULL, NULL, NULL }; - char *defmsg = NULL; - struct strbuf msgbuf = STRBUF_INIT; - int res; - - if (no_commit) { - /* - * We do not intend to commit immediately. We just want to - * merge the differences in, so let's compute the tree - * that represents the "current" state for merge-recursive - * to work on. - */ - if (write_cache_as_tree(head, 0, NULL)) - die ("Your index file is unmerged."); - } else { - if (get_sha1("HEAD", head)) - die ("You do not have a valid HEAD"); - if (index_differs_from("HEAD", 0)) - die_dirty_index(me); - } - discard_cache(); + const char * const * usage_str = revert_or_cherry_pick_usage(opts); + const char *me = action_name(opts); + int remove_state = 0; + int contin = 0; + int rollback = 0; + struct option options[] = { + OPT_BOOLEAN(0, "quit", &remove_state, "end revert or cherry-pick sequence"), + OPT_BOOLEAN(0, "continue", &contin, "resume revert or cherry-pick sequence"), + OPT_BOOLEAN(0, "abort", &rollback, "cancel revert or cherry-pick sequence"), + OPT_BOOLEAN('n', "no-commit", &opts->no_commit, "don't automatically commit"), + OPT_BOOLEAN('e', "edit", &opts->edit, "edit the commit message"), + OPT_NOOP_NOARG('r', NULL), + OPT_BOOLEAN('s', "signoff", &opts->signoff, "add Signed-off-by:"), + OPT_INTEGER('m', "mainline", &opts->mainline, "parent number"), + OPT_RERERE_AUTOUPDATE(&opts->allow_rerere_auto), + OPT_STRING(0, "strategy", &opts->strategy, "strategy", "merge strategy"), + OPT_CALLBACK('X', "strategy-option", &opts, "option", + "option for merge strategy", option_parse_x), + OPT_END(), + OPT_END(), + OPT_END(), + OPT_END(), + OPT_END(), + }; - if (!commit->parents) { - if (action == REVERT) - die ("Cannot revert a root commit"); - parent = NULL; + if (opts->action == REPLAY_PICK) { + struct option cp_extra[] = { + OPT_BOOLEAN('x', NULL, &opts->record_origin, "append commit name"), + OPT_BOOLEAN(0, "ff", &opts->allow_ff, "allow fast-forward"), + OPT_BOOLEAN(0, "allow-empty", &opts->allow_empty, "preserve initially empty commits"), + OPT_BOOLEAN(0, "keep-redundant-commits", &opts->keep_redundant_commits, "keep redundant, empty commits"), + OPT_END(), + }; + if (parse_options_concat(options, ARRAY_SIZE(options), cp_extra)) + die(_("program error")); } - else if (commit->parents->next) { - /* Reverting or cherry-picking a merge commit */ - int cnt; - struct commit_list *p; - - if (!mainline) - die("Commit %s is a merge but no -m option was given.", - sha1_to_hex(commit->object.sha1)); - for (cnt = 1, p = commit->parents; - cnt != mainline && p; - cnt++) - p = p->next; - if (cnt != mainline || !p) - die("Commit %s does not have parent %d", - sha1_to_hex(commit->object.sha1), mainline); - parent = p->item; - } else if (0 < mainline) - die("Mainline was specified but commit %s is not a merge.", - sha1_to_hex(commit->object.sha1)); + argc = parse_options(argc, argv, NULL, options, usage_str, + PARSE_OPT_KEEP_ARGV0 | + PARSE_OPT_KEEP_UNKNOWN); + + /* Check for incompatible subcommands */ + verify_opt_mutually_compatible(me, + "--quit", remove_state, + "--continue", contin, + "--abort", rollback, + NULL); + + /* implies allow_empty */ + if (opts->keep_redundant_commits) + opts->allow_empty = 1; + + /* Set the subcommand */ + if (remove_state) + opts->subcommand = REPLAY_REMOVE_STATE; + else if (contin) + opts->subcommand = REPLAY_CONTINUE; + else if (rollback) + opts->subcommand = REPLAY_ROLLBACK; else - parent = commit->parents->item; - - if (allow_ff && parent && !hashcmp(parent->object.sha1, head)) - return fast_forward_to(commit->object.sha1, head); - - if (parent && parse_commit(parent) < 0) - die("%s: cannot parse parent commit %s", - me, sha1_to_hex(parent->object.sha1)); - - if (get_message(commit->buffer, &msg) != 0) - die("Cannot get commit message for %s", - sha1_to_hex(commit->object.sha1)); - - /* - * "commit" is an existing commit. We would want to apply - * the difference it introduces since its first parent "prev" - * on top of the current HEAD if we are cherry-pick. Or the - * reverse of it if we are revert. - */ - - defmsg = git_pathdup("MERGE_MSG"); - - if (action == REVERT) { - base = commit; - base_label = msg.label; - next = parent; - next_label = msg.parent_label; - strbuf_addstr(&msgbuf, "Revert \""); - strbuf_addstr(&msgbuf, msg.subject); - strbuf_addstr(&msgbuf, "\"\n\nThis reverts commit "); - strbuf_addstr(&msgbuf, sha1_to_hex(commit->object.sha1)); - - if (commit->parents->next) { - strbuf_addstr(&msgbuf, ", reversing\nchanges made to "); - strbuf_addstr(&msgbuf, sha1_to_hex(parent->object.sha1)); - } - strbuf_addstr(&msgbuf, ".\n"); - } else { - base = parent; - base_label = msg.parent_label; - next = commit; - next_label = msg.label; - set_author_ident_env(msg.message); - add_message_to_msg(&msgbuf, msg.message); - if (no_replay) { - strbuf_addstr(&msgbuf, "(cherry picked from commit "); - strbuf_addstr(&msgbuf, sha1_to_hex(commit->object.sha1)); - strbuf_addstr(&msgbuf, ")\n"); + opts->subcommand = REPLAY_NONE; + + /* Check for incompatible command line arguments */ + if (opts->subcommand != REPLAY_NONE) { + char *this_operation; + if (opts->subcommand == REPLAY_REMOVE_STATE) + this_operation = "--quit"; + else if (opts->subcommand == REPLAY_CONTINUE) + this_operation = "--continue"; + else { + assert(opts->subcommand == REPLAY_ROLLBACK); + this_operation = "--abort"; } - } - if (!strategy || !strcmp(strategy, "recursive") || action == REVERT) { - res = do_recursive_merge(base, next, base_label, next_label, - head, &msgbuf); - write_message(&msgbuf, defmsg); - } else { - struct commit_list *common = NULL; - struct commit_list *remotes = NULL; - - write_message(&msgbuf, defmsg); - - commit_list_insert(base, &common); - commit_list_insert(next, &remotes); - res = try_merge_command(strategy, common, - sha1_to_hex(head), remotes); - free_commit_list(common); - free_commit_list(remotes); + verify_opt_compatible(me, this_operation, + "--no-commit", opts->no_commit, + "--signoff", opts->signoff, + "--mainline", opts->mainline, + "--strategy", opts->strategy ? 1 : 0, + "--strategy-option", opts->xopts ? 1 : 0, + "-x", opts->record_origin, + "--ff", opts->allow_ff, + NULL); } - if (res) { - error("could not %s %s... %s", - action == REVERT ? "revert" : "apply", - find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV), - msg.subject); - print_advice(); - rerere(allow_rerere_auto); + if (opts->allow_ff) + verify_opt_compatible(me, "--ff", + "--signoff", opts->signoff, + "--no-commit", opts->no_commit, + "-x", opts->record_origin, + "--edit", opts->edit, + NULL); + + if (opts->subcommand != REPLAY_NONE) { + opts->revs = NULL; } else { - if (!no_commit) - res = run_git_commit(defmsg); + struct setup_revision_opt s_r_opt; + opts->revs = xmalloc(sizeof(*opts->revs)); + init_revisions(opts->revs, NULL); + opts->revs->no_walk = 1; + if (argc < 2) + usage_with_options(usage_str, options); + memset(&s_r_opt, 0, sizeof(s_r_opt)); + s_r_opt.assume_dashdash = 1; + argc = setup_revisions(argc, argv, opts->revs, &s_r_opt); } - free_message(&msg); - free(defmsg); - - return res; -} - -static void prepare_revs(struct rev_info *revs) -{ - int argc; - - init_revisions(revs, NULL); - revs->no_walk = 1; - if (action != REVERT) - revs->reverse = 1; - - argc = setup_revisions(commit_argc, commit_argv, revs, NULL); if (argc > 1) - usage(*revert_or_cherry_pick_usage()); - - if (prepare_revision_walk(revs)) - die("revision walk setup failed"); - - if (!revs->commits) - die("empty commit set passed"); -} - -static int revert_or_cherry_pick(int argc, const char **argv) -{ - struct rev_info revs; - - git_config(git_default_config, NULL); - me = action == REVERT ? "revert" : "cherry-pick"; - setenv(GIT_REFLOG_ACTION, me, 0); - parse_args(argc, argv); - - if (allow_ff) { - if (signoff) - die("cherry-pick --ff cannot be used with --signoff"); - if (no_commit) - die("cherry-pick --ff cannot be used with --no-commit"); - if (no_replay) - die("cherry-pick --ff cannot be used with -x"); - if (edit) - die("cherry-pick --ff cannot be used with --edit"); - } - - if (read_cache() < 0) - die("git %s: failed to read the index", me); - - prepare_revs(&revs); - - while ((commit = get_revision(&revs))) { - int res = do_pick_commit(); - if (res) - return res; - } - - return 0; + usage_with_options(usage_str, options); } int cmd_revert(int argc, const char **argv, const char *prefix) { + struct replay_opts opts; + int res; + + memset(&opts, 0, sizeof(opts)); if (isatty(0)) - edit = 1; - action = REVERT; - return revert_or_cherry_pick(argc, argv); + opts.edit = 1; + opts.action = REPLAY_REVERT; + git_config(git_default_config, NULL); + parse_args(argc, argv, &opts); + res = sequencer_pick_revisions(&opts); + if (res < 0) + die(_("revert failed")); + return res; } int cmd_cherry_pick(int argc, const char **argv, const char *prefix) { - action = CHERRY_PICK; - return revert_or_cherry_pick(argc, argv); + struct replay_opts opts; + int res; + + memset(&opts, 0, sizeof(opts)); + opts.action = REPLAY_PICK; + git_config(git_default_config, NULL); + parse_args(argc, argv, &opts); + res = sequencer_pick_revisions(&opts); + if (res < 0) + die(_("cherry-pick failed")); + return res; } diff --git a/builtin/rm.c b/builtin/rm.c index f3772c84de..90c8a5047c 100644 --- a/builtin/rm.c +++ b/builtin/rm.c @@ -20,15 +20,6 @@ static struct { const char **name; } list; -static void add_list(const char *name) -{ - if (list.nr >= list.alloc) { - list.alloc = alloc_nr(list.alloc); - list.name = xrealloc(list.name, list.alloc * sizeof(const char *)); - } - list.name[list.nr++] = name; -} - static int check_local_mod(unsigned char *head, int index_only) { /* @@ -115,19 +106,19 @@ static int check_local_mod(unsigned char *head, int index_only) */ if (local_changes && staged_changes) { if (!index_only || !(ce->ce_flags & CE_INTENT_TO_ADD)) - errs = error("'%s' has staged content different " + errs = error(_("'%s' has staged content different " "from both the file and the HEAD\n" - "(use -f to force removal)", name); + "(use -f to force removal)"), name); } else if (!index_only) { if (staged_changes) - errs = error("'%s' has changes staged in the index\n" + errs = error(_("'%s' has changes staged in the index\n" "(use --cached to keep the file, " - "or -f to force removal)", name); + "or -f to force removal)"), name); if (local_changes) - errs = error("'%s' has local modifications\n" + errs = error(_("'%s' has local modifications\n" "(use --cached to keep the file, " - "or -f to force removal)", name); + "or -f to force removal)"), name); } } return errs; @@ -139,10 +130,10 @@ static int show_only = 0, force = 0, index_only = 0, recursive = 0, quiet = 0; static int ignore_unmatch = 0; static struct option builtin_rm_options[] = { - OPT__DRY_RUN(&show_only), - OPT__QUIET(&quiet), + OPT__DRY_RUN(&show_only, "dry run"), + OPT__QUIET(&quiet, "do not list removed files"), OPT_BOOLEAN( 0 , "cached", &index_only, "only remove from the index"), - OPT_BOOLEAN('f', "force", &force, "override the up-to-date check"), + OPT__FORCE(&force, "override the up-to-date check"), OPT_BOOLEAN('r', NULL, &recursive, "allow recursive removal"), OPT_BOOLEAN( 0 , "ignore-unmatch", &ignore_unmatch, "exit with a zero status even if nothing matched"), @@ -168,7 +159,7 @@ int cmd_rm(int argc, const char **argv, const char *prefix) newfd = hold_locked_index(&lock_file, 1); if (read_cache() < 0) - die("index file corrupt"); + die(_("index file corrupt")); pathspec = get_pathspec(prefix, argv); refresh_index(&the_index, REFRESH_QUIET, pathspec, NULL, NULL); @@ -182,7 +173,8 @@ int cmd_rm(int argc, const char **argv, const char *prefix) struct cache_entry *ce = active_cache[i]; if (!match_pathspec(pathspec, ce->name, ce_namelen(ce), 0, seen)) continue; - add_list(ce->name); + ALLOC_GROW(list.name, list.nr + 1, list.alloc); + list.name[list.nr++] = ce->name; } if (pathspec) { @@ -191,7 +183,7 @@ int cmd_rm(int argc, const char **argv, const char *prefix) for (i = 0; (match = pathspec[i]) != NULL ; i++) { if (!seen[i]) { if (!ignore_unmatch) { - die("pathspec '%s' did not match any files", + die(_("pathspec '%s' did not match any files"), match); } } @@ -199,7 +191,7 @@ int cmd_rm(int argc, const char **argv, const char *prefix) seen_any = 1; } if (!recursive && seen[i] == MATCHED_RECURSIVELY) - die("not removing '%s' recursively without -r", + die(_("not removing '%s' recursively without -r"), *match ? match : "."); } @@ -235,7 +227,7 @@ int cmd_rm(int argc, const char **argv, const char *prefix) printf("rm '%s'\n", path); if (remove_file_from_cache(path)) - die("git rm: unable to remove %s", path); + die(_("git rm: unable to remove %s"), path); } if (show_only) @@ -265,7 +257,7 @@ int cmd_rm(int argc, const char **argv, const char *prefix) if (active_cache_changed) { if (write_cache(newfd, active_cache, active_nr) || commit_locked_index(&lock_file)) - die("Unable to write new index file"); + die(_("Unable to write new index file")); } return 0; diff --git a/builtin/send-pack.c b/builtin/send-pack.c index 481602d8ae..d5d7105ba2 100644 --- a/builtin/send-pack.c +++ b/builtin/send-pack.c @@ -1,4 +1,4 @@ -#include "cache.h" +#include "builtin.h" #include "commit.h" #include "refs.h" #include "pkt-line.h" @@ -48,6 +48,7 @@ static int pack_objects(int fd, struct ref *refs, struct extra_have_objects *ext NULL, NULL, NULL, + NULL, }; struct child_process po; int i; @@ -57,8 +58,10 @@ static int pack_objects(int fd, struct ref *refs, struct extra_have_objects *ext argv[i++] = "--thin"; if (args->use_ofs_delta) argv[i++] = "--delta-base-offset"; - if (args->quiet) + if (args->quiet || !args->progress) argv[i++] = "-q"; + if (args->progress) + argv[i++] = "--progress"; memset(&po, 0, sizeof(po)); po.argv = argv; po.in = -1; @@ -101,7 +104,7 @@ static int pack_objects(int fd, struct ref *refs, struct extra_have_objects *ext } if (finish_command(&po)) - return error("pack-objects died with strange error"); + return -1; return 0; } @@ -225,8 +228,11 @@ static void print_helper_status(struct ref *ref) static int sideband_demux(int in, int out, void *data) { - int *fd = data; - int ret = recv_sideband("send-pack", fd[0], out); + int *fd = data, ret; +#ifdef NO_PTHREADS + close(fd[1]); +#endif + ret = recv_sideband("send-pack", fd[0], out); close(out); return ret; } @@ -244,6 +250,7 @@ int send_pack(struct send_pack_args *args, int allow_deleting_refs = 0; int status_report = 0; int use_sideband = 0; + int quiet_supported = 0; unsigned cmds_sent = 0; int ret; struct async demux; @@ -257,6 +264,8 @@ int send_pack(struct send_pack_args *args, args->use_ofs_delta = 1; if (server_supports("side-band-64k")) use_sideband = 1; + if (server_supports("quiet")) + quiet_supported = 1; if (!remote_refs) { fprintf(stderr, "No refs in common and none specified; doing nothing.\n" @@ -294,16 +303,18 @@ int send_pack(struct send_pack_args *args, } else { char *old_hex = sha1_to_hex(ref->old_sha1); char *new_hex = sha1_to_hex(ref->new_sha1); - - if (!cmds_sent && (status_report || use_sideband)) { - packet_buf_write(&req_buf, "%s %s %s%c%s%s", - old_hex, new_hex, ref->name, 0, - status_report ? " report-status" : "", - use_sideband ? " side-band-64k" : ""); + int quiet = quiet_supported && (args->quiet || !args->progress); + + if (!cmds_sent && (status_report || use_sideband || args->quiet)) { + packet_buf_write(&req_buf, "%s %s %s%c%s%s%s", + old_hex, new_hex, ref->name, 0, + status_report ? " report-status" : "", + use_sideband ? " side-band-64k" : "", + quiet ? " quiet" : ""); } else packet_buf_write(&req_buf, "%s %s %s", - old_hex, new_hex, ref->name); + old_hex, new_hex, ref->name); ref->status = status_report ? REF_STATUS_EXPECTING_REPORT : REF_STATUS_OK; @@ -328,7 +339,7 @@ int send_pack(struct send_pack_args *args, demux.data = fd; demux.out = -1; if (start_async(&demux)) - die("receive-pack: unable to fork off sideband demultiplexer"); + die("send-pack: unable to fork off sideband demultiplexer"); in = demux.out; } @@ -336,6 +347,10 @@ int send_pack(struct send_pack_args *args, if (pack_objects(out, remote_refs, extra_have, args) < 0) { for (ref = remote_refs; ref; ref = ref->next) ref->status = REF_STATUS_NONE; + if (args->stateless_rpc) + close(out); + if (git_connection_is_socket(conn)) + shutdown(fd[0], SHUT_WR); if (use_sideband) finish_async(&demux); return -1; @@ -395,6 +410,7 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix) const char *receivepack = "git-receive-pack"; int flags; int nonfastforward = 0; + int progress = -1; argv++; for (i = 1; i < argc; i++, argv++) { @@ -429,10 +445,22 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix) args.force_update = 1; continue; } + if (!strcmp(arg, "--quiet")) { + args.quiet = 1; + continue; + } if (!strcmp(arg, "--verbose")) { args.verbose = 1; continue; } + if (!strcmp(arg, "--progress")) { + progress = 1; + continue; + } + if (!strcmp(arg, "--no-progress")) { + progress = 0; + continue; + } if (!strcmp(arg, "--thin")) { args.use_thin_pack = 1; continue; @@ -473,6 +501,10 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix) } } + if (progress == -1) + progress = !args.quiet && isatty(2); + args.progress = progress; + if (args.stateless_rpc) { conn = NULL; fd[0] = 0; @@ -484,8 +516,7 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix) memset(&extra_have, 0, sizeof(extra_have)); - get_remote_heads(fd[0], &remote_refs, 0, NULL, REF_NORMAL, - &extra_have); + get_remote_heads(fd[0], &remote_refs, REF_NORMAL, &extra_have); transport_verify_remote_names(nr_refspecs, refspecs); @@ -499,7 +530,7 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix) flags |= MATCH_REFS_MIRROR; /* match them up */ - if (match_refs(local_refs, &remote_refs, nr_refspecs, refspecs, flags)) + if (match_push_refs(local_refs, &remote_refs, nr_refspecs, refspecs, flags)) return -1; set_ref_status_for_push(remote_refs, args.send_mirror, diff --git a/builtin/shortlog.c b/builtin/shortlog.c index 2135b0dde1..37f3193a33 100644 --- a/builtin/shortlog.c +++ b/builtin/shortlog.c @@ -29,9 +29,6 @@ static int compare_by_number(const void *a1, const void *a2) return -1; } -const char *format_subject(struct strbuf *sb, const char *msg, - const char *line_separator); - static void insert_one_record(struct shortlog *log, const char *author, const char *oneline) @@ -141,9 +138,8 @@ void shortlog_add_commit(struct shortlog *log, struct commit *commit) const char *author = NULL, *buffer; struct strbuf buf = STRBUF_INIT; struct strbuf ufbuf = STRBUF_INIT; - struct pretty_print_context ctx = {0}; - pretty_print_commit(CMIT_FMT_RAW, commit, &buf, &ctx); + pp_commit_easy(CMIT_FMT_RAW, commit, &buf); buffer = buf.buf; while (*buffer && *buffer != '\n') { const char *eol = strchr(buffer, '\n'); @@ -158,15 +154,16 @@ void shortlog_add_commit(struct shortlog *log, struct commit *commit) buffer = eol; } if (!author) - die("Missing author: %s", + die(_("Missing author: %s"), sha1_to_hex(commit->object.sha1)); if (log->user_format) { struct pretty_print_context ctx = {0}; + ctx.fmt = CMIT_FMT_USERFORMAT; ctx.abbrev = log->abbrev; ctx.subject = ""; ctx.after_subject = ""; ctx.date_mode = DATE_NORMAL; - pretty_print_commit(CMIT_FMT_USERFORMAT, commit, &ufbuf, &ctx); + pretty_print_commit(&ctx, commit, &ufbuf); buffer = ufbuf.buf; } else if (*buffer) { buffer++; @@ -181,7 +178,7 @@ static void get_from_rev(struct rev_info *rev, struct shortlog *log) struct commit *commit; if (prepare_revision_walk(rev)) - die("revision walk setup failed"); + die(_("revision walk setup failed")); while ((commit = get_revision(rev)) != NULL) shortlog_add_commit(log, commit); } @@ -268,8 +265,8 @@ int cmd_shortlog(int argc, const char **argv, const char *prefix) git_config(git_default_config, NULL); shortlog_init(&log); init_revisions(&rev, prefix); - parse_options_start(&ctx, argc, argv, prefix, PARSE_OPT_KEEP_DASHDASH | - PARSE_OPT_KEEP_ARGV0); + parse_options_start(&ctx, argc, argv, prefix, options, + PARSE_OPT_KEEP_DASHDASH | PARSE_OPT_KEEP_ARGV0); for (;;) { switch (parse_options_step(&ctx, options, shortlog_usage)) { @@ -284,7 +281,7 @@ parse_done: argc = parse_options_end(&ctx); if (setup_revisions(argc, argv, &rev, NULL) != 1) { - error("unrecognized argument: %s", argv[1]); + error(_("unrecognized argument: %s"), argv[1]); usage_with_options(shortlog_usage, options); } @@ -296,7 +293,7 @@ parse_done: add_head_to_pending(&rev); if (rev.pending.nr == 0) { if (isatty(0)) - fprintf(stderr, "(reading log message from standard input)\n"); + fprintf(stderr, _("(reading log message from standard input)\n")); read_from_stdin(&log); } else diff --git a/builtin/show-branch.c b/builtin/show-branch.c index 8663ccaa99..a59e088cf5 100644 --- a/builtin/show-branch.c +++ b/builtin/show-branch.c @@ -12,16 +12,6 @@ static const char* show_branch_usage[] = { }; static int showbranch_use_color = -1; -static char column_colors[][COLOR_MAXLEN] = { - GIT_COLOR_RED, - GIT_COLOR_GREEN, - GIT_COLOR_YELLOW, - GIT_COLOR_BLUE, - GIT_COLOR_MAGENTA, - GIT_COLOR_CYAN, -}; - -#define COLUMN_COLORS_MAX (ARRAY_SIZE(column_colors)) static int default_num; static int default_alloc; @@ -36,14 +26,14 @@ static const char **default_arg; static const char *get_color_code(int idx) { - if (showbranch_use_color) - return column_colors[idx]; + if (want_color(showbranch_use_color)) + return column_colors_ansi[idx % column_colors_ansi_max]; return ""; } static const char *get_color_reset_code(void) { - if (showbranch_use_color) + if (want_color(showbranch_use_color)) return GIT_COLOR_RESET; return ""; } @@ -243,7 +233,7 @@ static void join_revs(struct commit_list **list_p, if (mark_seen(p, seen_p) && !still_interesting) extra--; p->object.flags |= flags; - insert_by_date(p, list_p); + commit_list_insert_by_date(p, list_p); } } @@ -293,8 +283,7 @@ static void show_one_commit(struct commit *commit, int no_name) struct commit_name *name = commit->util; if (commit->object.parsed) { - struct pretty_print_context ctx = {0}; - pretty_print_commit(CMIT_FMT_ONELINE, commit, &pretty, &ctx); + pp_commit_easy(CMIT_FMT_ONELINE, commit, &pretty); pretty_str = pretty.buf; } if (!prefixcmp(pretty_str, "[PATCH] ")) @@ -584,7 +573,7 @@ static int git_show_branch_config(const char *var, const char *value, void *cb) } if (!strcmp(var, "color.showbranch")) { - showbranch_use_color = git_config_colorbool(var, value, -1); + showbranch_use_color = git_config_colorbool(var, value); return 0; } @@ -696,9 +685,6 @@ int cmd_show_branch(int ac, const char **av, const char *prefix) git_config(git_show_branch_config, NULL); - if (showbranch_use_color == -1) - showbranch_use_color = git_use_color_default; - /* If nothing is specified, try the default first */ if (ac == 1 && default_num) { ac = default_num; @@ -740,10 +726,8 @@ int cmd_show_branch(int ac, const char **av, const char *prefix) if (ac == 0) { static const char *fake_av[2]; - const char *refname; - refname = resolve_ref("HEAD", sha1, 1, NULL); - fake_av[0] = xstrdup(refname); + fake_av[0] = resolve_refdup("HEAD", sha1, 1, NULL); fake_av[1] = NULL; av = fake_av; ac = 1; @@ -805,7 +789,7 @@ int cmd_show_branch(int ac, const char **av, const char *prefix) } } - head_p = resolve_ref("HEAD", head_sha1, 1, NULL); + head_p = resolve_ref_unsafe("HEAD", head_sha1, 1, NULL); if (head_p) { head_len = strlen(head_p); memcpy(head, head_p, head_len + 1); @@ -859,7 +843,7 @@ int cmd_show_branch(int ac, const char **av, const char *prefix) */ commit->object.flags |= flag; if (commit->object.flags == flag) - insert_by_date(commit, &list); + commit_list_insert_by_date(commit, &list); rev[num_rev] = commit; } for (i = 0; i < num_rev; i++) @@ -868,7 +852,7 @@ int cmd_show_branch(int ac, const char **av, const char *prefix) if (0 <= extra) join_revs(&list, &seen, num_rev, extra); - sort_by_date(&seen); + commit_list_sort_by_date(&seen); if (merge_base) return show_merge_base(seen, num_rev); @@ -892,7 +876,7 @@ int cmd_show_branch(int ac, const char **av, const char *prefix) for (j = 0; j < i; j++) putchar(' '); printf("%s%c%s [%s] ", - get_color_code(i % COLUMN_COLORS_MAX), + get_color_code(i), is_head ? '*' : '!', get_color_reset_code(), ref_name[i]); } @@ -954,7 +938,7 @@ int cmd_show_branch(int ac, const char **av, const char *prefix) else mark = '+'; printf("%s%c%s", - get_color_code(i % COLUMN_COLORS_MAX), + get_color_code(i), mark, get_color_reset_code()); } putchar(' '); diff --git a/builtin/show-ref.c b/builtin/show-ref.c index be9b512eeb..3911661900 100644 --- a/builtin/show-ref.c +++ b/builtin/show-ref.c @@ -145,7 +145,7 @@ static int exclude_existing(const char *match) if (strncmp(ref, match, matchlen)) continue; } - if (check_ref_format(ref)) { + if (check_refname_format(ref, 0)) { warning("ref '%s' ignored", ref); continue; } @@ -193,7 +193,8 @@ static const struct option show_ref_options[] = { "only show SHA1 hash using <n> digits", PARSE_OPT_OPTARG, &hash_callback }, OPT__ABBREV(&abbrev), - OPT__QUIET(&quiet), + OPT__QUIET(&quiet, + "do not print results to stdout (useful with --verify)"), { OPTION_CALLBACK, 0, "exclude-existing", &exclude_existing_arg, "pattern", "show refs from stdin that aren't in local repository", PARSE_OPT_OPTARG | PARSE_OPT_NONEG, exclude_existing_callback }, @@ -224,7 +225,7 @@ int cmd_show_ref(int argc, const char **argv, const char *prefix) unsigned char sha1[20]; if (!prefixcmp(*pattern, "refs/") && - resolve_ref(*pattern, sha1, 1, NULL)) { + !read_ref(*pattern, sha1)) { if (!quiet) show_one(*pattern, sha1); } diff --git a/builtin/stripspace.c b/builtin/stripspace.c index 4d3b93fedb..f16986c0ae 100644 --- a/builtin/stripspace.c +++ b/builtin/stripspace.c @@ -22,8 +22,6 @@ static size_t cleanup(char *line, size_t len) * Remove empty lines from the beginning and end * and also trailing spaces from every line. * - * Note that the buffer will not be NUL-terminated. - * * Turn multiple consecutive empty lines between paragraphs * into just one empty line. * @@ -77,7 +75,7 @@ int cmd_stripspace(int argc, const char **argv, const char *prefix) !strcmp(argv[1], "--strip-comments"))) strip_comments = 1; else if (argc > 1) - usage("git stripspace [-s | --strip-comments] < <stream>"); + usage("git stripspace [-s | --strip-comments] < input"); if (strbuf_read(&buf, 0, 1024) < 0) die_errno("could not read the input"); diff --git a/builtin/symbolic-ref.c b/builtin/symbolic-ref.c index ca855a5eb2..801d62ece5 100644 --- a/builtin/symbolic-ref.c +++ b/builtin/symbolic-ref.c @@ -8,13 +8,15 @@ static const char * const git_symbolic_ref_usage[] = { NULL }; +static int shorten; + static void check_symref(const char *HEAD, int quiet) { unsigned char sha1[20]; int flag; - const char *refs_heads_master = resolve_ref(HEAD, sha1, 0, &flag); + const char *refname = resolve_ref_unsafe(HEAD, sha1, 0, &flag); - if (!refs_heads_master) + if (!refname) die("No such ref: %s", HEAD); else if (!(flag & REF_ISSYMREF)) { if (!quiet) @@ -22,7 +24,9 @@ static void check_symref(const char *HEAD, int quiet) else exit(1); } - puts(refs_heads_master); + if (shorten) + refname = shorten_unambiguous_ref(refname, 0); + puts(refname); } int cmd_symbolic_ref(int argc, const char **argv, const char *prefix) @@ -30,7 +34,9 @@ int cmd_symbolic_ref(int argc, const char **argv, const char *prefix) int quiet = 0; const char *msg = NULL; struct option options[] = { - OPT__QUIET(&quiet), + OPT__QUIET(&quiet, + "suppress error message for non-symbolic (detached) refs"), + OPT_BOOL(0, "short", &shorten, "shorten ref output"), OPT_STRING('m', NULL, &msg, "reason", "reason of the update"), OPT_END(), }; diff --git a/builtin/tag.c b/builtin/tag.c index d311491e49..7b1be85e48 100644 --- a/builtin/tag.c +++ b/builtin/tag.c @@ -12,93 +12,177 @@ #include "tag.h" #include "run-command.h" #include "parse-options.h" +#include "diff.h" +#include "revision.h" +#include "gpg-interface.h" +#include "sha1-array.h" +#include "column.h" static const char * const git_tag_usage[] = { "git tag [-a|-s|-u <key-id>] [-f] [-m <msg>|-F <file>] <tagname> [<head>]", "git tag -d <tagname>...", - "git tag -l [-n[<num>]] [<pattern>]", + "git tag -l [-n[<num>]] [--contains <commit>] [--points-at <object>] " + "\n\t\t[<pattern>...]", "git tag -v <tagname>...", NULL }; -static char signingkey[1000]; - struct tag_filter { - const char *pattern; + const char **patterns; int lines; struct commit_list *with_commit; }; -#define PGP_SIGNATURE "-----BEGIN PGP SIGNATURE-----" +static struct sha1_array points_at; +static unsigned int colopts; + +static int match_pattern(const char **patterns, const char *ref) +{ + /* no pattern means match everything */ + if (!*patterns) + return 1; + for (; *patterns; patterns++) + if (!fnmatch(*patterns, ref, 0)) + return 1; + return 0; +} + +static const unsigned char *match_points_at(const char *refname, + const unsigned char *sha1) +{ + const unsigned char *tagged_sha1 = NULL; + struct object *obj; + + if (sha1_array_lookup(&points_at, sha1) >= 0) + return sha1; + obj = parse_object(sha1); + if (!obj) + die(_("malformed object at '%s'"), refname); + if (obj->type == OBJ_TAG) + tagged_sha1 = ((struct tag *)obj)->tagged->sha1; + if (tagged_sha1 && sha1_array_lookup(&points_at, tagged_sha1) >= 0) + return tagged_sha1; + return NULL; +} + +static int in_commit_list(const struct commit_list *want, struct commit *c) +{ + for (; want; want = want->next) + if (!hashcmp(want->item->object.sha1, c->object.sha1)) + return 1; + return 0; +} + +static int contains_recurse(struct commit *candidate, + const struct commit_list *want) +{ + struct commit_list *p; + + /* was it previously marked as containing a want commit? */ + if (candidate->object.flags & TMP_MARK) + return 1; + /* or marked as not possibly containing a want commit? */ + if (candidate->object.flags & UNINTERESTING) + return 0; + /* or are we it? */ + if (in_commit_list(want, candidate)) + return 1; + + if (parse_commit(candidate) < 0) + return 0; + + /* Otherwise recurse and mark ourselves for future traversals. */ + for (p = candidate->parents; p; p = p->next) { + if (contains_recurse(p->item, want)) { + candidate->object.flags |= TMP_MARK; + return 1; + } + } + candidate->object.flags |= UNINTERESTING; + return 0; +} + +static int contains(struct commit *candidate, const struct commit_list *want) +{ + return contains_recurse(candidate, want); +} + +static void show_tag_lines(const unsigned char *sha1, int lines) +{ + int i; + unsigned long size; + enum object_type type; + char *buf, *sp, *eol; + size_t len; + + buf = read_sha1_file(sha1, &type, &size); + if (!buf) + die_errno("unable to read object %s", sha1_to_hex(sha1)); + if (type != OBJ_COMMIT && type != OBJ_TAG) + goto free_return; + if (!size) + die("an empty %s object %s?", + typename(type), sha1_to_hex(sha1)); + + /* skip header */ + sp = strstr(buf, "\n\n"); + if (!sp) + goto free_return; + + /* only take up to "lines" lines, and strip the signature from a tag */ + if (type == OBJ_TAG) + size = parse_signature(buf, size); + for (i = 0, sp += 2; i < lines && sp < buf + size; i++) { + if (i) + printf("\n "); + eol = memchr(sp, '\n', size - (sp - buf)); + len = eol ? eol - sp : size - (sp - buf); + fwrite(sp, len, 1, stdout); + if (!eol) + break; + sp = eol + 1; + } +free_return: + free(buf); +} static int show_reference(const char *refname, const unsigned char *sha1, int flag, void *cb_data) { struct tag_filter *filter = cb_data; - if (!fnmatch(filter->pattern, refname, 0)) { - int i; - unsigned long size; - enum object_type type; - char *buf, *sp, *eol; - size_t len; - + if (match_pattern(filter->patterns, refname)) { if (filter->with_commit) { struct commit *commit; commit = lookup_commit_reference_gently(sha1, 1); if (!commit) return 0; - if (!is_descendant_of(commit, filter->with_commit)) + if (!contains(commit, filter->with_commit)) return 0; } + if (points_at.nr && !match_points_at(refname, sha1)) + return 0; + if (!filter->lines) { printf("%s\n", refname); return 0; } printf("%-15s ", refname); - - buf = read_sha1_file(sha1, &type, &size); - if (!buf || !size) - return 0; - - /* skip header */ - sp = strstr(buf, "\n\n"); - if (!sp) { - free(buf); - return 0; - } - /* only take up to "lines" lines, and strip the signature */ - for (i = 0, sp += 2; - i < filter->lines && sp < buf + size && - prefixcmp(sp, PGP_SIGNATURE "\n"); - i++) { - if (i) - printf("\n "); - eol = memchr(sp, '\n', size - (sp - buf)); - len = eol ? eol - sp : size - (sp - buf); - fwrite(sp, len, 1, stdout); - if (!eol) - break; - sp = eol + 1; - } + show_tag_lines(sha1, filter->lines); putchar('\n'); - free(buf); } return 0; } -static int list_tags(const char *pattern, int lines, +static int list_tags(const char **patterns, int lines, struct commit_list *with_commit) { struct tag_filter filter; - if (pattern == NULL) - pattern = "*"; - - filter.pattern = pattern; + filter.patterns = patterns; filter.lines = lines; filter.with_commit = with_commit; @@ -120,12 +204,12 @@ static int for_each_tag_name(const char **argv, each_tag_name_fn fn) for (p = argv; *p; p++) { if (snprintf(ref, sizeof(ref), "refs/tags/%s", *p) >= sizeof(ref)) { - error("tag name too long: %.*s...", 50, *p); + error(_("tag name too long: %.*s..."), 50, *p); had_error = 1; continue; } - if (!resolve_ref(ref, sha1, 1, NULL)) { - error("tag '%s' not found.", *p); + if (read_ref(ref, sha1)) { + error(_("tag '%s' not found."), *p); had_error = 1; continue; } @@ -140,7 +224,7 @@ static int delete_tag(const char *name, const char *ref, { if (delete_ref(ref, sha1, 0)) return 1; - printf("Deleted tag '%s' (was %s)\n", name, find_unique_abbrev(sha1, DEFAULT_ABBREV)); + printf(_("Deleted tag '%s' (was %s)\n"), name, find_unique_abbrev(sha1, DEFAULT_ABBREV)); return 0; } @@ -152,89 +236,37 @@ static int verify_tag(const char *name, const char *ref, argv_verify_tag[2] = sha1_to_hex(sha1); if (run_command_v_opt(argv_verify_tag, RUN_GIT_CMD)) - return error("could not verify the tag '%s'", name); + return error(_("could not verify the tag '%s'"), name); return 0; } static int do_sign(struct strbuf *buffer) { - struct child_process gpg; - const char *args[4]; - char *bracket; - int len; - int i, j; - - if (!*signingkey) { - if (strlcpy(signingkey, git_committer_info(IDENT_ERROR_ON_NO_NAME), - sizeof(signingkey)) > sizeof(signingkey) - 1) - return error("committer info too long."); - bracket = strchr(signingkey, '>'); - if (bracket) - bracket[1] = '\0'; - } - - /* When the username signingkey is bad, program could be terminated - * because gpg exits without reading and then write gets SIGPIPE. */ - signal(SIGPIPE, SIG_IGN); - - memset(&gpg, 0, sizeof(gpg)); - gpg.argv = args; - gpg.in = -1; - gpg.out = -1; - args[0] = "gpg"; - args[1] = "-bsau"; - args[2] = signingkey; - args[3] = NULL; - - if (start_command(&gpg)) - return error("could not run gpg."); - - if (write_in_full(gpg.in, buffer->buf, buffer->len) != buffer->len) { - close(gpg.in); - close(gpg.out); - finish_command(&gpg); - return error("gpg did not accept the tag data"); - } - close(gpg.in); - len = strbuf_read(buffer, gpg.out, 1024); - close(gpg.out); - - if (finish_command(&gpg) || !len || len < 0) - return error("gpg failed to sign the tag"); - - /* Strip CR from the line endings, in case we are on Windows. */ - for (i = j = 0; i < buffer->len; i++) - if (buffer->buf[i] != '\r') { - if (i != j) - buffer->buf[j] = buffer->buf[i]; - j++; - } - strbuf_setlen(buffer, j); - - return 0; + return sign_buffer(buffer, buffer, get_signing_key()); } static const char tag_template[] = - "\n" + N_("\n" "#\n" "# Write a tag message\n" - "#\n"; + "# Lines starting with '#' will be ignored.\n" + "#\n"); -static void set_signingkey(const char *value) -{ - if (strlcpy(signingkey, value, sizeof(signingkey)) >= sizeof(signingkey)) - die("signing key value too long (%.10s...)", value); -} +static const char tag_template_nocleanup[] = + N_("\n" + "#\n" + "# Write a tag message\n" + "# Lines starting with '#' will be kept; you may remove them" + " yourself if you want to.\n" + "#\n"); static int git_tag_config(const char *var, const char *value, void *cb) { - if (!strcmp(var, "user.signingkey")) { - if (!value) - return config_error_nonbool(var); - set_signingkey(value); - return 0; - } - + int status = git_gpg_config(var, value, cb); + if (status) + return status; + if (!prefixcmp(var, "column.")) + return git_column_config(var, value, "tag", &colopts); return git_default_config(var, value, cb); } @@ -242,8 +274,7 @@ static void write_tag_body(int fd, const unsigned char *sha1) { unsigned long size; enum object_type type; - char *buf, *sp, *eob; - size_t len; + char *buf, *sp; buf = read_sha1_file(sha1, &type, &size); if (!buf) @@ -256,12 +287,7 @@ static void write_tag_body(int fd, const unsigned char *sha1) return; } sp += 2; /* skip the 2 LFs */ - eob = strstr(sp, "\n" PGP_SIGNATURE "\n"); - if (eob) - len = eob - sp; - else - len = buf + size - sp; - write_or_die(fd, sp, len); + write_or_die(fd, sp, parse_signature(sp, buf + size - sp)); free(buf); } @@ -269,14 +295,24 @@ static void write_tag_body(int fd, const unsigned char *sha1) static int build_tag_object(struct strbuf *buf, int sign, unsigned char *result) { if (sign && do_sign(buf) < 0) - return error("unable to sign the tag"); + return error(_("unable to sign the tag")); if (write_sha1_file(buf->buf, buf->len, tag_type, result) < 0) - return error("unable to write tag file"); + return error(_("unable to write tag file")); return 0; } +struct create_tag_options { + unsigned int message_given:1; + unsigned int sign; + enum { + CLEANUP_NONE, + CLEANUP_SPACE, + CLEANUP_ALL + } cleanup_mode; +}; + static void create_tag(const unsigned char *object, const char *tag, - struct strbuf *buf, int message, int sign, + struct strbuf *buf, struct create_tag_options *opt, unsigned char *prev, unsigned char *result) { enum object_type type; @@ -286,7 +322,7 @@ static void create_tag(const unsigned char *object, const char *tag, type = sha1_object_info(object, NULL); if (type <= OBJ_NONE) - die("bad object type."); + die(_("bad object type.")); header_len = snprintf(header_buf, sizeof(header_buf), "object %s\n" @@ -296,43 +332,48 @@ static void create_tag(const unsigned char *object, const char *tag, sha1_to_hex(object), typename(type), tag, - git_committer_info(IDENT_ERROR_ON_NO_NAME)); + git_committer_info(IDENT_STRICT)); if (header_len > sizeof(header_buf) - 1) - die("tag header too big."); + die(_("tag header too big.")); - if (!message) { + if (!opt->message_given) { int fd; /* write the template message before editing: */ path = git_pathdup("TAG_EDITMSG"); fd = open(path, O_CREAT | O_TRUNC | O_WRONLY, 0600); if (fd < 0) - die_errno("could not create file '%s'", path); + die_errno(_("could not create file '%s'"), path); if (!is_null_sha1(prev)) write_tag_body(fd, prev); + else if (opt->cleanup_mode == CLEANUP_ALL) + write_or_die(fd, _(tag_template), + strlen(_(tag_template))); else - write_or_die(fd, tag_template, strlen(tag_template)); + write_or_die(fd, _(tag_template_nocleanup), + strlen(_(tag_template_nocleanup))); close(fd); if (launch_editor(path, buf, NULL)) { fprintf(stderr, - "Please supply the message using either -m or -F option.\n"); + _("Please supply the message using either -m or -F option.\n")); exit(1); } } - stripspace(buf, 1); + if (opt->cleanup_mode != CLEANUP_NONE) + stripspace(buf, opt->cleanup_mode == CLEANUP_ALL); - if (!message && !buf->len) - die("no tag message?"); + if (!opt->message_given && !buf->len) + die(_("no tag message?")); strbuf_insert(buf, 0, header_buf, header_len); - if (build_tag_object(buf, sign, result) < 0) { + if (build_tag_object(buf, opt->sign, result) < 0) { if (path) - fprintf(stderr, "The tag message has been left in %s\n", + fprintf(stderr, _("The tag message has been left in %s\n"), path); exit(128); } @@ -360,37 +401,69 @@ static int parse_msg_arg(const struct option *opt, const char *arg, int unset) return 0; } +static int strbuf_check_tag_ref(struct strbuf *sb, const char *name) +{ + if (name[0] == '-') + return -1; + + strbuf_reset(sb); + strbuf_addf(sb, "refs/tags/%s", name); + + return check_refname_format(sb->buf, 0); +} + +static int parse_opt_points_at(const struct option *opt __attribute__((unused)), + const char *arg, int unset) +{ + unsigned char sha1[20]; + + if (unset) { + sha1_array_clear(&points_at); + return 0; + } + if (!arg) + return error(_("switch 'points-at' requires an object")); + if (get_sha1(arg, sha1)) + return error(_("malformed object name '%s'"), arg); + sha1_array_append(&points_at, sha1); + return 0; +} + int cmd_tag(int argc, const char **argv, const char *prefix) { struct strbuf buf = STRBUF_INIT; + struct strbuf ref = STRBUF_INIT; unsigned char object[20], prev[20]; - char ref[PATH_MAX]; const char *object_ref, *tag; struct ref_lock *lock; - - int annotate = 0, sign = 0, force = 0, lines = -1, - list = 0, delete = 0, verify = 0; + struct create_tag_options opt; + char *cleanup_arg = NULL; + int annotate = 0, force = 0, lines = -1, list = 0, + delete = 0, verify = 0; const char *msgfile = NULL, *keyid = NULL; struct msg_arg msg = { 0, STRBUF_INIT }; struct commit_list *with_commit = NULL; struct option options[] = { - OPT_BOOLEAN('l', NULL, &list, "list tag names"), + OPT_BOOLEAN('l', "list", &list, "list tag names"), { OPTION_INTEGER, 'n', NULL, &lines, "n", "print <n> lines of each tag message", PARSE_OPT_OPTARG, NULL, 1 }, - OPT_BOOLEAN('d', NULL, &delete, "delete tags"), - OPT_BOOLEAN('v', NULL, &verify, "verify tags"), + OPT_BOOLEAN('d', "delete", &delete, "delete tags"), + OPT_BOOLEAN('v', "verify", &verify, "verify tags"), OPT_GROUP("Tag creation options"), - OPT_BOOLEAN('a', NULL, &annotate, + OPT_BOOLEAN('a', "annotate", &annotate, "annotated tag, needs a message"), - OPT_CALLBACK('m', NULL, &msg, "msg", - "message for the tag", parse_msg_arg), - OPT_FILENAME('F', NULL, &msgfile, "message in a file"), - OPT_BOOLEAN('s', NULL, &sign, "annotated and GPG-signed tag"), - OPT_STRING('u', NULL, &keyid, "key-id", + OPT_CALLBACK('m', "message", &msg, "message", + "tag message", parse_msg_arg), + OPT_FILENAME('F', "file", &msgfile, "read message from file"), + OPT_BOOLEAN('s', "sign", &opt.sign, "annotated and GPG-signed tag"), + OPT_STRING(0, "cleanup", &cleanup_arg, "mode", + "how to strip spaces and #comments from message"), + OPT_STRING('u', "local-user", &keyid, "key-id", "use another key to sign the tag"), - OPT_BOOLEAN('f', "force", &force, "replace the tag if exists"), + OPT__FORCE(&force, "replace the tag if exists"), + OPT_COLUMN(0, "column", &colopts, "show tag list in columns"), OPT_GROUP("Tag listing options"), { @@ -399,18 +472,24 @@ int cmd_tag(int argc, const char **argv, const char *prefix) PARSE_OPT_LASTARG_DEFAULT, parse_opt_with_commit, (intptr_t)"HEAD", }, + { + OPTION_CALLBACK, 0, "points-at", NULL, "object", + "print only tags of the object", 0, parse_opt_points_at + }, OPT_END() }; git_config(git_tag_config, NULL); + memset(&opt, 0, sizeof(opt)); + argc = parse_options(argc, argv, prefix, options, git_tag_usage, 0); if (keyid) { - sign = 1; - set_signingkey(keyid); + opt.sign = 1; + set_signing_key(keyid); } - if (sign) + if (opt.sign) annotate = 1; if (argc == 0 && !(delete || verify)) list = 1; @@ -421,13 +500,31 @@ int cmd_tag(int argc, const char **argv, const char *prefix) if (list + delete + verify > 1) usage_with_options(git_tag_usage, options); - if (list) - return list_tags(argv[0], lines == -1 ? 0 : lines, - with_commit); + finalize_colopts(&colopts, -1); + if (list && lines != -1) { + if (explicitly_enable_column(colopts)) + die(_("--column and -n are incompatible")); + colopts = 0; + } + if (list) { + int ret; + if (column_active(colopts)) { + struct column_options copts; + memset(&copts, 0, sizeof(copts)); + copts.padding = 2; + run_column_filter(colopts, &copts); + } + ret = list_tags(argv, lines == -1 ? 0 : lines, with_commit); + if (column_active(colopts)) + stop_column_filter(); + return ret; + } if (lines != -1) - die("-n option is only allowed with -l."); + die(_("-n option is only allowed with -l.")); if (with_commit) - die("--contains option is only allowed with -l."); + die(_("--contains option is only allowed with -l.")); + if (points_at.nr) + die(_("--points-at option is only allowed with -l.")); if (delete) return for_each_tag_name(argv, delete_tag); if (verify) @@ -435,17 +532,17 @@ int cmd_tag(int argc, const char **argv, const char *prefix) if (msg.given || msgfile) { if (msg.given && msgfile) - die("only one -F or -m option is allowed."); + die(_("only one -F or -m option is allowed.")); annotate = 1; if (msg.given) strbuf_addbuf(&buf, &(msg.buf)); else { if (!strcmp(msgfile, "-")) { if (strbuf_read(&buf, 0, 1024) < 0) - die_errno("cannot read '%s'", msgfile); + die_errno(_("cannot read '%s'"), msgfile); } else { if (strbuf_read_file(&buf, msgfile, 1024) < 0) - die_errno("could not open or read '%s'", + die_errno(_("could not open or read '%s'"), msgfile); } } @@ -455,33 +552,42 @@ int cmd_tag(int argc, const char **argv, const char *prefix) object_ref = argc == 2 ? argv[1] : "HEAD"; if (argc > 2) - die("too many params"); + die(_("too many params")); if (get_sha1(object_ref, object)) - die("Failed to resolve '%s' as a valid ref.", object_ref); + die(_("Failed to resolve '%s' as a valid ref."), object_ref); - if (snprintf(ref, sizeof(ref), "refs/tags/%s", tag) > sizeof(ref) - 1) - die("tag name too long: %.*s...", 50, tag); - if (check_ref_format(ref)) - die("'%s' is not a valid tag name.", tag); + if (strbuf_check_tag_ref(&ref, tag)) + die(_("'%s' is not a valid tag name."), tag); - if (!resolve_ref(ref, prev, 1, NULL)) + if (read_ref(ref.buf, prev)) hashclr(prev); else if (!force) - die("tag '%s' already exists", tag); + die(_("tag '%s' already exists"), tag); + + opt.message_given = msg.given || msgfile; + + if (!cleanup_arg || !strcmp(cleanup_arg, "strip")) + opt.cleanup_mode = CLEANUP_ALL; + else if (!strcmp(cleanup_arg, "verbatim")) + opt.cleanup_mode = CLEANUP_NONE; + else if (!strcmp(cleanup_arg, "whitespace")) + opt.cleanup_mode = CLEANUP_SPACE; + else + die(_("Invalid cleanup mode %s"), cleanup_arg); if (annotate) - create_tag(object, tag, &buf, msg.given || msgfile, - sign, prev, object); + create_tag(object, tag, &buf, &opt, prev, object); - lock = lock_any_ref_for_update(ref, prev, 0); + lock = lock_any_ref_for_update(ref.buf, prev, 0); if (!lock) - die("%s: cannot lock the ref", ref); + die(_("%s: cannot lock the ref"), ref.buf); if (write_ref_sha1(lock, object, NULL) < 0) - die("%s: cannot update the ref", ref); + die(_("%s: cannot update the ref"), ref.buf); if (force && hashcmp(prev, object)) - printf("Updated tag '%s' (was %s)\n", tag, find_unique_abbrev(prev, DEFAULT_ABBREV)); + printf(_("Updated tag '%s' (was %s)\n"), tag, find_unique_abbrev(prev, DEFAULT_ABBREV)); strbuf_release(&buf); + strbuf_release(&ref); return 0; } diff --git a/builtin/unpack-file.c b/builtin/unpack-file.c index 608590ada8..19200291a2 100644 --- a/builtin/unpack-file.c +++ b/builtin/unpack-file.c @@ -1,6 +1,4 @@ -#include "cache.h" -#include "blob.h" -#include "exec_cmd.h" +#include "builtin.h" static char *create_temp_file(unsigned char *sha1) { diff --git a/builtin/unpack-objects.c b/builtin/unpack-objects.c index f63973c914..2217d7b3ae 100644 --- a/builtin/unpack-objects.c +++ b/builtin/unpack-objects.c @@ -90,7 +90,7 @@ static void use(int bytes) static void *get_data(unsigned long size) { - z_stream stream; + git_zstream stream; void *buf = xmalloc(size); memset(&stream, 0, sizeof(stream)); @@ -107,7 +107,7 @@ static void *get_data(unsigned long size) if (stream.total_out == size && ret == Z_STREAM_END) break; if (ret != Z_OK) { - error("inflate returned %d\n", ret); + error("inflate returned %d", ret); free(buf); buf = NULL; if (!recover) diff --git a/builtin/update-index.c b/builtin/update-index.c index 62d9f3f0fa..5f038d64da 100644 --- a/builtin/update-index.c +++ b/builtin/update-index.c @@ -10,6 +10,7 @@ #include "builtin.h" #include "refs.h" #include "resolve-undo.h" +#include "parse-options.h" /* * Default to not allowing changes to the list of files. The @@ -98,8 +99,11 @@ static int add_one_path(struct cache_entry *old, const char *path, int len, stru fill_stat_cache_info(ce, st); ce->ce_mode = ce_mode_from_stat(old, st->st_mode); - if (index_path(ce->sha1, path, st, !info_only)) + if (index_path(ce->sha1, path, st, + info_only ? 0 : HASH_WRITE_OBJECT)) { + free(ce); return -1; + } option = allow_add ? ADD_CACHE_OK_TO_ADD : 0; option |= allow_replace ? ADD_CACHE_OK_TO_REPLACE : 0; if (add_cache_entry(ce, option)) @@ -397,8 +401,10 @@ static void read_index_info(int line_termination) strbuf_release(&uq); } -static const char update_index_usage[] = -"git update-index [-q] [--add] [--replace] [--remove] [--unmerged] [--refresh] [--really-refresh] [--cacheinfo] [--chmod=(+|-)x] [--assume-unchanged] [--skip-worktree|--no-skip-worktree] [--info-only] [--force-remove] [--stdin] [--index-info] [--unresolve] [--again | -g] [--ignore-missing] [-z] [--verbose] [--] [<file>...]"; +static const char * const update_index_usage[] = { + "git update-index [options] [--] [<file>...]", + NULL +}; static unsigned char head_sha1[20]; static unsigned char merge_head_sha1[20]; @@ -543,7 +549,10 @@ static int do_reupdate(int ac, const char **av, */ int pos; int has_head = 1; - const char **pathspec = get_pathspec(prefix, av + 1); + const char **paths = get_pathspec(prefix, av + 1); + struct pathspec pathspec; + + init_pathspec(&pathspec, paths); if (read_ref("HEAD", head_sha1)) /* If there is no HEAD, that means it is an initial @@ -556,7 +565,7 @@ static int do_reupdate(int ac, const char **av, struct cache_entry *old = NULL; int save_nr; - if (ce_stage(ce) || !ce_path_match(ce, pathspec)) + if (ce_stage(ce) || !ce_path_match(ce, &pathspec)) continue; if (has_head) old = read_one_ent(NULL, head_sha1, @@ -575,19 +584,221 @@ static int do_reupdate(int ac, const char **av, if (save_nr != active_nr) goto redo; } + free_pathspec(&pathspec); + return 0; +} + +struct refresh_params { + unsigned int flags; + int *has_errors; +}; + +static int refresh(struct refresh_params *o, unsigned int flag) +{ + setup_work_tree(); + *o->has_errors |= refresh_cache(o->flags | flag); + return 0; +} + +static int refresh_callback(const struct option *opt, + const char *arg, int unset) +{ + return refresh(opt->value, 0); +} + +static int really_refresh_callback(const struct option *opt, + const char *arg, int unset) +{ + return refresh(opt->value, REFRESH_REALLY); +} + +static int chmod_callback(const struct option *opt, + const char *arg, int unset) +{ + char *flip = opt->value; + if ((arg[0] != '-' && arg[0] != '+') || arg[1] != 'x' || arg[2]) + return error("option 'chmod' expects \"+x\" or \"-x\""); + *flip = arg[0]; + return 0; +} + +static int resolve_undo_clear_callback(const struct option *opt, + const char *arg, int unset) +{ + resolve_undo_clear(); + return 0; +} + +static int cacheinfo_callback(struct parse_opt_ctx_t *ctx, + const struct option *opt, int unset) +{ + unsigned char sha1[20]; + unsigned int mode; + + if (ctx->argc <= 3) + return error("option 'cacheinfo' expects three arguments"); + if (strtoul_ui(*++ctx->argv, 8, &mode) || + get_sha1_hex(*++ctx->argv, sha1) || + add_cacheinfo(mode, sha1, *++ctx->argv, 0)) + die("git update-index: --cacheinfo cannot add %s", *ctx->argv); + ctx->argc -= 3; + return 0; +} + +static int stdin_cacheinfo_callback(struct parse_opt_ctx_t *ctx, + const struct option *opt, int unset) +{ + int *line_termination = opt->value; + + if (ctx->argc != 1) + return error("option '%s' must be the last argument", opt->long_name); + allow_add = allow_replace = allow_remove = 1; + read_index_info(*line_termination); + return 0; +} + +static int stdin_callback(struct parse_opt_ctx_t *ctx, + const struct option *opt, int unset) +{ + int *read_from_stdin = opt->value; + + if (ctx->argc != 1) + return error("option '%s' must be the last argument", opt->long_name); + *read_from_stdin = 1; + return 0; +} + +static int unresolve_callback(struct parse_opt_ctx_t *ctx, + const struct option *opt, int flags) +{ + int *has_errors = opt->value; + const char *prefix = startup_info->prefix; + + /* consume remaining arguments. */ + *has_errors = do_unresolve(ctx->argc, ctx->argv, + prefix, prefix ? strlen(prefix) : 0); + if (*has_errors) + active_cache_changed = 0; + + ctx->argv += ctx->argc - 1; + ctx->argc = 1; + return 0; +} + +static int reupdate_callback(struct parse_opt_ctx_t *ctx, + const struct option *opt, int flags) +{ + int *has_errors = opt->value; + const char *prefix = startup_info->prefix; + + /* consume remaining arguments. */ + setup_work_tree(); + *has_errors = do_reupdate(ctx->argc, ctx->argv, + prefix, prefix ? strlen(prefix) : 0); + if (*has_errors) + active_cache_changed = 0; + + ctx->argv += ctx->argc - 1; + ctx->argc = 1; return 0; } int cmd_update_index(int argc, const char **argv, const char *prefix) { - int i, newfd, entries, has_errors = 0, line_termination = '\n'; - int allow_options = 1; + int newfd, entries, has_errors = 0, line_termination = '\n'; int read_from_stdin = 0; int prefix_length = prefix ? strlen(prefix) : 0; + int preferred_index_format = 0; char set_executable_bit = 0; - unsigned int refresh_flags = 0; + struct refresh_params refresh_args = {0, &has_errors}; int lock_error = 0; struct lock_file *lock_file; + struct parse_opt_ctx_t ctx; + int parseopt_state = PARSE_OPT_UNKNOWN; + struct option options[] = { + OPT_BIT('q', NULL, &refresh_args.flags, + "continue refresh even when index needs update", + REFRESH_QUIET), + OPT_BIT(0, "ignore-submodules", &refresh_args.flags, + "refresh: ignore submodules", + REFRESH_IGNORE_SUBMODULES), + OPT_SET_INT(0, "add", &allow_add, + "do not ignore new files", 1), + OPT_SET_INT(0, "replace", &allow_replace, + "let files replace directories and vice-versa", 1), + OPT_SET_INT(0, "remove", &allow_remove, + "notice files missing from worktree", 1), + OPT_BIT(0, "unmerged", &refresh_args.flags, + "refresh even if index contains unmerged entries", + REFRESH_UNMERGED), + {OPTION_CALLBACK, 0, "refresh", &refresh_args, NULL, + "refresh stat information", + PARSE_OPT_NOARG | PARSE_OPT_NONEG, + refresh_callback}, + {OPTION_CALLBACK, 0, "really-refresh", &refresh_args, NULL, + "like --refresh, but ignore assume-unchanged setting", + PARSE_OPT_NOARG | PARSE_OPT_NONEG, + really_refresh_callback}, + {OPTION_LOWLEVEL_CALLBACK, 0, "cacheinfo", NULL, + "<mode> <object> <path>", + "add the specified entry to the index", + PARSE_OPT_NOARG | /* disallow --cacheinfo=<mode> form */ + PARSE_OPT_NONEG | PARSE_OPT_LITERAL_ARGHELP, + (parse_opt_cb *) cacheinfo_callback}, + {OPTION_CALLBACK, 0, "chmod", &set_executable_bit, "(+/-)x", + "override the executable bit of the listed files", + PARSE_OPT_NONEG | PARSE_OPT_LITERAL_ARGHELP, + chmod_callback}, + {OPTION_SET_INT, 0, "assume-unchanged", &mark_valid_only, NULL, + "mark files as \"not changing\"", + PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, MARK_FLAG}, + {OPTION_SET_INT, 0, "no-assume-unchanged", &mark_valid_only, NULL, + "clear assumed-unchanged bit", + PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, UNMARK_FLAG}, + {OPTION_SET_INT, 0, "skip-worktree", &mark_skip_worktree_only, NULL, + "mark files as \"index-only\"", + PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, MARK_FLAG}, + {OPTION_SET_INT, 0, "no-skip-worktree", &mark_skip_worktree_only, NULL, + "clear skip-worktree bit", + PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, UNMARK_FLAG}, + OPT_SET_INT(0, "info-only", &info_only, + "add to index only; do not add content to object database", 1), + OPT_SET_INT(0, "force-remove", &force_remove, + "remove named paths even if present in worktree", 1), + OPT_SET_INT('z', NULL, &line_termination, + "with --stdin: input lines are terminated by null bytes", '\0'), + {OPTION_LOWLEVEL_CALLBACK, 0, "stdin", &read_from_stdin, NULL, + "read list of paths to be updated from standard input", + PARSE_OPT_NONEG | PARSE_OPT_NOARG, + (parse_opt_cb *) stdin_callback}, + {OPTION_LOWLEVEL_CALLBACK, 0, "index-info", &line_termination, NULL, + "add entries from standard input to the index", + PARSE_OPT_NONEG | PARSE_OPT_NOARG, + (parse_opt_cb *) stdin_cacheinfo_callback}, + {OPTION_LOWLEVEL_CALLBACK, 0, "unresolve", &has_errors, NULL, + "repopulate stages #2 and #3 for the listed paths", + PARSE_OPT_NONEG | PARSE_OPT_NOARG, + (parse_opt_cb *) unresolve_callback}, + {OPTION_LOWLEVEL_CALLBACK, 'g', "again", &has_errors, NULL, + "only update entries that differ from HEAD", + PARSE_OPT_NONEG | PARSE_OPT_NOARG, + (parse_opt_cb *) reupdate_callback}, + OPT_BIT(0, "ignore-missing", &refresh_args.flags, + "ignore files missing from worktree", + REFRESH_IGNORE_MISSING), + OPT_SET_INT(0, "verbose", &verbose, + "report actions to standard output", 1), + {OPTION_CALLBACK, 0, "clear-resolve-undo", NULL, NULL, + "(for porcelains) forget saved unresolved conflicts", + PARSE_OPT_NOARG | PARSE_OPT_NONEG, + resolve_undo_clear_callback}, + OPT_INTEGER(0, "index-version", &preferred_index_format, + "write index in this format"), + OPT_END() + }; + + if (argc == 2 && !strcmp(argv[1], "-h")) + usage(update_index_usage[0]); git_config(git_default_config, NULL); @@ -602,151 +813,59 @@ int cmd_update_index(int argc, const char **argv, const char *prefix) if (entries < 0) die("cache corrupted"); - for (i = 1 ; i < argc; i++) { - const char *path = argv[i]; - const char *p; + /* + * Custom copy of parse_options() because we want to handle + * filename arguments as they come. + */ + parse_options_start(&ctx, argc, argv, prefix, + options, PARSE_OPT_STOP_AT_NON_OPTION); + while (ctx.argc) { + if (parseopt_state != PARSE_OPT_DONE) + parseopt_state = parse_options_step(&ctx, options, + update_index_usage); + if (!ctx.argc) + break; + switch (parseopt_state) { + case PARSE_OPT_HELP: + exit(129); + case PARSE_OPT_NON_OPTION: + case PARSE_OPT_DONE: + { + const char *path = ctx.argv[0]; + const char *p; - if (allow_options && *path == '-') { - if (!strcmp(path, "--")) { - allow_options = 0; - continue; - } - if (!strcmp(path, "-q")) { - refresh_flags |= REFRESH_QUIET; - continue; - } - if (!strcmp(path, "--ignore-submodules")) { - refresh_flags |= REFRESH_IGNORE_SUBMODULES; - continue; - } - if (!strcmp(path, "--add")) { - allow_add = 1; - continue; - } - if (!strcmp(path, "--replace")) { - allow_replace = 1; - continue; - } - if (!strcmp(path, "--remove")) { - allow_remove = 1; - continue; - } - if (!strcmp(path, "--unmerged")) { - refresh_flags |= REFRESH_UNMERGED; - continue; - } - if (!strcmp(path, "--refresh")) { - setup_work_tree(); - has_errors |= refresh_cache(refresh_flags); - continue; - } - if (!strcmp(path, "--really-refresh")) { - setup_work_tree(); - has_errors |= refresh_cache(REFRESH_REALLY | refresh_flags); - continue; - } - if (!strcmp(path, "--cacheinfo")) { - unsigned char sha1[20]; - unsigned int mode; - - if (i+3 >= argc) - die("git update-index: --cacheinfo <mode> <sha1> <path>"); - - if (strtoul_ui(argv[i+1], 8, &mode) || - get_sha1_hex(argv[i+2], sha1) || - add_cacheinfo(mode, sha1, argv[i+3], 0)) - die("git update-index: --cacheinfo" - " cannot add %s", argv[i+3]); - i += 3; - continue; - } - if (!strcmp(path, "--chmod=-x") || - !strcmp(path, "--chmod=+x")) { - if (argc <= i+1) - die("git update-index: %s <path>", path); - set_executable_bit = path[8]; - continue; - } - if (!strcmp(path, "--assume-unchanged")) { - mark_valid_only = MARK_FLAG; - continue; - } - if (!strcmp(path, "--no-assume-unchanged")) { - mark_valid_only = UNMARK_FLAG; - continue; - } - if (!strcmp(path, "--no-skip-worktree")) { - mark_skip_worktree_only = UNMARK_FLAG; - continue; - } - if (!strcmp(path, "--skip-worktree")) { - mark_skip_worktree_only = MARK_FLAG; - continue; - } - if (!strcmp(path, "--info-only")) { - info_only = 1; - continue; - } - if (!strcmp(path, "--force-remove")) { - force_remove = 1; - continue; - } - if (!strcmp(path, "-z")) { - line_termination = 0; - continue; - } - if (!strcmp(path, "--stdin")) { - if (i != argc - 1) - die("--stdin must be at the end"); - read_from_stdin = 1; - break; - } - if (!strcmp(path, "--index-info")) { - if (i != argc - 1) - die("--index-info must be at the end"); - allow_add = allow_replace = allow_remove = 1; - read_index_info(line_termination); - break; - } - if (!strcmp(path, "--unresolve")) { - has_errors = do_unresolve(argc - i, argv + i, - prefix, prefix_length); - if (has_errors) - active_cache_changed = 0; - goto finish; - } - if (!strcmp(path, "--again") || !strcmp(path, "-g")) { - setup_work_tree(); - has_errors = do_reupdate(argc - i, argv + i, - prefix, prefix_length); - if (has_errors) - active_cache_changed = 0; - goto finish; - } - if (!strcmp(path, "--ignore-missing")) { - refresh_flags |= REFRESH_IGNORE_MISSING; - continue; - } - if (!strcmp(path, "--verbose")) { - verbose = 1; - continue; - } - if (!strcmp(path, "--clear-resolve-undo")) { - resolve_undo_clear(); - continue; - } - if (!strcmp(path, "-h") || !strcmp(path, "--help")) - usage(update_index_usage); - die("unknown option %s", path); + setup_work_tree(); + p = prefix_path(prefix, prefix_length, path); + update_one(p, NULL, 0); + if (set_executable_bit) + chmod_path(set_executable_bit, p); + if (p < path || p > path + strlen(path)) + free((char *)p); + ctx.argc--; + ctx.argv++; + break; + } + case PARSE_OPT_UNKNOWN: + if (ctx.argv[0][1] == '-') + error("unknown option '%s'", ctx.argv[0] + 2); + else + error("unknown switch '%c'", *ctx.opt); + usage_with_options(update_index_usage, options); } - setup_work_tree(); - p = prefix_path(prefix, prefix_length, path); - update_one(p, NULL, 0); - if (set_executable_bit) - chmod_path(set_executable_bit, p); - if (p < path || p > path + strlen(path)) - free((char *)p); } + argc = parse_options_end(&ctx); + if (preferred_index_format) { + if (preferred_index_format < INDEX_FORMAT_LB || + INDEX_FORMAT_UB < preferred_index_format) + die("index-version %d not in range: %d..%d", + preferred_index_format, + INDEX_FORMAT_LB, INDEX_FORMAT_UB); + + if (the_index.version != preferred_index_format) + active_cache_changed = 1; + the_index.version = preferred_index_format; + } + if (read_from_stdin) { struct strbuf buf = STRBUF_INIT, nbuf = STRBUF_INIT; @@ -770,10 +889,9 @@ int cmd_update_index(int argc, const char **argv, const char *prefix) strbuf_release(&buf); } - finish: if (active_cache_changed) { if (newfd < 0) { - if (refresh_flags & REFRESH_QUIET) + if (refresh_args.flags & REFRESH_QUIET) exit(128); unable_to_lock_index_die(get_index_file(), lock_error); } diff --git a/builtin/update-ref.c b/builtin/update-ref.c index 76ba1d5881..835c62ab15 100644 --- a/builtin/update-ref.c +++ b/builtin/update-ref.c @@ -11,7 +11,7 @@ static const char * const git_update_ref_usage[] = { int cmd_update_ref(int argc, const char **argv, const char *prefix) { - const char *refname, *oldval, *msg=NULL; + const char *refname, *oldval, *msg = NULL; unsigned char sha1[20], oldsha1[20]; int delete = 0, no_deref = 0, flags = 0; struct option options[] = { diff --git a/builtin/update-server-info.c b/builtin/update-server-info.c index 2b3fddcc69..0d63c4498c 100644 --- a/builtin/update-server-info.c +++ b/builtin/update-server-info.c @@ -11,11 +11,11 @@ int cmd_update_server_info(int argc, const char **argv, const char *prefix) { int force = 0; struct option options[] = { - OPT_BOOLEAN('f', "force", &force, - "update the info files from scratch"), + OPT__FORCE(&force, "update the info files from scratch"), OPT_END() }; + git_config(git_default_config, NULL); argc = parse_options(argc, argv, prefix, options, update_server_info_usage, 0); if (argc > 0) diff --git a/builtin/upload-archive.c b/builtin/upload-archive.c index 73f788ef24..b928beb8ed 100644 --- a/builtin/upload-archive.c +++ b/builtin/upload-archive.c @@ -6,6 +6,7 @@ #include "archive.h" #include "pkt-line.h" #include "sideband.h" +#include "run-command.h" static const char upload_archive_usage[] = "git upload-archive <repo>"; @@ -13,12 +14,9 @@ static const char upload_archive_usage[] = static const char deadchild[] = "git upload-archive: archiver died with error"; -static const char lostchild[] = -"git upload-archive: archiver process was lost"; - #define MAX_ARGS (64) -static int run_upload_archive(int argc, const char **argv, const char *prefix) +int cmd_upload_archive_writer(int argc, const char **argv, const char *prefix) { const char *sent_argv[MAX_ARGS]; const char *arg_cmd = "argument "; @@ -64,7 +62,7 @@ static int run_upload_archive(int argc, const char **argv, const char *prefix) sent_argv[sent_argc] = NULL; /* parse all options sent by the client */ - return write_archive(sent_argc, sent_argv, prefix, 0); + return write_archive(sent_argc, sent_argv, prefix, 0, NULL, 1); } __attribute__((format (printf, 1, 2))) @@ -96,8 +94,8 @@ static ssize_t process_input(int child_fd, int band) int cmd_upload_archive(int argc, const char **argv, const char *prefix) { - pid_t writer; - int fd1[2], fd2[2]; + struct child_process writer = { argv }; + /* * Set up sideband subprocess. * @@ -105,39 +103,24 @@ int cmd_upload_archive(int argc, const char **argv, const char *prefix) * multiplexed out to our fd#1. If the child dies, we tell the other * end over channel #3. */ - if (pipe(fd1) < 0 || pipe(fd2) < 0) { - int err = errno; - packet_write(1, "NACK pipe failed on the remote side\n"); - die("upload-archive: %s", strerror(err)); - } - writer = fork(); - if (writer < 0) { + argv[0] = "upload-archive--writer"; + writer.out = writer.err = -1; + writer.git_cmd = 1; + if (start_command(&writer)) { int err = errno; - packet_write(1, "NACK fork failed on the remote side\n"); + packet_write(1, "NACK unable to spawn subprocess\n"); die("upload-archive: %s", strerror(err)); } - if (!writer) { - /* child - connect fd#1 and fd#2 to the pipe */ - dup2(fd1[1], 1); - dup2(fd2[1], 2); - close(fd1[1]); close(fd2[1]); - close(fd1[0]); close(fd2[0]); /* we do not read from pipe */ - - exit(run_upload_archive(argc, argv, prefix)); - } - /* parent - read from child, multiplex and send out to fd#1 */ - close(fd1[1]); close(fd2[1]); /* we do not write to pipe */ packet_write(1, "ACK\n"); packet_flush(1); while (1) { struct pollfd pfd[2]; - int status; - pfd[0].fd = fd1[0]; + pfd[0].fd = writer.out; pfd[0].events = POLLIN; - pfd[1].fd = fd2[0]; + pfd[1].fd = writer.err; pfd[1].events = POLLIN; if (poll(pfd, 2, -1) < 0) { if (errno != EINTR) { @@ -156,9 +139,7 @@ int cmd_upload_archive(int argc, const char **argv, const char *prefix) if (process_input(pfd[0].fd, 1)) continue; - if (waitpid(writer, &status, 0) < 0) - error_clnt("%s", lostchild); - else if (!WIFEXITED(status) || WEXITSTATUS(status) > 0) + if (finish_command(&writer)) error_clnt("%s", deadchild); packet_flush(1); break; diff --git a/builtin/var.c b/builtin/var.c index 0744bb8318..aedbb53a2d 100644 --- a/builtin/var.c +++ b/builtin/var.c @@ -3,8 +3,7 @@ * * Copyright (C) Eric Biederman, 2005 */ -#include "cache.h" -#include "exec_cmd.h" +#include "builtin.h" static const char var_usage[] = "git var (-l | <variable>)"; @@ -12,7 +11,7 @@ static const char *editor(int flag) { const char *pgm = git_editor(); - if (!pgm && flag & IDENT_ERROR_ON_NO_NAME) + if (!pgm && flag & IDENT_STRICT) die("Terminal is dumb, but EDITOR unset"); return pgm; @@ -56,7 +55,7 @@ static const char *read_var(const char *var) val = NULL; for (ptr = git_vars; ptr->read; ptr++) { if (strcmp(var, ptr->name) == 0) { - val = ptr->read(IDENT_ERROR_ON_NO_NAME); + val = ptr->read(IDENT_STRICT); break; } } diff --git a/builtin/verify-pack.c b/builtin/verify-pack.c index b6079ae6cb..e841b4a38d 100644 --- a/builtin/verify-pack.c +++ b/builtin/verify-pack.c @@ -1,134 +1,53 @@ #include "builtin.h" #include "cache.h" -#include "pack.h" -#include "pack-revindex.h" +#include "run-command.h" #include "parse-options.h" -#define MAX_CHAIN 50 - #define VERIFY_PACK_VERBOSE 01 #define VERIFY_PACK_STAT_ONLY 02 -static void show_pack_info(struct packed_git *p, unsigned int flags) -{ - uint32_t nr_objects, i; - int cnt; - int stat_only = flags & VERIFY_PACK_STAT_ONLY; - unsigned long chain_histogram[MAX_CHAIN+1], baseobjects; - - nr_objects = p->num_objects; - memset(chain_histogram, 0, sizeof(chain_histogram)); - baseobjects = 0; - - for (i = 0; i < nr_objects; i++) { - const unsigned char *sha1; - unsigned char base_sha1[20]; - const char *type; - unsigned long size; - unsigned long store_size; - off_t offset; - unsigned int delta_chain_length; - - sha1 = nth_packed_object_sha1(p, i); - if (!sha1) - die("internal error pack-check nth-packed-object"); - offset = nth_packed_object_offset(p, i); - type = packed_object_info_detail(p, offset, &size, &store_size, - &delta_chain_length, - base_sha1); - if (!stat_only) - printf("%s ", sha1_to_hex(sha1)); - if (!delta_chain_length) { - if (!stat_only) - printf("%-6s %lu %lu %"PRIuMAX"\n", - type, size, store_size, (uintmax_t)offset); - baseobjects++; - } - else { - if (!stat_only) - printf("%-6s %lu %lu %"PRIuMAX" %u %s\n", - type, size, store_size, (uintmax_t)offset, - delta_chain_length, sha1_to_hex(base_sha1)); - if (delta_chain_length <= MAX_CHAIN) - chain_histogram[delta_chain_length]++; - else - chain_histogram[0]++; - } - } - - if (baseobjects) - printf("non delta: %lu object%s\n", - baseobjects, baseobjects > 1 ? "s" : ""); - - for (cnt = 1; cnt <= MAX_CHAIN; cnt++) { - if (!chain_histogram[cnt]) - continue; - printf("chain length = %d: %lu object%s\n", cnt, - chain_histogram[cnt], - chain_histogram[cnt] > 1 ? "s" : ""); - } - if (chain_histogram[0]) - printf("chain length > %d: %lu object%s\n", MAX_CHAIN, - chain_histogram[0], - chain_histogram[0] > 1 ? "s" : ""); -} - static int verify_one_pack(const char *path, unsigned int flags) { - char arg[PATH_MAX]; - int len; + struct child_process index_pack; + const char *argv[] = {"index-pack", NULL, NULL, NULL }; + struct strbuf arg = STRBUF_INIT; int verbose = flags & VERIFY_PACK_VERBOSE; int stat_only = flags & VERIFY_PACK_STAT_ONLY; - struct packed_git *pack; int err; - len = strlcpy(arg, path, PATH_MAX); - if (len >= PATH_MAX) - return error("name too long: %s", path); - - /* - * In addition to "foo.idx" we accept "foo.pack" and "foo"; - * normalize these forms to "foo.idx" for add_packed_git(). - */ - if (has_extension(arg, ".pack")) { - strcpy(arg + len - 5, ".idx"); - len--; - } else if (!has_extension(arg, ".idx")) { - if (len + 4 >= PATH_MAX) - return error("name too long: %s.idx", arg); - strcpy(arg + len, ".idx"); - len += 4; - } + if (stat_only) + argv[1] = "--verify-stat-only"; + else if (verbose) + argv[1] = "--verify-stat"; + else + argv[1] = "--verify"; /* - * add_packed_git() uses our buffer (containing "foo.idx") to - * build the pack filename ("foo.pack"). Make sure it fits. + * In addition to "foo.pack" we accept "foo.idx" and "foo"; + * normalize these forms to "foo.pack" for "index-pack --verify". */ - if (len + 1 >= PATH_MAX) { - arg[len - 4] = '\0'; - return error("name too long: %s.pack", arg); - } - - pack = add_packed_git(arg, len, 1); - if (!pack) - return error("packfile %s not found.", arg); + strbuf_addstr(&arg, path); + if (has_extension(arg.buf, ".idx")) + strbuf_splice(&arg, arg.len - 3, 3, "pack", 4); + else if (!has_extension(arg.buf, ".pack")) + strbuf_add(&arg, ".pack", 5); + argv[2] = arg.buf; - install_packed_git(pack); + memset(&index_pack, 0, sizeof(index_pack)); + index_pack.argv = argv; + index_pack.git_cmd = 1; - if (!stat_only) - err = verify_pack(pack); - else - err = open_pack_index(pack); + err = run_command(&index_pack); if (verbose || stat_only) { if (err) - printf("%s: bad\n", pack->pack_name); + printf("%s: bad\n", arg.buf); else { - show_pack_info(pack, flags); if (!stat_only) - printf("%s: ok\n", pack->pack_name); + printf("%s: ok\n", arg.buf); } } + strbuf_release(&arg); return err; } @@ -159,7 +78,6 @@ int cmd_verify_pack(int argc, const char **argv, const char *prefix) for (i = 0; i < argc; i++) { if (verify_one_pack(argv[i], flags)) err = 1; - discard_revindex(); } return err; diff --git a/builtin/verify-tag.c b/builtin/verify-tag.c index 9f482c29f5..986789f706 100644 --- a/builtin/verify-tag.c +++ b/builtin/verify-tag.c @@ -11,56 +11,25 @@ #include "run-command.h" #include <signal.h> #include "parse-options.h" +#include "gpg-interface.h" static const char * const verify_tag_usage[] = { "git verify-tag [-v|--verbose] <tag>...", NULL }; -#define PGP_SIGNATURE "-----BEGIN PGP SIGNATURE-----" - static int run_gpg_verify(const char *buf, unsigned long size, int verbose) { - struct child_process gpg; - const char *args_gpg[] = {"gpg", "--verify", "FILE", "-", NULL}; - char path[PATH_MAX], *eol; - size_t len; - int fd, ret; - - fd = git_mkstemp(path, PATH_MAX, ".git_vtag_tmpXXXXXX"); - if (fd < 0) - return error("could not create temporary file '%s': %s", - path, strerror(errno)); - if (write_in_full(fd, buf, size) < 0) - return error("failed writing temporary file '%s': %s", - path, strerror(errno)); - close(fd); - - /* find the length without signature */ - len = 0; - while (len < size && prefixcmp(buf + len, PGP_SIGNATURE)) { - eol = memchr(buf + len, '\n', size - len); - len += eol ? eol - (buf + len) + 1 : size - len; - } + int len; + + len = parse_signature(buf, size); if (verbose) write_in_full(1, buf, len); - memset(&gpg, 0, sizeof(gpg)); - gpg.argv = args_gpg; - gpg.in = -1; - args_gpg[2] = path; - if (start_command(&gpg)) { - unlink(path); - return error("could not run gpg."); - } - - write_in_full(gpg.in, buf, len); - close(gpg.in); - ret = finish_command(&gpg); - - unlink_or_warn(path); + if (size == len) + return error("no signature found"); - return ret; + return verify_signed_buffer(buf, len, buf + len, size - len, NULL); } static int verify_tag(const char *name, int verbose) @@ -89,15 +58,23 @@ static int verify_tag(const char *name, int verbose) return ret; } +static int git_verify_tag_config(const char *var, const char *value, void *cb) +{ + int status = git_gpg_config(var, value, cb); + if (status) + return status; + return git_default_config(var, value, cb); +} + int cmd_verify_tag(int argc, const char **argv, const char *prefix) { int i = 1, verbose = 0, had_error = 0; const struct option verify_tag_options[] = { - OPT__VERBOSE(&verbose), + OPT__VERBOSE(&verbose, "print tag contents"), OPT_END() }; - git_config(git_default_config, NULL); + git_config(git_verify_tag_config, NULL); argc = parse_options(argc, argv, prefix, verify_tag_options, verify_tag_usage, PARSE_OPT_KEEP_ARGV0); |