diff options
Diffstat (limited to 'builtin')
83 files changed, 8081 insertions, 6537 deletions
diff --git a/builtin/add.c b/builtin/add.c index c59b0c98fe..632594768d 100644 --- a/builtin/add.c +++ b/builtin/add.c @@ -13,9 +13,10 @@ #include "diff.h" #include "diffcore.h" #include "revision.h" +#include "bulk-checkin.h" static const char * const builtin_add_usage[] = { - "git add [options] [--] <filepattern>...", + N_("git add [options] [--] <pathspec>..."), NULL }; static int patch_interactive, add_interactive, edit_interactive; @@ -259,7 +260,7 @@ int interactive_add(int argc, const char **argv, const char *prefix, int patch) static int edit_patch(int argc, const char **argv, const char *prefix) { - char *file = xstrdup(git_path("ADD_EDIT.patch")); + char *file = git_pathdup("ADD_EDIT.patch"); const char *apply_argv[] = { "apply", "--recount", "--cached", NULL, NULL }; struct child_process child; @@ -279,7 +280,8 @@ static int edit_patch(int argc, const char **argv, const char *prefix) argc = setup_revisions(argc, argv, &rev, NULL); rev.diffopt.output_format = DIFF_FORMAT_PATCH; - out = open(file, O_CREAT | O_WRONLY, 0644); + DIFF_OPT_SET(&rev.diffopt, IGNORE_DIRTY_SUBMODULES); + out = open(file, O_CREAT | O_WRONLY, 0666); if (out < 0) die (_("Could not open '%s' for writing."), file); rev.diffopt.file = xfdopen(out, "w"); @@ -301,6 +303,7 @@ static int edit_patch(int argc, const char **argv, const char *prefix) die (_("Could not apply '%s'"), file); unlink(file); + free(file); return 0; } @@ -313,19 +316,19 @@ 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, "dry run"), - OPT__VERBOSE(&verbose, "be verbose"), + OPT__DRY_RUN(&show_only, N_("dry run")), + OPT__VERBOSE(&verbose, N_("be verbose")), OPT_GROUP(""), - OPT_BOOLEAN('i', "interactive", &add_interactive, "interactive picking"), - OPT_BOOLEAN('p', "patch", &patch_interactive, "select hunks interactively"), - OPT_BOOLEAN('e', "edit", &edit_interactive, "edit current diff and apply"), - 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 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"), + OPT_BOOLEAN('i', "interactive", &add_interactive, N_("interactive picking")), + OPT_BOOLEAN('p', "patch", &patch_interactive, N_("select hunks interactively")), + OPT_BOOLEAN('e', "edit", &edit_interactive, N_("edit current diff and apply")), + OPT__FORCE(&ignored_too, N_("allow adding otherwise ignored files")), + OPT_BOOLEAN('u', "update", &take_worktree_changes, N_("update tracked files")), + OPT_BOOLEAN('N', "intent-to-add", &intent_to_add, N_("record only the fact that the path will be added later")), + OPT_BOOLEAN('A', "all", &addremove, N_("add changes from all tracked and untracked files")), + OPT_BOOLEAN( 0 , "refresh", &refresh_only, N_("don't add, only refresh the index")), + OPT_BOOLEAN( 0 , "ignore-errors", &ignore_add_errors, N_("just skip files which cannot be added because of errors")), + OPT_BOOLEAN( 0 , "ignore-missing", &ignore_missing, N_("check if - even missing - files are ignored in dry run")), OPT_END(), }; @@ -441,6 +444,9 @@ 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++) { @@ -448,7 +454,7 @@ int cmd_add(int argc, const char **argv, const char *prefix) && !file_exists(pathspec[i])) { if (ignore_missing) { int dtype = DT_UNKNOWN; - if (excluded(&dir, pathspec[i], &dtype)) + 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"), @@ -456,13 +462,18 @@ int cmd_add(int argc, const char **argv, const char *prefix) } } 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) || diff --git a/builtin/apply.c b/builtin/apply.c index f2edc52818..080ce2ea3e 100644 --- a/builtin/apply.c +++ b/builtin/apply.c @@ -14,7 +14,11 @@ #include "builtin.h" #include "string-list.h" #include "dir.h" +#include "diff.h" #include "parse-options.h" +#include "xdiff-interface.h" +#include "ll-merge.h" +#include "rerere.h" /* * --check turns on checking that the working tree matches the @@ -45,11 +49,12 @@ static int apply_with_reject; static int apply_verbosely; static int allow_overlap; static int no_add; +static int threeway; 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 }; @@ -102,7 +107,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) @@ -117,7 +122,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) @@ -151,9 +156,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; }; @@ -178,7 +188,6 @@ struct patch { int is_new, is_delete; /* -1 = unknown, 0 = false, 1 = true */ int rejected; unsigned ws_rule; - unsigned long deflate_origlen; int lines_added, lines_deleted; int score; unsigned int is_toplevel_relative:1; @@ -187,14 +196,49 @@ struct patch { unsigned int is_copy:1; unsigned int is_rename:1; unsigned int recount:1; + unsigned int conflicted_threeway:1; + unsigned int direct_to_threeway:1; struct fragment *fragments; char *result; size_t resultsize; char old_sha1_prefix[41]; char new_sha1_prefix[41]; struct patch *next; + + /* three-way fallback result */ + unsigned char threeway_stage[3][20]; }; +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 @@ -250,9 +294,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--; @@ -304,6 +345,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) { @@ -333,29 +379,31 @@ static void prepare_image(struct image *image, char *buf, size_t len, static void clear_image(struct image *image) { free(image->buf); - image->buf = NULL; - image->len = 0; + free(image->line_allocated); + memset(image, 0, sizeof(*image)); } -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) @@ -418,7 +466,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; @@ -441,11 +489,7 @@ 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)); @@ -610,8 +654,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; @@ -632,10 +681,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 @@ -646,8 +695,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) { @@ -772,7 +820,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; } @@ -781,7 +829,7 @@ 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; } @@ -844,8 +892,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; @@ -855,11 +905,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) @@ -876,7 +927,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); @@ -888,30 +942,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; } @@ -930,20 +994,23 @@ 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; + free(patch->old_name); patch->old_name = find_name(line, NULL, p_value ? p_value - 1 : 0, 0); return 0; } @@ -951,6 +1018,7 @@ static int gitdiff_copysrc(const char *line, struct patch *patch) static int gitdiff_copydst(const char *line, struct patch *patch) { patch->is_copy = 1; + free(patch->new_name); patch->new_name = find_name(line, NULL, p_value ? p_value - 1 : 0, 0); return 0; } @@ -958,6 +1026,7 @@ static int gitdiff_copydst(const char *line, struct patch *patch) static int gitdiff_renamesrc(const char *line, struct patch *patch) { patch->is_rename = 1; + free(patch->old_name); patch->old_name = find_name(line, NULL, p_value ? p_value - 1 : 0, 0); return 0; } @@ -965,21 +1034,24 @@ static int gitdiff_renamesrc(const char *line, struct patch *patch) static int gitdiff_renamedst(const char *line, struct patch *patch) { patch->is_rename = 1; + free(patch->new_name); patch->new_name = find_name(line, NULL, p_value ? p_value - 1 : 0, 0); return 0; } static int gitdiff_similarity(const char *line, struct patch *patch) { - if ((patch->score = strtoul(line, NULL, 10)) == ULONG_MAX) - patch->score = 0; + unsigned long val = strtoul(line, NULL, 10); + if (val <= 100) + patch->score = val; return 0; } static int gitdiff_dissimilarity(const char *line, struct patch *patch) { - if ((patch->score = strtoul(line, NULL, 10)) == ULONG_MAX) - patch->score = 0; + unsigned long val = strtoul(line, NULL, 10); + if (val <= 100) + patch->score = val; return 0; } @@ -1025,15 +1097,23 @@ static int gitdiff_unrecognized(const char *line, struct patch *patch) return -1; } -static const char *stop_at_slash(const char *line, int llen) +/* + * Skip p_value leading components from "line"; as we do not accept + * absolute paths, return NULL in that case. + */ +static const char *skip_tree_prefix(const char *line, int llen) { - int nslash = p_value; + int nslash; int i; + if (!p_value) + return (llen && line[0] == '/') ? NULL : line; + + nslash = p_value; for (i = 0; i < llen; i++) { int ch = line[i]; if (ch == '/' && --nslash <= 0) - return &line[i]; + return (i == 0) ? NULL : &line[i + 1]; } return NULL; } @@ -1046,7 +1126,7 @@ 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; @@ -1063,12 +1143,11 @@ static char *git_header_name(char *line, int llen) if (unquote_c_style(&first, line, &second)) goto free_and_fail1; - /* advance to the first slash */ - cp = stop_at_slash(first.buf, first.len); - /* we do not accept absolute paths */ - if (!cp || cp == first.buf) + /* strip the a/b prefix including trailing slash */ + cp = skip_tree_prefix(first.buf, first.len); + if (!cp) goto free_and_fail1; - strbuf_remove(&first, 0, cp + 1 - first.buf); + strbuf_remove(&first, 0, cp - first.buf); /* * second points at one past closing dq of name. @@ -1082,22 +1161,21 @@ static char *git_header_name(char *line, int llen) if (*second == '"') { if (unquote_c_style(&sp, second, NULL)) goto free_and_fail1; - cp = stop_at_slash(sp.buf, sp.len); - if (!cp || cp == sp.buf) + cp = skip_tree_prefix(sp.buf, sp.len); + if (!cp) goto free_and_fail1; /* They must match, otherwise ignore */ - if (strcmp(cp + 1, first.buf)) + if (strcmp(cp, first.buf)) goto free_and_fail1; strbuf_release(&sp); return strbuf_detach(&first, NULL); } /* unquoted second */ - cp = stop_at_slash(second, line + llen - second); - if (!cp || cp == second) + cp = skip_tree_prefix(second, line + llen - second); + if (!cp) goto free_and_fail1; - cp++; - if (line + llen - cp != first.len + 1 || + if (line + llen - cp != first.len || memcmp(first.buf, cp, first.len)) goto free_and_fail1; return strbuf_detach(&first, NULL); @@ -1109,10 +1187,9 @@ static char *git_header_name(char *line, int llen) } /* unquoted first name */ - name = stop_at_slash(line, llen); - if (!name || name == line) + name = skip_tree_prefix(line, llen); + if (!name) return NULL; - name++; /* * since the first name is unquoted, a dq if exists must be @@ -1126,10 +1203,9 @@ static char *git_header_name(char *line, int llen) if (unquote_c_style(&sp, second, NULL)) goto free_and_fail2; - np = stop_at_slash(sp.buf, sp.len); - if (!np || np == sp.buf) + np = skip_tree_prefix(sp.buf, sp.len); + if (!np) goto free_and_fail2; - np++; len = sp.buf + sp.len - np; if (len < second - name && @@ -1161,19 +1237,33 @@ static char *git_header_name(char *line, int llen) case '\n': return NULL; case '\t': case ' ': - second = stop_at_slash(name + len, line_len - len); + /* + * Is this the separator between the preimage + * and the postimage pathname? Again, we are + * only interested in the case where there is + * no rename, as this is only to set def_name + * and a rename patch has the names elsewhere + * in an unambiguous form. + */ + if (!name[len + 1]) + return NULL; /* no postimage name */ + second = skip_tree_prefix(name + len + 1, + line_len - (len + 1)); if (!second) return NULL; - second++; - if (second[len] == '\n' && !strncmp(name, second, len)) { + /* + * Does len bytes starting at "name" and "second" + * (that are separated by one HT or SP we just + * found) exactly match? + */ + if (second[len] == '\n' && !strncmp(name, second, len)) return xmemdupz(name, len); - } } } } /* Verify that we recognize the lines following a git header */ -static int parse_git_header(char *line, int len, unsigned int size, struct patch *patch) +static int parse_git_header(const char *line, int len, unsigned int size, struct patch *patch) { unsigned long offset; @@ -1289,7 +1379,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; @@ -1329,7 +1419,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; } @@ -1343,7 +1433,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; @@ -1357,7 +1447,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; @@ -1386,7 +1476,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); } @@ -1403,10 +1493,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; @@ -1465,7 +1563,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; @@ -1555,13 +1653,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; @@ -1575,7 +1681,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; @@ -1611,12 +1717,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; } @@ -1654,6 +1762,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, @@ -1741,6 +1854,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); @@ -1754,7 +1868,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; } @@ -1783,7 +1897,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; @@ -1806,6 +1920,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; @@ -1862,7 +1983,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; @@ -1952,11 +2073,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: @@ -1976,7 +2097,7 @@ static void update_pre_post_images(struct image *preimage, char *buf, size_t len, size_t postlen) { - int i, ctx; + int i, ctx, reduced; char *new, *old, *fixed; struct image fixed_preimage; @@ -1986,8 +2107,10 @@ static void update_pre_post_images(struct image *preimage, * free "oldlines". */ prepare_image(&fixed_preimage, buf, len, 1); - assert(fixed_preimage.nr == preimage->nr); - for (i = 0; i < preimage->nr; i++) + assert(postlen + ? fixed_preimage.nr == preimage->nr + : fixed_preimage.nr <= preimage->nr); + for (i = 0; i < fixed_preimage.nr; i++) fixed_preimage.line[i].flag = preimage->line[i].flag; free(preimage->line_allocated); *preimage = fixed_preimage; @@ -2007,7 +2130,8 @@ static void update_pre_post_images(struct image *preimage, else new = old; fixed = preimage->buf; - for (i = ctx = 0; i < postimage->nr; i++) { + + for (i = reduced = ctx = 0; i < postimage->nr; i++) { size_t len = postimage->line[i].len; if (!(postimage->line[i].flag & LINE_COMMON)) { /* an added line -- no counterparts in preimage */ @@ -2026,8 +2150,15 @@ static void update_pre_post_images(struct image *preimage, fixed += preimage->line[ctx].len; ctx++; } - if (preimage->nr <= ctx) - die("oops"); + + /* + * preimage is expected to run out, if the caller + * fixed addition of trailing blank lines. + */ + if (preimage->nr <= ctx) { + reduced++; + continue; + } /* and copy it in, while fixing the line length */ len = preimage->line[ctx].len; @@ -2040,6 +2171,7 @@ static void update_pre_post_images(struct image *preimage, /* Fix the length of the whole thing */ postimage->len = new - postimage->buf; + postimage->nr -= reduced; } static int match_fragment(struct image *img, @@ -2366,6 +2498,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, @@ -2437,6 +2574,11 @@ static void update_image(struct image *img, 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 nth_fragment) @@ -2447,6 +2589,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; @@ -2537,17 +2681,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' && @@ -2629,7 +2777,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); @@ -2649,9 +2798,11 @@ static int apply_one_fragment(struct image *img, struct fragment *frag, int offset = applied_pos - pos; if (apply_in_reverse) offset = 0 - offset; - fprintf(stderr, - "Hunk #%d succeeded at %d (offset %d lines).\n", - nth_fragment, applied_pos + 1, 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); } /* @@ -2660,13 +2811,13 @@ static int apply_one_fragment(struct image *img, struct fragment *frag, */ 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); } @@ -2685,7 +2836,7 @@ static int apply_binary_fragment(struct image *img, struct patch *patch) void *dst; if (!fragment) - return error("missing binary patch data for '%s'", + return error(_("missing binary patch data for '%s'"), patch->new_name ? patch->new_name : patch->old_name); @@ -2720,6 +2871,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; @@ -2782,13 +2939,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)); } @@ -2809,7 +2966,7 @@ static int apply_fragments(struct image *img, struct patch *patch) while (frag) { nth++; if (apply_one_fragment(img, frag, inaccurate_eof, ws_rule, nth)) { - error("patch failed: %s:%ld", name, frag->oldpos); + error(_("patch failed: %s:%ld"), name, frag->oldpos); if (!apply_with_reject) return -1; frag->rejected = 1; @@ -2819,20 +2976,17 @@ static int apply_fragments(struct image *img, struct patch *patch) return 0; } -static int read_file_or_gitlink(struct cache_entry *ce, struct strbuf *buf) +static int read_blob_object(struct strbuf *buf, const unsigned char *sha1, unsigned mode) { - if (!ce) - return 0; - - if (S_ISGITLINK(ce->ce_mode)) { + if (S_ISGITLINK(mode)) { strbuf_grow(buf, 100); - strbuf_addf(buf, "Subproject commit %s\n", sha1_to_hex(ce->sha1)); + strbuf_addf(buf, "Subproject commit %s\n", sha1_to_hex(sha1)); } else { enum object_type type; unsigned long sz; char *result; - result = read_sha1_file(ce->sha1, &type, &sz); + result = read_sha1_file(sha1, &type, &sz); if (!result) return -1; /* XXX read_sha1_file NUL-terminates */ @@ -2841,6 +2995,13 @@ static int read_file_or_gitlink(struct cache_entry *ce, struct strbuf *buf) return 0; } +static int read_file_or_gitlink(struct cache_entry *ce, struct strbuf *buf) +{ + if (!ce) + return 0; + return read_blob_object(buf, ce->sha1, ce->ce_mode); +} + static struct patch *in_fn_table(const char *name) { struct string_list_item *item; @@ -2859,9 +3020,15 @@ static struct patch *in_fn_table(const char *name) * item->util in the filename table records the status of the path. * Usually it points at a patch (whose result records the contents * of it after applying it), but it could be PATH_WAS_DELETED for a - * path that a previously applied patch has already removed. + * path that a previously applied patch has already removed, or + * PATH_TO_BE_DELETED for a path that a later patch would remove. + * + * The latter is needed to deal with a case where two paths A and B + * are swapped by first renaming A to B and then renaming B to A; + * moving A to B should not be prevented due to presense of B as we + * will remove it in a later patch. */ - #define PATH_TO_BE_DELETED ((struct patch *) -2) +#define PATH_TO_BE_DELETED ((struct patch *) -2) #define PATH_WAS_DELETED ((struct patch *) -1) static int to_be_deleted(struct patch *patch) @@ -2913,152 +3080,346 @@ static void prepare_fn_table(struct patch *patch) } } -static int apply_data(struct patch *patch, struct stat *st, struct cache_entry *ce) +static int checkout_target(struct cache_entry *ce, struct stat *st) +{ + struct checkout costate; + + memset(&costate, 0, sizeof(costate)); + costate.base_dir = ""; + costate.refresh_cache = 1; + if (checkout_entry(ce, &costate, NULL) || lstat(ce->name, st)) + return error(_("cannot checkout %s"), ce->name); + return 0; +} + +static struct patch *previous_patch(struct patch *patch, int *gone) +{ + struct patch *previous; + + *gone = 0; + if (patch->is_copy || patch->is_rename) + return NULL; /* "git" patches do not depend on the order */ + + previous = in_fn_table(patch->old_name); + if (!previous) + return NULL; + + if (to_be_deleted(previous)) + return NULL; /* the deletion hasn't happened yet */ + + if (was_deleted(previous)) + *gone = 1; + + return previous; +} + +static int verify_index_match(struct cache_entry *ce, struct stat *st) +{ + if (S_ISGITLINK(ce->ce_mode)) { + if (!S_ISDIR(st->st_mode)) + return -1; + return 0; + } + return ce_match_stat(ce, st, CE_MATCH_IGNORE_VALID|CE_MATCH_IGNORE_SKIP_WORKTREE); +} + +#define SUBMODULE_PATCH_WITHOUT_INDEX 1 + +static int load_patch_target(struct strbuf *buf, + struct cache_entry *ce, + struct stat *st, + const char *name, + unsigned expected_mode) +{ + if (cached) { + if (read_file_or_gitlink(ce, buf)) + return error(_("read of %s failed"), name); + } else if (name) { + if (S_ISGITLINK(expected_mode)) { + if (ce) + return read_file_or_gitlink(ce, buf); + else + return SUBMODULE_PATCH_WITHOUT_INDEX; + } else { + if (read_old_data(st, name, buf)) + return error(_("read of %s failed"), name); + } + } + return 0; +} + +/* + * We are about to apply "patch"; populate the "image" with the + * current version we have, from the working tree or from the index, + * depending on the situation e.g. --cached/--index. If we are + * applying a non-git patch that incrementally updates the tree, + * we read from the result of a previous diff. + */ +static int load_preimage(struct image *image, + struct patch *patch, struct stat *st, struct cache_entry *ce) { struct strbuf buf = STRBUF_INIT; - struct image image; size_t len; char *img; - struct patch *tpatch; + struct patch *previous; + int status; - if (!(patch->is_copy || patch->is_rename) && - (tpatch = in_fn_table(patch->old_name)) != NULL && !to_be_deleted(tpatch)) { - if (was_deleted(tpatch)) { - return error("patch %s has been renamed/deleted", - patch->old_name); - } - /* We have a patched copy in memory use that */ - strbuf_add(&buf, tpatch->result, tpatch->resultsize); - } else if (cached) { - if (read_file_or_gitlink(ce, &buf)) - return error("read of %s failed", patch->old_name); - } else if (patch->old_name) { - if (S_ISGITLINK(patch->old_mode)) { - if (ce) { - read_file_or_gitlink(ce, &buf); - } else { - /* - * There is no way to apply subproject - * patch without looking at the index. - */ - patch->fragments = NULL; - } - } else { - if (read_old_data(st, patch->old_name, &buf)) - return error("read of %s failed", patch->old_name); + previous = previous_patch(patch, &status); + if (status) + return error(_("path %s has been renamed/deleted"), + patch->old_name); + if (previous) { + /* We have a patched copy in memory; use that. */ + strbuf_add(&buf, previous->result, previous->resultsize); + } else { + status = load_patch_target(&buf, ce, st, + patch->old_name, patch->old_mode); + if (status < 0) + return status; + else if (status == SUBMODULE_PATCH_WITHOUT_INDEX) { + /* + * 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 (status) { + return error(_("read of %s failed"), patch->old_name); } } img = strbuf_detach(&buf, &len); - prepare_image(&image, img, len, !patch->is_binary); + prepare_image(image, img, len, !patch->is_binary); + return 0; +} - if (apply_fragments(&image, patch) < 0) - return -1; /* note with --reject this succeeds. */ - patch->result = image.buf; - patch->resultsize = image.len; - add_to_fn_table(patch); - free(image.line_allocated); +static int three_way_merge(struct image *image, + char *path, + const unsigned char *base, + const unsigned char *ours, + const unsigned char *theirs) +{ + mmfile_t base_file, our_file, their_file; + mmbuffer_t result = { NULL }; + int status; - if (0 < patch->is_delete && patch->resultsize) - return error("removal patch leaves file contents"); + read_mmblob(&base_file, base); + read_mmblob(&our_file, ours); + read_mmblob(&their_file, theirs); + status = ll_merge(&result, path, + &base_file, "base", + &our_file, "ours", + &their_file, "theirs", NULL); + free(base_file.ptr); + free(our_file.ptr); + free(their_file.ptr); + if (status < 0 || !result.ptr) { + free(result.ptr); + return -1; + } + clear_image(image); + image->buf = result.ptr; + image->len = result.size; + return status; +} + +/* + * When directly falling back to add/add three-way merge, we read from + * the current contents of the new_name. In no cases other than that + * this function will be called. + */ +static int load_current(struct image *image, struct patch *patch) +{ + struct strbuf buf = STRBUF_INIT; + int status, pos; + size_t len; + char *img; + struct stat st; + struct cache_entry *ce; + char *name = patch->new_name; + unsigned mode = patch->new_mode; + + if (!patch->is_new) + die("BUG: patch to %s is not a creation", patch->old_name); + + pos = cache_name_pos(name, strlen(name)); + if (pos < 0) + return error(_("%s: does not exist in index"), name); + ce = active_cache[pos]; + if (lstat(name, &st)) { + if (errno != ENOENT) + return error(_("%s: %s"), name, strerror(errno)); + if (checkout_target(ce, &st)) + return -1; + } + if (verify_index_match(ce, &st)) + return error(_("%s: does not match index"), name); + + status = load_patch_target(&buf, ce, &st, name, mode); + if (status < 0) + return status; + else if (status) + return -1; + img = strbuf_detach(&buf, &len); + prepare_image(image, img, len, !patch->is_binary); return 0; } -static int check_to_create_blob(const char *new_name, int ok_if_exists) +static int try_threeway(struct image *image, struct patch *patch, + struct stat *st, struct cache_entry *ce) { - struct stat nst; - if (!lstat(new_name, &nst)) { - if (S_ISDIR(nst.st_mode) || ok_if_exists) - return 0; - /* - * A leading component of new_name might be a symlink - * that is going to be removed with this patch, but - * still pointing at somewhere that has the path. - * In such a case, path "new_name" does not exist as - * far as git is concerned. - */ - if (has_symlink_leading_path(new_name, strlen(new_name))) - return 0; + unsigned char pre_sha1[20], post_sha1[20], our_sha1[20]; + struct strbuf buf = STRBUF_INIT; + size_t len; + int status; + char *img; + struct image tmp_image; + + /* No point falling back to 3-way merge in these cases */ + if (patch->is_delete || + S_ISGITLINK(patch->old_mode) || S_ISGITLINK(patch->new_mode)) + return -1; + + /* Preimage the patch was prepared for */ + if (patch->is_new) + write_sha1_file("", 0, blob_type, pre_sha1); + else if (get_sha1(patch->old_sha1_prefix, pre_sha1) || + read_blob_object(&buf, pre_sha1, patch->old_mode)) + return error("repository lacks the necessary blob to fall back on 3-way merge."); + + fprintf(stderr, "Falling back to three-way merge...\n"); - return error("%s: already exists in working directory", new_name); + img = strbuf_detach(&buf, &len); + prepare_image(&tmp_image, img, len, 1); + /* Apply the patch to get the post image */ + if (apply_fragments(&tmp_image, patch) < 0) { + clear_image(&tmp_image); + return -1; + } + /* post_sha1[] is theirs */ + write_sha1_file(tmp_image.buf, tmp_image.len, blob_type, post_sha1); + clear_image(&tmp_image); + + /* our_sha1[] is ours */ + if (patch->is_new) { + if (load_current(&tmp_image, patch)) + return error("cannot read the current contents of '%s'", + patch->new_name); + } else { + if (load_preimage(&tmp_image, patch, st, ce)) + return error("cannot read the current contents of '%s'", + patch->old_name); + } + write_sha1_file(tmp_image.buf, tmp_image.len, blob_type, our_sha1); + clear_image(&tmp_image); + + /* in-core three-way merge between post and our using pre as base */ + status = three_way_merge(image, patch->new_name, + pre_sha1, our_sha1, post_sha1); + if (status < 0) { + fprintf(stderr, "Failed to fall back on three-way merge...\n"); + return status; + } + + if (status) { + patch->conflicted_threeway = 1; + if (patch->is_new) + hashclr(patch->threeway_stage[0]); + else + hashcpy(patch->threeway_stage[0], pre_sha1); + hashcpy(patch->threeway_stage[1], our_sha1); + hashcpy(patch->threeway_stage[2], post_sha1); + fprintf(stderr, "Applied patch to '%s' with conflicts.\n", patch->new_name); + } else { + fprintf(stderr, "Applied patch to '%s' cleanly.\n", patch->new_name); } - else if ((errno != ENOENT) && (errno != ENOTDIR)) - return error("%s: %s", new_name, strerror(errno)); return 0; } -static int verify_index_match(struct cache_entry *ce, struct stat *st) +static int apply_data(struct patch *patch, struct stat *st, struct cache_entry *ce) { - if (S_ISGITLINK(ce->ce_mode)) { - if (!S_ISDIR(st->st_mode)) + struct image image; + + if (load_preimage(&image, patch, st, ce) < 0) + return -1; + + if (patch->direct_to_threeway || + apply_fragments(&image, patch) < 0) { + /* Note: with --reject, apply_fragments() returns 0 */ + if (!threeway || try_threeway(&image, patch, st, ce) < 0) return -1; - return 0; } - return ce_match_stat(ce, st, CE_MATCH_IGNORE_VALID|CE_MATCH_IGNORE_SKIP_WORKTREE); + patch->result = image.buf; + patch->resultsize = image.len; + add_to_fn_table(patch); + free(image.line_allocated); + + if (0 < patch->is_delete && patch->resultsize) + return error(_("removal patch leaves file contents")); + + return 0; } +/* + * If "patch" that we are looking at modifies or deletes what we have, + * we would want it not to lose any local modification we have, either + * in the working tree or in the index. + * + * This also decides if a non-git patch is a creation patch or a + * modification to an existing empty file. We do not check the state + * of the current tree for a creation patch in this function; the caller + * check_patch() separately makes sure (and errors out otherwise) that + * the path the patch creates does not exist in the current tree. + */ static int check_preimage(struct patch *patch, struct cache_entry **ce, struct stat *st) { const char *old_name = patch->old_name; - struct patch *tpatch = NULL; - int stat_ret = 0; + struct patch *previous = NULL; + int stat_ret = 0, status; unsigned st_mode = 0; - /* - * Make sure that we do not have local modifications from the - * index when we are looking at the index. Also make sure - * we have the preimage file to be patched in the work tree, - * unless --cached, which tells git to apply only in the index. - */ if (!old_name) return 0; assert(patch->is_new <= 0); + previous = previous_patch(patch, &status); - if (!(patch->is_copy || patch->is_rename) && - (tpatch = in_fn_table(old_name)) != NULL && !to_be_deleted(tpatch)) { - if (was_deleted(tpatch)) - return error("%s: has been deleted/renamed", old_name); - st_mode = tpatch->new_mode; + if (status) + return error(_("path %s has been renamed/deleted"), old_name); + if (previous) { + st_mode = previous->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)) - tpatch = NULL; - - if (check_index && !tpatch) { + if (check_index && !previous) { int pos = cache_name_pos(old_name, strlen(old_name)); if (pos < 0) { if (patch->is_new < 0) goto is_new; - return error("%s: does not exist in index", old_name); + return error(_("%s: does not exist in index"), old_name); } *ce = active_cache[pos]; if (stat_ret < 0) { - struct checkout costate; - /* checkout */ - memset(&costate, 0, sizeof(costate)); - costate.base_dir = ""; - costate.refresh_cache = 1; - if (checkout_entry(*ce, &costate, NULL) || - lstat(old_name, st)) + if (checkout_target(*ce, st)) 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) + if (!cached && !previous) st_mode = ce_mode_from_stat(*ce, st->st_mode); if (patch->is_new < 0) @@ -3066,9 +3427,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; @@ -3077,10 +3438,50 @@ 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; } + +#define EXISTS_IN_INDEX 1 +#define EXISTS_IN_WORKTREE 2 + +static int check_to_create(const char *new_name, int ok_if_exists) +{ + struct stat nst; + + if (check_index && + cache_name_pos(new_name, strlen(new_name)) >= 0 && + !ok_if_exists) + return EXISTS_IN_INDEX; + if (cached) + return 0; + + if (!lstat(new_name, &nst)) { + if (S_ISDIR(nst.st_mode) || ok_if_exists) + return 0; + /* + * A leading component of new_name might be a symlink + * that is going to be removed with this patch, but + * still pointing at somewhere that has the path. + * In such a case, path "new_name" does not exist as + * far as git is concerned. + */ + if (has_symlink_leading_path(new_name, strlen(new_name))) + return 0; + + return EXISTS_IN_WORKTREE; + } else if ((errno != ENOENT) && (errno != ENOTDIR)) { + return error("%s: %s", new_name, strerror(errno)); + } + 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; @@ -3099,31 +3500,45 @@ static int check_patch(struct patch *patch) return status; old_name = patch->old_name; + /* + * A type-change diff is always split into a patch to delete + * old, immediately followed by a patch to create new (see + * diff.c::run_diff()); in such a case it is Ok that the entry + * to be deleted by the previous patch is still in the working + * tree and in the index. + * + * A patch to swap-rename between A and B would first rename A + * to B and then rename B to A. While applying the first one, + * the presense of B should not stop A from getting renamed to + * B; ask to_be_deleted() about the later rename. Removal of + * B and rename from A to B is handled the same way by asking + * was_deleted(). + */ if ((tpatch = in_fn_table(new_name)) && - (was_deleted(tpatch) || to_be_deleted(tpatch))) - /* - * A type-change diff is always split into a patch to - * delete old, immediately followed by a patch to - * create new (see diff.c::run_diff()); in such a case - * it is Ok that the entry to be deleted by the - * previous patch is still in the working tree and in - * the index. - */ + (was_deleted(tpatch) || to_be_deleted(tpatch))) ok_if_exists = 1; else ok_if_exists = 0; if (new_name && ((0 < patch->is_new) | (0 < patch->is_rename) | patch->is_copy)) { - if (check_index && - cache_name_pos(new_name, strlen(new_name)) >= 0 && - !ok_if_exists) - return error("%s: already exists in index", new_name); - if (!cached) { - int err = check_to_create_blob(new_name, ok_if_exists); - if (err) - return err; + int err = check_to_create(new_name, ok_if_exists); + + if (err && threeway) { + patch->direct_to_threeway = 1; + } else switch (err) { + case 0: + break; /* happy */ + case EXISTS_IN_INDEX: + return error(_("%s: already exists in index"), new_name); + break; + case EXISTS_IN_WORKTREE: + return error(_("%s: already exists in working directory"), + new_name); + default: + return err; } + if (!patch->new_mode) { if (0 < patch->is_new) patch->new_mode = S_IFREG | 0644; @@ -3136,14 +3551,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; } @@ -3156,7 +3579,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; } @@ -3188,7 +3611,6 @@ static void build_fake_ancestor(struct patch *list, const char *filename) * worth showing the new sha1 prefix, but until then... */ for (patch = list; patch; patch = patch->next) { - const unsigned char *sha1_ptr; unsigned char sha1[20]; struct cache_entry *ce; const char *name; @@ -3196,22 +3618,25 @@ static void build_fake_ancestor(struct patch *list, const char *filename) name = patch->old_name ? patch->old_name : patch->new_name; if (0 < patch->is_new) continue; - else if (get_sha1(patch->old_sha1_prefix, sha1)) - /* git diff has no index line for mode/type changes */ - if (!patch->lines_added && !patch->lines_deleted) { - if (get_current_sha1(patch->old_name, sha1)) - die("mode change for %s, which is not " - "in current HEAD", name); - sha1_ptr = sha1; - } else - die("sha1 information is lacking or useless " - "(%s).", name); - else - sha1_ptr = sha1; - ce = make_cache_entry(patch->old_mode, sha1_ptr, name, 0, 0); + if (S_ISGITLINK(patch->old_mode)) { + if (get_sha1_hex(patch->old_sha1_prefix, sha1)) + die("submoule change for %s without full index name", + name); + } else if (!get_sha1_blob(patch->old_sha1_prefix, sha1)) { + ; /* ok */ + } else if (!patch->lines_added && !patch->lines_deleted) { + /* mode-only change: update the current */ + if (get_current_sha1(patch->old_name, sha1)) + die("mode change for %s, which is not " + "in current HEAD", name); + } else + die("sha1 information is lacking or useless " + "(%s).", name); + + ce = make_cache_entry(patch->old_mode, sha1, 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); } @@ -3234,7 +3659,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) @@ -3354,7 +3779,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) { @@ -3376,24 +3801,25 @@ static void add_index_file(const char *path, unsigned mode, void *buf, unsigned ce = xcalloc(1, ce_size); memcpy(ce->name, path, namelen); ce->ce_mode = create_ce_mode(mode); - ce->ce_flags = namelen; + ce->ce_flags = create_ce_flags(0); + ce->ce_namelen = namelen; if (S_ISGITLINK(mode)) { const char *s = buf; if (get_sha1_hex(s + strlen("Subproject commit "), ce->sha1)) - die("corrupt patch for subproject %s", path); + 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) @@ -3426,7 +3852,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; } @@ -3475,7 +3901,34 @@ 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 add_conflicted_stages_file(struct patch *patch) +{ + int stage, namelen; + unsigned ce_size, mode; + struct cache_entry *ce; + + if (!update_index) + return; + namelen = strlen(patch->new_name); + ce_size = cache_entry_size(namelen); + mode = patch->new_mode ? patch->new_mode : (S_IFREG | 0644); + + remove_file_from_cache(patch->new_name); + for (stage = 1; stage < 4; stage++) { + if (is_null_sha1(patch->threeway_stage[stage - 1])) + continue; + ce = xcalloc(1, ce_size); + memcpy(ce->name, patch->new_name, namelen); + ce->ce_mode = create_ce_mode(mode); + ce->ce_flags = create_ce_flags(stage); + ce->ce_namelen = namelen; + hashcpy(ce->sha1, patch->threeway_stage[stage - 1]); + if (add_cache_entry(ce, ADD_CACHE_OK_TO_ADD) < 0) + die(_("unable to add cache entry for %s"), patch->new_name); + } } static void create_file(struct patch *patch) @@ -3488,7 +3941,11 @@ static void create_file(struct patch *patch) if (!mode) mode = S_IFREG | 0644; create_one_file(path, mode, buf, size); - add_index_file(path, mode, buf, size); + + if (patch->conflicted_threeway) + add_conflicted_stages_file(patch); + else + add_index_file(path, mode, buf, size); } /* phase zero is to remove, phase one is to create */ @@ -3520,6 +3977,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) @@ -3530,7 +3988,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; } @@ -3538,16 +3996,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); @@ -3555,7 +4017,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 @@ -3568,10 +4030,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); @@ -3580,14 +4042,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"); + struct string_list cpath = STRING_LIST_INIT_DUP; for (phase = 0; phase < 2; phase++) { l = list; @@ -3596,12 +4056,30 @@ static int write_out_results(struct patch *list, int skipped_patch) errs = 1; else { write_out_one_result(l, phase); - if (phase == 1 && write_out_one_reject(l)) - errs = 1; + if (phase == 1) { + if (write_out_one_reject(l)) + errs = 1; + if (l->conflicted_threeway) { + string_list_append(&cpath, l->new_name); + errs = 1; + } + } } l = l->next; } } + + if (cpath.nr) { + struct string_list_item *item; + + sort_string_list(&cpath); + for_each_string_list_item(item, &cpath) + fprintf(stderr, "U %s\n", item->string); + string_list_clear(&cpath, 0); + + rerere(0); + } + return errs; } @@ -3660,15 +4138,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); } } @@ -3678,12 +4149,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; @@ -3707,13 +4176,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; @@ -3723,7 +4194,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) && @@ -3731,8 +4202,12 @@ static int apply_patch(int fd, const char *filename, int options) !apply_with_reject) exit(1); - if (apply && write_out_results(list, skipped_patch)) - exit(1); + if (apply && write_out_results(list)) { + if (apply_with_reject) + exit(1); + /* with --3way, we still need to write the index out */ + return 1; + } if (fake_ancestor) build_fake_ancestor(list, fake_ancestor); @@ -3746,7 +4221,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; } @@ -3831,76 +4308,73 @@ 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_("show 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_BOOL('3', "3way", &threeway, + N_( "attempt three-way merge if a patch does not apply")), 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"), + N_("leave the rejected hunks in corresponding *.rej files")), OPT_BOOLEAN(0, "allow-overlap", &allow_overlap, - "allow overlapping hunks"), - OPT__VERBOSE(&apply_verbosely, "be verbose"), + 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() }; @@ -3916,15 +4390,24 @@ int cmd_apply(int argc, const char **argv, const char *prefix_) argc = parse_options(argc, argv, prefix, builtin_apply_options, apply_usage, 0); + if (apply_with_reject && threeway) + die("--reject and --3way cannot be used together."); + if (cached && threeway) + die("--cached and --3way cannot be used together."); + if (threeway) { + if (is_not_gitdir) + die(_("--3way outside a repository")); + check_index = 1; + } if (apply_with_reject) apply = apply_verbosely = 1; if (!force_apply && (diffstat || numstat || summary || check || fake_ancestor)) apply = 0; if (check_index && is_not_gitdir) - die("--index outside a repository"); + 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++) { @@ -3940,7 +4423,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); @@ -3954,32 +4437,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 b14eaba159..9a1cfd3dac 100644 --- a/builtin/archive.c +++ b/builtin/archive.c @@ -24,7 +24,8 @@ static void create_output_file(const char *output_file) } 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; @@ -37,6 +38,17 @@ static int run_remote_archiver(int argc, const char **argv, 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]); @@ -49,6 +61,8 @@ static int run_remote_archiver(int argc, const char **argv, if (strcmp(buf, "ACK")) { if (len > 5 && !prefixcmp(buf, "NACK ")) die(_("git archive: NACK %s"), buf + 5); + if (len > 4 && !prefixcmp(buf, "ERR ")) + die(_("remote error: %s"), buf + 4); die(_("git archive: protocol error")); } @@ -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,46 +87,26 @@ int cmd_archive(int argc, const char **argv, const char *prefix) const char *exec = "git-upload-archive"; const char *output = NULL; const char *remote = NULL; - const char *format_option = NULL; struct option local_opts[] = { - OPT_STRING('o', "output", &output, "file", - "write the archive to this file"), - OPT_STRING(0, "remote", &remote, "repo", - "retrieve the archive from remote repository <repo>"), - OPT_STRING(0, "exec", &exec, "cmd", - "path to the remote git-upload-archive command"), + OPT_STRING('o', "output", &output, N_("file"), + N_("write the archive to this file")), + OPT_STRING(0, "remote", &remote, N_("repo"), + N_("retrieve the archive from remote repository <repo>")), + OPT_STRING(0, "exec", &exec, N_("command"), + N_("path to the remote git-upload-archive command")), OPT_END() }; argc = parse_options(argc, argv, prefix, local_opts, NULL, PARSE_OPT_KEEP_ALL); - if (output) { + 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..e3884e3bb6 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", + N_("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'"), + N_("perform 'git bisect next'")), + OPT_BOOLEAN(0, "no-checkout", &no_checkout, + N_("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 3e1f7e1e45..cfae569905 100644 --- a/builtin/blame.c +++ b/builtin/blame.c @@ -22,12 +22,12 @@ #include "utf8.h" #include "userdiff.h" -static char blame_usage[] = "git blame [options] [rev-opts] [rev] [--] file"; +static char blame_usage[] = N_("git blame [options] [rev-opts] [rev] [--] file"); static const char *blame_opt_usage[] = { blame_usage, "", - "[rev-opts] are documented in git-rev-list(1)", + N_("[rev-opts] are documented in git-rev-list(1)"), NULL }; @@ -88,6 +88,20 @@ struct origin { 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. @@ -96,6 +110,7 @@ struct origin { int textconv_object(const char *path, unsigned mode, const unsigned char *sha1, + int sha1_valid, char **buf, unsigned long *buf_size) { @@ -103,7 +118,7 @@ int textconv_object(const char *path, struct userdiff_driver *textconv; df = alloc_filespec(path); - fill_filespec(df, sha1, mode); + fill_filespec(df, sha1, sha1_valid, mode); textconv = get_textconv(df); if (!textconv) { free_filespec(df); @@ -128,7 +143,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->mode, o->blob_sha1, &file->ptr, &file_size)) + textconv_object(o->path, o->mode, o->blob_sha1, 1, &file->ptr, &file_size)) ; else file->ptr = read_sha1_file(o->blob_sha1, &type, &file_size); @@ -392,8 +407,7 @@ static struct origin *find_origin(struct scoreboard *sb, paths[1] = NULL; diff_tree_setup_paths(paths, &diff_opts); - if (diff_setup_done(&diff_opts) < 0) - die("diff-setup"); + diff_setup_done(&diff_opts); if (is_null_sha1(origin->commit->object.sha1)) do_diff_cache(parent->tree->object.sha1, &diff_opts); @@ -479,8 +493,7 @@ static struct origin *find_rename(struct scoreboard *sb, diff_opts.single_follow = origin->path; paths[0] = NULL; diff_tree_setup_paths(paths, &diff_opts); - if (diff_setup_done(&diff_opts) < 0) - die("diff-setup"); + diff_setup_done(&diff_opts); if (is_null_sha1(origin->commit->object.sha1)) do_diff_cache(parent->tree->object.sha1, &diff_opts); @@ -759,12 +772,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; } /* @@ -779,8 +794,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); @@ -791,11 +805,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); @@ -899,12 +909,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; } /* @@ -922,8 +935,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; /* @@ -943,12 +955,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); } @@ -1065,8 +1073,7 @@ static int find_copy_in_parent(struct scoreboard *sb, paths[0] = NULL; diff_tree_setup_paths(paths, &diff_opts); - if (diff_setup_done(&diff_opts) < 0) - die("diff-setup"); + diff_setup_done(&diff_opts); /* Try "find copies harder" on new path if requested; * we do not want to use diffcore_rename() actually to @@ -1418,7 +1425,7 @@ static void get_commit_info(struct commit *commit, int detailed) { int len; - const char *subject; + const char *subject, *encoding; char *reencoded, *message; static char author_name[1024]; static char author_mail[1024]; @@ -1439,7 +1446,8 @@ static void get_commit_info(struct commit *commit, die("Cannot read commit %s", sha1_to_hex(commit->object.sha1)); } - reencoded = reencode_commit_message(commit, NULL); + encoding = get_log_output_encoding(); + reencoded = logmsg_reencode(commit, encoding); message = reencoded ? reencoded : commit->buffer; ret->author = author_name; ret->author_mail = author_mail; @@ -1828,16 +1836,14 @@ static int read_ancestry(const char *graft_file) return 0; } -/* - * How many columns do we need to show line numbers in decimal? - */ -static int lineno_width(int lines) +static int update_auto_abbrev(int auto_abbrev, struct origin *suspect) { - int i, width; - - for (width = 1, i = 10; i <= lines; width++) - i *= 10; - return width; + const char *uniq = find_unique_abbrev(suspect->commit->object.sha1, + auto_abbrev); + int len = strlen(uniq); + if (auto_abbrev < len) + return len; + return auto_abbrev; } /* @@ -1850,12 +1856,16 @@ static void find_alignment(struct scoreboard *sb, int *option) int longest_dst_lines = 0; unsigned largest_score = 0; struct blame_entry *e; + int compute_auto_abbrev = (abbrev < 0); + int auto_abbrev = default_abbrev; for (e = sb->ent; e; e = e->next) { struct origin *suspect = e->suspect; struct commit_info ci; int num; + if (compute_auto_abbrev) + auto_abbrev = update_auto_abbrev(auto_abbrev, suspect); if (strcmp(suspect->path, sb->path)) *option |= OUTPUT_SHOW_NAME; num = strlen(suspect->path); @@ -1880,9 +1890,13 @@ 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); + + if (compute_auto_abbrev) + /* one more abbrev length is needed for the boundary commit */ + abbrev = auto_abbrev + 1; } /* @@ -2050,18 +2064,61 @@ 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); } +static void verify_working_tree_path(struct commit *work_tree, const char *path) +{ + struct commit_list *parents; + + for (parents = work_tree->parents; parents; parents = parents->next) { + const unsigned char *commit_sha1 = parents->item->object.sha1; + unsigned char blob_sha1[20]; + unsigned mode; + + if (!get_tree_entry(commit_sha1, path, blob_sha1, &mode) && + sha1_object_info(blob_sha1, NULL) == OBJ_BLOB) + return; + } + die("no such path '%s' in HEAD", path); +} + +static struct commit_list **append_parent(struct commit_list **tail, const unsigned char *sha1) +{ + struct commit *parent; + + parent = lookup_commit_reference(sha1); + if (!parent) + die("no such commit %s", sha1_to_hex(sha1)); + return &commit_list_insert(parent, tail)->next; +} + +static void append_merge_parents(struct commit_list **tail) +{ + int merge_head; + const char *merge_head_file = git_path("MERGE_HEAD"); + struct strbuf line = STRBUF_INIT; + + merge_head = open(merge_head_file, O_RDONLY); + if (merge_head < 0) { + if (errno == ENOENT) + return; + die("cannot open '%s' for reading", merge_head_file); + } + + while (!strbuf_getwholeline_fd(&line, merge_head, '\n')) { + unsigned char sha1[20]; + if (line.len < 40 || get_sha1_hex(line.buf, sha1)) + die("unknown line in '%s': %s", merge_head_file, line.buf); + tail = append_parent(tail, sha1); + } + close(merge_head); + strbuf_release(&line); +} + /* * Prepare a dummy commit that represents the work tree (or staged) item. * Note that annotating work tree item never works in the reverse. @@ -2072,6 +2129,7 @@ static struct commit *fake_working_tree_commit(struct diff_options *opt, { struct commit *commit; struct origin *origin; + struct commit_list **parent_tail, *parent; unsigned char head_sha1[20]; struct strbuf buf = STRBUF_INIT; const char *ident; @@ -2079,23 +2137,42 @@ static struct commit *fake_working_tree_commit(struct diff_options *opt, int size, len; struct cache_entry *ce; unsigned mode; - - if (get_sha1("HEAD", head_sha1)) - die("No such ref: HEAD"); + struct strbuf msg = STRBUF_INIT; time(&now); commit = xcalloc(1, sizeof(*commit)); - commit->parents = xcalloc(1, sizeof(*commit->parents)); - commit->parents->item = lookup_commit_reference(head_sha1); commit->object.parsed = 1; commit->date = now; commit->object.type = OBJ_COMMIT; + parent_tail = &commit->parents; + + if (!resolve_ref_unsafe("HEAD", head_sha1, 1, NULL)) + die("no such ref: HEAD"); + + parent_tail = append_parent(parent_tail, head_sha1); + append_merge_parents(parent_tail); + verify_working_tree_path(commit, path); origin = make_origin(commit, path); + ident = fmt_ident("Not Committed Yet", "not.committed.yet", NULL, 0); + strbuf_addstr(&msg, "tree 0000000000000000000000000000000000000000\n"); + for (parent = commit->parents; parent; parent = parent->next) + strbuf_addf(&msg, "parent %s\n", + sha1_to_hex(parent->item->object.sha1)); + strbuf_addf(&msg, + "author %s\n" + "committer %s\n\n" + "Version of %s from %s\n", + ident, ident, path, + (!contents_from ? path : + (!strcmp(contents_from, "-") ? "standard input" : contents_from))); + commit->buffer = strbuf_detach(&msg, NULL); + if (!contents_from || strcmp("-", contents_from)) { struct stat st; const char *read_from; + char *buf_ptr; unsigned long buf_len; if (contents_from) { @@ -2113,8 +2190,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, mode, null_sha1, &buf.buf, &buf_len)) - buf.len = buf_len; + textconv_object(read_from, mode, null_sha1, 0, &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; @@ -2128,7 +2205,6 @@ static struct commit *fake_working_tree_commit(struct diff_options *opt, } else { /* Reading from stdin */ - contents_from = "standard input"; mode = 0; if (strbuf_read(&buf, 0, 0) < 0) die_errno("failed to read from stdin"); @@ -2161,7 +2237,8 @@ static struct commit *fake_working_tree_commit(struct diff_options *opt, ce = xcalloc(1, size); hashcpy(ce->sha1, origin->blob_sha1); memcpy(ce->name, path, len); - ce->ce_flags = create_ce_flags(len, 0); + ce->ce_flags = create_ce_flags(0); + ce->ce_namelen = len; ce->ce_mode = create_ce_mode(mode); add_cache_entry(ce, ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE); @@ -2172,16 +2249,6 @@ static struct commit *fake_working_tree_commit(struct diff_options *opt, */ cache_tree_invalidate_path(active_cache_tree, path); - commit->buffer = xmalloc(400); - ident = fmt_ident("Not Committed Yet", "not.committed.yet", NULL, 0); - snprintf(commit->buffer, 400, - "tree 0000000000000000000000000000000000000000\n" - "parent %s\n" - "author %s\n" - "committer %s\n\n" - "Version of %s from %s\n", - sha1_to_hex(head_sha1), - ident, ident, path, contents_from ? contents_from : path); return commit; } @@ -2304,26 +2371,27 @@ int cmd_blame(int argc, const char **argv, const char *prefix) static const char *revs_file = NULL; static const char *contents_from = NULL; static const struct option options[] = { - OPT_BOOLEAN(0, "incremental", &incremental, "Show blame entries as we find them, incrementally"), - OPT_BOOLEAN('b', NULL, &blank_boundary, "Show blank SHA-1 for boundary commits (Default: off)"), - OPT_BOOLEAN(0, "root", &show_root, "Do not treat root commits as boundaries (Default: off)"), - OPT_BOOLEAN(0, "show-stats", &show_stats, "Show work cost statistics"), - OPT_BIT(0, "score-debug", &output_option, "Show output score for blame entries", OUTPUT_SHOW_SCORE), - OPT_BIT('f', "show-name", &output_option, "Show original filename (Default: auto)", OUTPUT_SHOW_NAME), - OPT_BIT('n', "show-number", &output_option, "Show original linenumber (Default: off)", OUTPUT_SHOW_NUMBER), - OPT_BIT('p', "porcelain", &output_option, "Show in a format designed for machine consumption", OUTPUT_PORCELAIN), - OPT_BIT(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_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_BOOLEAN(0, "incremental", &incremental, N_("Show blame entries as we find them, incrementally")), + OPT_BOOLEAN('b', NULL, &blank_boundary, N_("Show blank SHA-1 for boundary commits (Default: off)")), + OPT_BOOLEAN(0, "root", &show_root, N_("Do not treat root commits as boundaries (Default: off)")), + OPT_BOOLEAN(0, "show-stats", &show_stats, N_("Show work cost statistics")), + OPT_BIT(0, "score-debug", &output_option, N_("Show output score for blame entries"), OUTPUT_SHOW_SCORE), + OPT_BIT('f', "show-name", &output_option, N_("Show original filename (Default: auto)"), OUTPUT_SHOW_NAME), + OPT_BIT('n', "show-number", &output_option, N_("Show original linenumber (Default: off)"), OUTPUT_SHOW_NUMBER), + OPT_BIT('p', "porcelain", &output_option, N_("Show in a format designed for machine consumption"), OUTPUT_PORCELAIN), + OPT_BIT(0, "line-porcelain", &output_option, N_("Show porcelain format with per-line commit information"), OUTPUT_PORCELAIN|OUTPUT_LINE_PORCELAIN), + OPT_BIT('c', NULL, &output_option, N_("Use the same output mode as git-annotate (Default: off)"), OUTPUT_ANNOTATE_COMPAT), + OPT_BIT('t', NULL, &output_option, N_("Show raw timestamp (Default: off)"), OUTPUT_RAW_TIMESTAMP), + OPT_BIT('l', NULL, &output_option, N_("Show long commit SHA1 (Default: off)"), OUTPUT_LONG_OBJECT_NAME), + OPT_BIT('s', NULL, &output_option, N_("Suppress author name and timestamp (Default: off)"), OUTPUT_NO_AUTHOR), + OPT_BIT('e', "show-email", &output_option, N_("Show author email instead of name (Default: off)"), OUTPUT_SHOW_EMAIL), + OPT_BIT('w', NULL, &xdl_opts, N_("Ignore whitespace differences"), XDF_IGNORE_WHITESPACE), + OPT_BIT(0, "minimal", &xdl_opts, N_("Spend extra cycles to find better match"), XDF_NEED_MINIMAL), + OPT_STRING('S', NULL, &revs_file, N_("file"), N_("Use revisions from <file> instead of calling git-rev-list")), + OPT_STRING(0, "contents", &contents_from, N_("file"), N_("Use <file>'s contents as the final image")), + { OPTION_CALLBACK, 'C', NULL, &opt, N_("score"), N_("Find line copies within and across files"), PARSE_OPT_OPTARG, blame_copy_callback }, + { OPTION_CALLBACK, 'M', NULL, &opt, N_("score"), N_("Find line movements within and across files"), PARSE_OPT_OPTARG, blame_move_callback }, + OPT_CALLBACK('L', NULL, &bottomtop, N_("n,m"), N_("Process only line range n,m, counting from 1"), blame_bottomtop_callback), OPT__ABBREV(&abbrev), OPT_END() }; @@ -2360,10 +2428,9 @@ 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 (0 < 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); @@ -2505,7 +2572,7 @@ parse_done: die("no such path %s in %s", path, final_commit_name); if (DIFF_OPT_TST(&sb.revs->diffopt, ALLOW_TEXTCONV) && - textconv_object(path, o->mode, o->blob_sha1, (char **) &sb.final_buf, + textconv_object(path, o->mode, o->blob_sha1, 1, (char **) &sb.final_buf, &sb.final_buf_size)) ; else diff --git a/builtin/branch.c b/builtin/branch.c index 3142daa57a..947c84be2d 100644 --- a/builtin/branch.c +++ b/builtin/branch.c @@ -15,12 +15,15 @@ #include "branch.h" #include "diff.h" #include "revision.h" +#include "string-list.h" +#include "column.h" +#include "utf8.h" static const char * const builtin_branch_usage[] = { - "git branch [options] [-r | -a] [--merged | --no-merged]", - "git branch [options] [-l] [-f] <branchname> [<start-point>]", - "git branch [options] [-r] (-d | -D) <branchname>...", - "git branch [options] (-m | -M) [<oldbranch>] <newbranch>", + N_("git branch [options] [-r | -a] [--merged | --no-merged]"), + N_("git branch [options] [-l] [-f] <branchname> [<start-point>]"), + N_("git branch [options] [-r] (-d | -D) <branchname>..."), + N_("git branch [options] (-m | -M) [<oldbranch>] <newbranch>"), NULL }; @@ -53,6 +56,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 +76,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 +96,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 +112,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,14 +123,14 @@ 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) reference_rev = head_rev; - merged = in_merge_bases(rev, &reference_rev, 1); + merged = in_merge_bases(rev, reference_rev); /* * After the safety valve is fully redefined to "check with @@ -131,7 +140,7 @@ static int branch_merged(int kind, const char *name, * a gentle reminder is in order. */ if ((head_rev != reference_rev) && - in_merge_bases(rev, &head_rev, 1) != merged) { + in_merge_bases(rev, head_rev) != merged) { if (merged) warning(_("deleting branch '%s' that has been merged to\n" " '%s', but not yet merged to HEAD."), @@ -141,29 +150,59 @@ static int branch_merged(int kind, const char *name, " '%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 check_branch_commit(const char *branchname, const char *refname, + unsigned char *sha1, struct commit *head_rev, + int kinds, int force) +{ + struct commit *rev = lookup_commit_reference(sha1); + if (!rev) { + error(_("Couldn't look up commit object for '%s'"), refname); + return -1; + } + if (!force && !branch_merged(kinds, branchname, rev, head_rev)) { + error(_("The branch '%s' is not fully merged.\n" + "If you are sure you want to delete it, " + "run 'git branch -D %s'."), branchname, branchname); + return -1; + } + return 0; +} + +static void delete_branch_config(const char *branchname) +{ + struct strbuf buf = STRBUF_INIT; + strbuf_addf(&buf, "branch.%s", branchname); + if (git_config_rename_section(buf.buf, NULL) < 0) + warning(_("Update of config-file failed")); + strbuf_release(&buf); +} + +static int delete_branches(int argc, const char **argv, int force, int kinds, + int quiet) { - struct commit *rev, *head_rev = NULL; + struct commit *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"; - /* TRANSLATORS: This is "remote " in "remote branch '%s' not found" */ - 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")); @@ -175,6 +214,9 @@ static int delete_branches(int argc, const char **argv, int force, int kinds) die(_("Couldn't look up commit object for HEAD")); } for (i = 0; i < argc; i++, strbuf_release(&bname)) { + const char *target; + int flags = 0; + strbuf_branchname(&bname, argv[i]); if (kinds == REF_LOCAL_BRANCH && !strcmp(head, bname.buf)) { error(_("Cannot delete the branch '%s' " @@ -185,43 +227,42 @@ 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); + name = mkpathdup(fmt, bname.buf); + target = resolve_ref_unsafe(name, sha1, 0, &flags); + if (!target || + (!(flags & REF_ISSYMREF) && is_null_sha1(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); + if (!(flags & REF_ISSYMREF) && + check_branch_commit(bname.buf, name, sha1, head_rev, kinds, + force)) { ret = 1; continue; } - if (!force && !branch_merged(kinds, bname.buf, rev, head_rev)) { - error(_("The branch '%s' is not fully merged.\n" - "If you are sure you want to delete it, " - "run 'git branch -D %s'."), bname.buf, bname.buf); + if (delete_ref(name, sha1, REF_NODEREF)) { + error(remote_branch + ? _("Error deleting remote branch '%s'") + : _("Error deleting branch '%s'"), + bname.buf); ret = 1; continue; } - - if (delete_ref(name, sha1, 0)) { - error(_("Error deleting %sbranch '%s'"), remote, - bname.buf); - ret = 1; - } else { - struct strbuf buf = STRBUF_INIT; - printf(_("Deleted %sbranch %s (was %s).\n"), remote, + 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")); - strbuf_release(&buf); + (flags & REF_ISSYMREF) + ? target + : find_unique_abbrev(sha1, DEFAULT_ABBREV)); } + delete_branch_config(bname.buf); } free(name); @@ -232,7 +273,7 @@ static int delete_branches(int argc, const char **argv, int force, int kinds) struct ref_item { char *name; char *dest; - unsigned int kind, len; + unsigned int kind, width; struct commit *commit; }; @@ -250,7 +291,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))) @@ -260,9 +301,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); @@ -297,6 +351,9 @@ 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); @@ -321,14 +378,14 @@ static int append_ref(const char *refname, const unsigned char *sha1, int flags, newitem->name = xstrdup(refname); newitem->kind = kind; newitem->commit = commit; - newitem->len = strlen(refname); + newitem->width = utf8_strwidth(refname); newitem->dest = resolve_symref(orig_refname, prefix); /* adjust for "remotes/" */ if (newitem->kind == REF_REMOTE_BRANCH && ref_list->kinds != REF_REMOTE_BRANCH) - newitem->len += 8; - if (newitem->len > ref_list->maxwidth) - ref_list->maxwidth = newitem->len; + newitem->width += 8; + if (newitem->width > ref_list->maxwidth) + ref_list->maxwidth = newitem->width; return 0; } @@ -358,6 +415,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)) { @@ -368,16 +426,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) @@ -443,11 +514,12 @@ static void print_ref_item(struct ref_item *item, int maxwidth, int verbose, } strbuf_addf(&name, "%s%s", prefix, item->name); - if (verbose) + if (verbose) { + int utf8_compensation = strlen(name.buf) - utf8_strwidth(name.buf); strbuf_addf(&out, "%c %s%-*s%s", c, branch_get_color(color), - maxwidth, name.buf, + maxwidth + utf8_compensation, name.buf, branch_get_color(BRANCH_COLOR_RESET)); - else + } else strbuf_addf(&out, "%c %s%s%s", c, branch_get_color(color), name.buf, branch_get_color(BRANCH_COLOR_RESET)); @@ -456,7 +528,12 @@ static void print_ref_item(struct ref_item *item, int maxwidth, int verbose, else if (verbose) /* " f7c0c00 [ahead 58, behind 197] vcs-svn: drop obj_pool.h" */ add_verbose_info(&out, item, verbose, abbrev); - printf("%s\n", out.buf); + if (column_active(colopts)) { + assert(!verbose && "--column and --verbose are incompatible"); + string_list_append(&output, out.buf); + } else { + printf("%s\n", out.buf); + } strbuf_release(&name); strbuf_release(&out); } @@ -467,8 +544,8 @@ static int calc_maxwidth(struct ref_list *refs) for (i = 0; i < refs->index; i++) { if (!matches_merge_filter(refs->list[i].commit)) continue; - if (refs->list[i].len > w) - w = refs->list[i].len; + if (refs->list[i].width > w) + w = refs->list[i].width; } return w; } @@ -481,18 +558,18 @@ 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.len = strlen(item.name); + item.width = utf8_strwidth(item.name); item.kind = REF_LOCAL_BRANCH; item.dest = NULL; item.commit = head_commit; - if (item.len > ref_list->maxwidth) - ref_list->maxwidth = item.len; + if (item.width > ref_list->maxwidth) + ref_list->maxwidth = item.width; print_ref_item(&item, ref_list->maxwidth, ref_list->verbose, ref_list->abbrev, 1, ""); free(item.name); } } -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 +583,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 +605,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++) { @@ -548,9 +630,9 @@ 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.")); @@ -560,17 +642,19 @@ static void rename_branch(const char *oldname, const char *newname, int force) * 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); } - 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); @@ -610,61 +694,108 @@ static int opt_parse_merge_filter(const struct option *opt, const char *arg, int 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, unset_upstream = 0; + const char *new_upstream = NULL; enum branch_track track; int kinds = REF_LOCAL_BRANCH; struct commit_list *with_commit = NULL; struct option options[] = { - OPT_GROUP("Generic options"), + OPT_GROUP(N_("Generic options")), OPT__VERBOSE(&verbose, - "show hash and subject, give twice for upstream branch"), - OPT_SET_INT('t', "track", &track, "set up tracking mode (see git-pull(1))", + N_("show hash and subject, give twice for upstream branch")), + OPT__QUIET(&quiet, N_("suppress informational messages")), + OPT_SET_INT('t', "track", &track, N_("set up tracking mode (see git-pull(1))"), BRANCH_TRACK_EXPLICIT), - OPT_SET_INT( 0, "set-upstream", &track, "change upstream info", + OPT_SET_INT( 0, "set-upstream", &track, N_("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_STRING('u', "set-upstream-to", &new_upstream, "upstream", "change the upstream info"), + OPT_BOOLEAN(0, "unset-upstream", &unset_upstream, "Unset the upstream info"), + OPT__COLOR(&branch_use_color, N_("use colored output")), + OPT_SET_INT('r', "remotes", &kinds, N_("act on remote-tracking branches"), REF_REMOTE_BRANCH), { - OPTION_CALLBACK, 0, "contains", &with_commit, "commit", - "print only branches that contain the commit", + OPTION_CALLBACK, 0, "contains", &with_commit, N_("commit"), + N_("print only branches that contain the commit"), PARSE_OPT_LASTARG_DEFAULT, parse_opt_with_commit, (intptr_t)"HEAD", }, { - OPTION_CALLBACK, 0, "with", &with_commit, "commit", - "print only branches that contain the commit", + OPTION_CALLBACK, 0, "with", &with_commit, N_("commit"), + N_("print only branches that contain the commit"), PARSE_OPT_HIDDEN | PARSE_OPT_LASTARG_DEFAULT, parse_opt_with_commit, (intptr_t) "HEAD", }, OPT__ABBREV(&abbrev), - OPT_GROUP("Specific git-branch actions:"), - OPT_SET_INT('a', NULL, &kinds, "list both remote-tracking and local branches", + OPT_GROUP(N_("Specific git-branch actions:")), + OPT_SET_INT('a', "all", &kinds, N_("list both remote-tracking and local branches"), REF_REMOTE_BRANCH | REF_LOCAL_BRANCH), - OPT_BIT('d', NULL, &delete, "delete fully merged branch", 1), - OPT_BIT('D', NULL, &delete, "delete branch (even if not merged)", 2), - OPT_BIT('m', NULL, &rename, "move/rename a branch and its reflog", 1), - OPT_BIT('M', NULL, &rename, "move/rename a branch, even if target exists", 2), - OPT_BOOLEAN('l', NULL, &reflog, "create the branch's reflog"), - OPT__FORCE(&force_create, "force creation (when already exists)"), + OPT_BIT('d', "delete", &delete, N_("delete fully merged branch"), 1), + OPT_BIT('D', NULL, &delete, N_("delete branch (even if not merged)"), 2), + OPT_BIT('m', "move", &rename, N_("move/rename a branch and its reflog"), 1), + OPT_BIT('M', NULL, &rename, N_("move/rename a branch, even if target exists"), 2), + OPT_BOOLEAN(0, "list", &list, N_("list branch names")), + OPT_BOOLEAN('l', "create-reflog", &reflog, N_("create the branch's reflog")), + OPT_BOOLEAN(0, "edit-description", &edit_description, + N_("edit the description for the branch")), + OPT__FORCE(&force_create, N_("force creation (when already exists)")), { OPTION_CALLBACK, 0, "no-merged", &merge_filter_ref, - "commit", "print only not merged branches", + N_("commit"), N_("print only not merged branches"), PARSE_OPT_LASTARG_DEFAULT | PARSE_OPT_NONEG, opt_parse_merge_filter, (intptr_t) "HEAD", }, { OPTION_CALLBACK, 0, "merged", &merge_filter_ref, - "commit", "print only merged branches", + N_("commit"), N_("print only merged branches"), PARSE_OPT_LASTARG_DEFAULT | PARSE_OPT_NONEG, opt_parse_merge_filter, (intptr_t) "HEAD", }, + OPT_COLUMN(0, "column", &colopts, N_("list branches in columns")), OPT_END(), }; @@ -673,15 +804,11 @@ int cmd_branch(int argc, const char **argv, const char *prefix) git_config(git_branch_config, NULL); - if (branch_use_color == -1) - branch_use_color = git_use_color_default; - track = git_branch_track; - head = resolve_ref("HEAD", head_sha1, 0, NULL); + head = resolve_refdup("HEAD", head_sha1, 0, NULL); if (!head) die(_("Failed to resolve HEAD as a valid ref.")); - head = xstrdup(head); if (!strcmp(head, "HEAD")) { detached = 1; } else { @@ -691,24 +818,124 @@ int cmd_branch(int argc, const char **argv, const char *prefix) } 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 && !new_upstream && !unset_upstream && argc == 0) + list = 1; + + if (!!delete + !!rename + !!force_create + !!list + !!new_upstream + !!unset_upstream > 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 (!argc) { + if (detached) + die("Cannot give description to detached HEAD"); + 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 (new_upstream) { + struct branch *branch = branch_get(argv[0]); + + if (!ref_exists(branch->refname)) + die(_("branch '%s' does not exist"), branch->name); + + /* + * create_branch takes care of setting up the tracking + * info and making sure new_upstream is correct + */ + create_branch(head, branch->name, new_upstream, 0, 0, 0, quiet, BRANCH_TRACK_OVERRIDE); + } else if (unset_upstream) { + struct branch *branch = branch_get(argv[0]); + struct strbuf buf = STRBUF_INIT; + + if (!branch_has_merge_config(branch)) { + die(_("Branch '%s' has no upstream information"), branch->name); + } + + strbuf_addf(&buf, "branch.%s.remote", branch->name); + git_config_set_multivar(buf.buf, NULL, NULL, 1); + strbuf_reset(&buf); + strbuf_addf(&buf, "branch.%s.merge", branch->name); + git_config_set_multivar(buf.buf, NULL, NULL, 1); + strbuf_release(&buf); + } else if (argc > 0 && argc <= 2) { + struct branch *branch = branch_get(argv[0]); + int branch_existed = 0, remote_tracking = 0; + struct strbuf buf = STRBUF_INIT; + if (kinds != REF_LOCAL_BRANCH) die(_("-a and -r options to 'git branch' do not make sense with a branch name")); + + if (track == BRANCH_TRACK_OVERRIDE) + fprintf(stderr, _("The --set-upstream flag is deprecated and will be removed. Consider using --track or --set-upstream-to\n")); + + strbuf_addf(&buf, "refs/remotes/%s", branch->name); + remote_tracking = ref_exists(buf.buf); + strbuf_release(&buf); + + branch_existed = ref_exists(branch->refname); create_branch(head, argv[0], (argc == 2) ? argv[1] : head, - force_create, reflog, track); + force_create, reflog, 0, quiet, track); + + /* + * We only show the instructions if the user gave us + * one branch which doesn't exist locally, but is the + * name of a remote-tracking branch. + */ + if (argc == 1 && track == BRANCH_TRACK_OVERRIDE && + !branch_existed && remote_tracking) { + fprintf(stderr, _("\nIf you wanted to make '%s' track '%s', do this:\n\n"), head, branch->name); + fprintf(stderr, _(" git branch -d %s\n"), branch->name); + fprintf(stderr, _(" git branch --set-upstream-to %s\n"), branch->name); + } + } else usage_with_options(builtin_branch_usage, options); diff --git a/builtin/bundle.c b/builtin/bundle.c index 81046a9cb8..92a8a6026a 100644 --- a/builtin/bundle.c +++ b/builtin/bundle.c @@ -58,7 +58,7 @@ int cmd_bundle(int argc, const char **argv, const char *prefix) } else if (!strcmp(cmd, "unbundle")) { if (!startup_info->have_repository) die(_("Need a repository to unbundle.")); - return !!unbundle(&header, bundle_fd) || + 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 07bd984084..00528ddc38 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 @@ -90,7 +91,7 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name) unsigned long size; struct object_context obj_context; - if (get_sha1_with_context(obj_name, sha1, &obj_context)) + if (get_sha1_with_context(obj_name, 0, sha1, &obj_context)) die("Not a valid object name %s", obj_name); buf = NULL; @@ -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, obj_context.mode, sha1, &buf, &size)) + if (!textconv_object(obj_context.path, obj_context.mode, sha1, 1, &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; @@ -219,21 +244,15 @@ static int batch_objects(int print_contents) } static const char * const cat_file_usage[] = { - "git cat-file (-t|-s|-e|-p|<type>|--textconv) <object>", - "git cat-file (--batch|--batch-check) < <list_of_objects>", + N_("git cat-file (-t|-s|-e|-p|<type>|--textconv) <object>"), + N_("git cat-file (--batch|--batch-check) < <list_of_objects>"), NULL }; 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); } @@ -244,19 +263,19 @@ int cmd_cat_file(int argc, const char **argv, const char *prefix) const char *exp_type = NULL, *obj_name = NULL; const struct option options[] = { - OPT_GROUP("<type> can be one of: blob, tree, commit, tag"), - OPT_SET_INT('t', NULL, &opt, "show object type", 't'), - OPT_SET_INT('s', NULL, &opt, "show object size", 's'), + OPT_GROUP(N_("<type> can be one of: blob, tree, commit, tag")), + OPT_SET_INT('t', NULL, &opt, N_("show object type"), 't'), + OPT_SET_INT('s', NULL, &opt, N_("show object size"), 's'), OPT_SET_INT('e', NULL, &opt, - "exit with zero when there's no error", 'e'), - OPT_SET_INT('p', NULL, &opt, "pretty-print object's content", 'p'), + N_("exit with zero when there's no error"), 'e'), + OPT_SET_INT('p', NULL, &opt, N_("pretty-print object's content"), 'p'), OPT_SET_INT(0, "textconv", &opt, - "for blob objects, run textconv on object's content", 'c'), + N_("for blob objects, run textconv on object's content"), 'c'), OPT_SET_INT(0, "batch", &batch, - "show info and content of objects fed from the standard input", + N_("show info and content of objects fed from the standard input"), BATCH), OPT_SET_INT(0, "batch-check", &batch, - "show info about objects fed from the standard input", + N_("show info about objects fed from the standard input"), BATCH_CHECK), OPT_END() }; diff --git a/builtin/check-attr.c b/builtin/check-attr.c index 3016d29caa..075d01d30c 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>", +N_("git check-attr [-a | --all | attr...] [--] pathname..."), +N_("git check-attr --stdin [-z] [-a | --all | attr...] < <list-of-paths>"), NULL }; static int null_term_line; static const struct option check_attr_options[] = { - OPT_BOOLEAN(0 , "stdin", &stdin_paths, "read file names from stdin"), + OPT_BOOLEAN('a', "all", &all_attrs, N_("report all attributes set on file")), + OPT_BOOLEAN(0, "cached", &cached_attrs, N_("use .gitattributes only from the index")), + OPT_BOOLEAN(0 , "stdin", &stdin_paths, N_("read file names from stdin")), OPT_BOOLEAN('z', NULL, &null_term_line, - "input paths are terminated by a null character"), + N_("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 0723cf245e..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>"; /* - * Remove leading slashes and 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 = '/'; + 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 c16d82b7de..b1feda7d5e 100644 --- a/builtin/checkout-index.c +++ b/builtin/checkout-index.c @@ -123,7 +123,7 @@ static void checkout_all(const char *prefix, int prefix_length) } static const char * const builtin_checkout_index_usage[] = { - "git checkout-index [options] [--] [<file>...]", + N_("git checkout-index [options] [--] [<file>...]"), NULL }; @@ -184,27 +184,27 @@ int cmd_checkout_index(int argc, const char **argv, const char *prefix) int force = 0, quiet = 0, not_new = 0; struct option builtin_checkout_index_options[] = { OPT_BOOLEAN('a', "all", &all, - "checks out all files in the index"), - OPT__FORCE(&force, "forces overwrite of existing files"), + N_("check out all files in the index")), + OPT__FORCE(&force, N_("force overwrite of existing files")), OPT__QUIET(&quiet, - "no warning for existing files and files not in index"), + N_("no warning for existing files and files not in index")), OPT_BOOLEAN('n', "no-create", ¬_new, - "don't checkout new files"), + N_("don't checkout new files")), { OPTION_CALLBACK, 'u', "index", &newfd, NULL, - "update stat information in the index file", + N_("update stat information in the index file"), PARSE_OPT_NOARG, option_parse_u }, { OPTION_CALLBACK, 'z', NULL, NULL, NULL, - "paths are separated with NUL character", + N_("paths are separated with NUL character"), PARSE_OPT_NOARG, option_parse_z }, OPT_BOOLEAN(0, "stdin", &read_from_stdin, - "read list of paths from the standard input"), + N_("read list of paths from the standard input")), OPT_BOOLEAN(0, "temp", &to_tempfile, - "write the content to temporary files"), - OPT_CALLBACK(0, "prefix", NULL, "string", - "when creating files, prepend <string>", + N_("write the content to temporary files")), + OPT_CALLBACK(0, "prefix", NULL, N_("string"), + N_("when creating files, prepend <string>"), option_parse_prefix), OPT_CALLBACK(0, "stage", NULL, NULL, - "copy out the files from named stage", + N_("copy out the files from named stage"), option_parse_stage), OPT_END() }; diff --git a/builtin/checkout.c b/builtin/checkout.c index 4c20dae34d..a9c1b5a95f 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -19,23 +19,22 @@ #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>", - "git checkout [options] [<branch>] -- <file>...", + N_("git checkout [options] <branch>"), + N_("git checkout [options] [<branch>] -- <file>..."), NULL, }; struct checkout_opts { + int patch_mode; int quiet; int merge; int force; int force_detach; int writeout_stage; - int writeout_error; - - /* not set by parse_options */ - int branch_exists; + int overwrite_ignore; const char *new_branch; const char *new_branch_force; @@ -43,6 +42,11 @@ struct checkout_opts { int new_branch_log; enum branch_track track; struct diff_options diff_options; + + int branch_exists; + const char *prefix; + const char **pathspec; + struct tree *source_tree; }; static int post_checkout_hook(struct commit *old, struct commit *new, @@ -71,7 +75,8 @@ 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(0) | CE_UPDATE; + ce->ce_namelen = len; ce->ce_mode = create_ce_mode(mode); add_cache_entry(ce, ADD_CACHE_OK_TO_ADD | ADD_CACHE_OK_TO_REPLACE); return 0; @@ -113,16 +118,21 @@ static int check_stage(int stage, struct cache_entry *ce, int pos) 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; } @@ -149,18 +159,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 @@ -191,17 +210,15 @@ 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); + ce = make_cache_entry(mode, sha1, path, 2, 0); if (!ce) die(_("make_cache_entry failed for path '%s'"), path); status = checkout_entry(ce, state, NULL); return status; } -static int checkout_paths(struct tree *source_tree, const char **pathspec, - const char *prefix, struct checkout_opts *opts) +static int checkout_paths(const struct checkout_opts *opts, + const char *revision) { int pos; struct checkout state; @@ -213,35 +230,65 @@ static int checkout_paths(struct tree *source_tree, const char **pathspec, int stage = opts->writeout_stage; int merge = opts->merge; int newfd; - struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file)); + struct lock_file *lock_file; + + if (opts->track != BRANCH_TRACK_UNSPECIFIED) + die(_("'%s' cannot be used with updating paths"), "--track"); + + if (opts->new_branch_log) + die(_("'%s' cannot be used with updating paths"), "-l"); + + if (opts->force && opts->patch_mode) + die(_("'%s' cannot be used with updating paths"), "-f"); + + if (opts->force_detach) + die(_("'%s' cannot be used with updating paths"), "--detach"); + + if (opts->merge && opts->patch_mode) + die(_("'%s' cannot be used with %s"), "--merge", "--patch"); + + if (opts->force && opts->merge) + die(_("'%s' cannot be used with %s"), "-f", "-m"); + + if (opts->new_branch) + die(_("Cannot update paths and switch to branch '%s' at the same time."), + opts->new_branch); + + if (opts->patch_mode) + return run_add_interactive(revision, "--patch=checkout", + opts->pathspec); + + lock_file = xcalloc(1, sizeof(struct lock_file)); newfd = hold_locked_index(lock_file, 1); - if (read_cache_preload(pathspec) < 0) + if (read_cache_preload(opts->pathspec) < 0) return error(_("corrupt index file")); - if (source_tree) - read_tree_some(source_tree, pathspec); + if (opts->source_tree) + read_tree_some(opts->source_tree, opts->pathspec); - for (pos = 0; pathspec[pos]; pos++) + for (pos = 0; opts->pathspec[pos]; pos++) ; ps_matched = xcalloc(1, pos); for (pos = 0; pos < active_nr; pos++) { struct cache_entry *ce = active_cache[pos]; - match_pathspec(pathspec, ce->name, ce_namelen(ce), 0, ps_matched); + if (opts->source_tree && !(ce->ce_flags & CE_UPDATE)) + continue; + match_pathspec(opts->pathspec, ce->name, ce_namelen(ce), 0, ps_matched); } - if (report_path_error(ps_matched, pathspec, prefix)) + if (report_path_error(ps_matched, opts->pathspec, opts->prefix)) return 1; /* "checkout -m path" to recreate conflicted state */ if (opts->merge) - unmerge_cache(pathspec); + unmerge_cache(opts->pathspec); /* Any unmerged paths? */ for (pos = 0; pos < active_nr; pos++) { struct cache_entry *ce = active_cache[pos]; - if (match_pathspec(pathspec, ce->name, ce_namelen(ce), 0, NULL)) { + if (match_pathspec(opts->pathspec, ce->name, ce_namelen(ce), 0, NULL)) { if (!ce_stage(ce)) continue; if (opts->force) { @@ -249,7 +296,7 @@ static int checkout_paths(struct tree *source_tree, const char **pathspec, } 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); @@ -266,7 +313,9 @@ 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 (match_pathspec(pathspec, ce->name, ce_namelen(ce), 0, NULL)) { + if (opts->source_tree && !(ce->ce_flags & CE_UPDATE)) + continue; + if (match_pathspec(opts->pathspec, ce->name, ce_namelen(ce), 0, NULL)) { if (!ce_stage(ce)) { errs |= checkout_entry(ce, &state, NULL); continue; @@ -283,22 +332,22 @@ static int checkout_paths(struct tree *source_tree, const char **pathspec, commit_locked_index(lock_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); return errs; } -static void show_local_changes(struct object *head, struct diff_options *opts) +static void show_local_changes(struct object *head, + const struct diff_options *opts) { struct rev_info rev; /* I think we want full paths, even if we're in a subdirectory. */ init_revisions(&rev, NULL); 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")); + diff_setup_done(&rev.diffopt); add_pending_object(&rev, head, NULL); run_diff_index(&rev, 0); } @@ -313,7 +362,8 @@ static void describe_detached_head(const char *msg, struct commit *commit) strbuf_release(&sb); } -static int reset_tree(struct tree *tree, struct checkout_opts *o, int worktree) +static int reset_tree(struct tree *tree, const struct checkout_opts *o, + int worktree, int *writeout_error) { struct unpack_trees_options opts; struct tree_desc tree_desc; @@ -325,14 +375,14 @@ 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); init_tree_desc(&tree_desc, tree->buffer, tree->size); switch (unpack_trees(1, &tree_desc, &opts)) { case -2: - o->writeout_error = 1; + *writeout_error = 1; /* * We return 0 nevertheless, as the index is all right * and more importantly we have made best efforts to @@ -363,8 +413,10 @@ static void setup_branch_path(struct branch_info *branch) branch->path = strbuf_detach(&buf, NULL); } -static int merge_working_tree(struct checkout_opts *opts, - struct branch_info *old, struct branch_info *new) +static int merge_working_tree(const struct checkout_opts *opts, + struct branch_info *old, + struct branch_info *new, + int *writeout_error) { int ret; struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file)); @@ -375,7 +427,7 @@ static int merge_working_tree(struct checkout_opts *opts, resolve_undo_clear(); if (opts->force) { - ret = reset_tree(new->commit->tree, opts, 1); + ret = reset_tree(new->commit->tree, opts, 1, writeout_error); if (ret) return ret; } else { @@ -402,11 +454,13 @@ 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 : EMPTY_TREE_SHA1_BIN); @@ -459,7 +513,8 @@ static int merge_working_tree(struct checkout_opts *opts, o.verbosity = 0; work = write_tree_from_memory(&o); - ret = reset_tree(new->commit->tree, opts, 1); + ret = reset_tree(new->commit->tree, opts, 1, + writeout_error); if (ret) return ret; o.ancestor = old->name; @@ -467,7 +522,8 @@ static int merge_working_tree(struct checkout_opts *opts, o.branch2 = "local"; merge_trees(&o, new->commit->tree, work, old->commit->tree, &result); - ret = reset_tree(new->commit->tree, opts, 0); + ret = reset_tree(new->commit->tree, opts, 0, + writeout_error); if (ret) return ret; } @@ -494,21 +550,7 @@ 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, +static void update_refs_for_switch(const struct checkout_opts *opts, struct branch_info *old, struct branch_info *new) { @@ -535,7 +577,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); } @@ -553,15 +598,19 @@ static void update_refs_for_switch(struct checkout_opts *opts, REF_NODEREF, DIE_ON_ERR); if (!opts->quiet) { if (old->path && advice_detached_head) - detach_advice(old->path, new->name); + 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); + 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); @@ -588,35 +637,11 @@ static void update_refs_for_switch(struct checkout_opts *opts, report_tracking(new); } -struct rev_list_args { - int argc; - int alloc; - const char **argv; -}; - -static void add_one_rev_list_arg(struct rev_list_args *args, const char *s) -{ - ALLOC_GROW(args->argv, args->argc + 1, args->alloc); - args->argv[args->argc++] = s; -} - -static int add_one_ref_to_rev_list_arg(const char *refname, - const unsigned char *sha1, - int flags, - void *cb_data) -{ - add_one_rev_list_arg(cb_data, refname); - return 0; -} - -static int clear_commit_marks_from_one_ref(const char *refname, - const unsigned char *sha1, - int flags, - void *cb_data) +static int add_pending_uninteresting_ref(const char *refname, + const unsigned char *sha1, + int flags, void *cb_data) { - struct commit *commit = lookup_commit_reference_gently(sha1, 1); - if (commit) - clear_commit_marks(commit, -1); + add_pending_sha1(cb_data, refname, sha1, UNINTERESTING); return 0; } @@ -657,24 +682,25 @@ static void suggest_reattach(struct commit *commit, struct rev_info *revs) "Warning: you are leaving %d commit behind, " "not connected to\n" "any of your branches:\n\n" - "%s\n" - "If you want to keep it 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", + "%s\n", /* The plural version */ "Warning: you are leaving %d commits behind, " "not connected to\n" "any of your branches:\n\n" - "%s\n" - "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", + "%s\n", /* Give ngettext() the count */ lost), lost, - sb.buf, - sha1_to_hex(commit->object.sha1)); + 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)); } /* @@ -682,45 +708,48 @@ static void suggest_reattach(struct commit *commit, struct rev_info *revs) * 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 *commit) +static void orphaned_commit_warning(struct commit *old, struct commit *new) { - struct rev_list_args args = { 0, 0, NULL }; struct rev_info revs; - - add_one_rev_list_arg(&args, "(internal)"); - add_one_rev_list_arg(&args, sha1_to_hex(commit->object.sha1)); - add_one_rev_list_arg(&args, "--not"); - for_each_ref(add_one_ref_to_rev_list_arg, &args); - add_one_rev_list_arg(&args, "--"); - add_one_rev_list_arg(&args, NULL); + struct object *object = &old->object; + struct object_array refs; init_revisions(&revs, NULL); - if (setup_revisions(args.argc - 1, args.argv, &revs, NULL) != 1) - die(_("internal error: only -- alone should have been left")); + 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 (!(commit->object.flags & UNINTERESTING)) - suggest_reattach(commit, &revs); + if (!(old->object.flags & UNINTERESTING)) + suggest_reattach(old, &revs); else - describe_detached_head(_("Previous HEAD position was"), commit); + describe_detached_head(_("Previous HEAD position was"), old); - clear_commit_marks(commit, -1); - for_each_ref(clear_commit_marks_from_one_ref, NULL); + 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) +static int switch_branches(const 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; + int flag, writeout_error = 0; memset(&old, 0, sizeof(old)); - old.path = xstrdup(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)) { - free((char *)old.path); + if (!(flag & REF_ISSYMREF)) old.path = NULL; - } if (old.path && !prefixcmp(old.path, "refs/heads/")) old.name = old.path + strlen("refs/heads/"); @@ -733,18 +762,20 @@ static int switch_branches(struct checkout_opts *opts, struct branch_info *new) parse_commit(new->commit); } - ret = merge_working_tree(opts, &old, new); - if (ret) + ret = merge_working_tree(opts, &old, new, &writeout_error); + if (ret) { + free(path_to_free); return ret; + } if (!opts->quiet && !old.path && old.commit && new->commit != old.commit) - orphaned_commit_warning(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((char *)old.path); - return ret || opts->writeout_error; + free(path_to_free); + return ret || writeout_error; } static int git_checkout_config(const char *var, const char *value, void *cb) @@ -761,12 +792,6 @@ static int git_checkout_config(const char *var, const char *value, void *cb) return git_xmerge_config(var, value, NULL); } -static int interactive_checkout(const char *revision, const char **pathspec, - struct checkout_opts *opts) -{ - return run_add_interactive(revision, "--patch=checkout", pathspec); -} - struct tracking_name_data { const char *name; char *remote; @@ -881,8 +906,8 @@ static int parse_branchname_arg(int argc, const char **argv, new->name = arg; setup_branch_path(new); - if (check_ref_format(new->path) == CHECK_REF_FORMAT_OK && - resolve_ref(new->path, branch_rev, 1, NULL)) + 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 */ @@ -916,43 +941,104 @@ static int parse_branchname_arg(int argc, const char **argv, return argcount; } +static int switch_unborn_to_new_branch(const struct checkout_opts *opts) +{ + int status; + struct strbuf branch_ref = STRBUF_INIT; + + if (!opts->new_branch) + die(_("You are on a branch yet to be born")); + strbuf_addf(&branch_ref, "refs/heads/%s", opts->new_branch); + status = create_symref("HEAD", branch_ref.buf, "checkout -b"); + strbuf_release(&branch_ref); + if (!opts->quiet) + fprintf(stderr, _("Switched to a new branch '%s'\n"), + opts->new_branch); + return status; +} + +static int checkout_branch(struct checkout_opts *opts, + struct branch_info *new) +{ + if (opts->pathspec) + die(_("paths cannot be used with switching branches")); + + if (opts->patch_mode) + die(_("'%s' cannot be used with switching branches"), + "--patch"); + + if (opts->writeout_stage) + die(_("'%s' cannot be used with switching branches"), + "--ours/--theirs"); + + if (opts->force && opts->merge) + die(_("'%s' cannot be used with '%s'"), "-f", "-m"); + + if (opts->force_detach && opts->new_branch) + die(_("'%s' cannot be used with '%s'"), + "--detach", "-b/-B/--orphan"); + + if (opts->new_orphan_branch) { + if (opts->track != BRANCH_TRACK_UNSPECIFIED) + die(_("'%s' cannot be used with '%s'"), "--orphan", "-t"); + } else if (opts->force_detach) { + if (opts->track != BRANCH_TRACK_UNSPECIFIED) + die(_("'%s' cannot be used with '%s'"), "--detach", "-t"); + } else if (opts->track == BRANCH_TRACK_UNSPECIFIED) + opts->track = git_branch_track; + + if (new->name && !new->commit) + die(_("Cannot switch branch to a non-commit '%s'"), + new->name); + + 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); +} + int cmd_checkout(int argc, const char **argv, const char *prefix) { struct checkout_opts opts; - unsigned char rev[20]; 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, "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", + OPT__QUIET(&opts.quiet, N_("suppress progress reporting")), + OPT_STRING('b', NULL, &opts.new_branch, N_("branch"), + N_("create and checkout a new branch")), + OPT_STRING('B', NULL, &opts.new_branch_force, N_("branch"), + N_("create/reset and checkout a branch")), + OPT_BOOLEAN('l', NULL, &opts.new_branch_log, N_("create reflog for new branch")), + OPT_BOOLEAN(0, "detach", &opts.force_detach, N_("detach the HEAD at named commit")), + OPT_SET_INT('t', "track", &opts.track, N_("set upstream info for new branch"), BRANCH_TRACK_EXPLICIT), - OPT_STRING(0, "orphan", &opts.new_orphan_branch, "new branch", "new unparented branch"), - OPT_SET_INT('2', "ours", &opts.writeout_stage, "checkout our version for unmerged files", + OPT_STRING(0, "orphan", &opts.new_orphan_branch, N_("new branch"), N_("new unparented branch")), + OPT_SET_INT('2', "ours", &opts.writeout_stage, N_("checkout our version for unmerged files"), 2), - OPT_SET_INT('3', "theirs", &opts.writeout_stage, "checkout their version for unmerged files", + OPT_SET_INT('3', "theirs", &opts.writeout_stage, N_("checkout their version for unmerged files"), 3), - 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_STRING(0, "conflict", &conflict_style, "style", - "conflict style (merge or diff3)"), - OPT_BOOLEAN('p', "patch", &patch_mode, "select hunks interactively"), + OPT__FORCE(&opts.force, N_("force checkout (throw away local modifications)")), + OPT_BOOLEAN('m', "merge", &opts.merge, N_("perform a 3-way merge with the new branch")), + OPT_BOOLEAN(0, "overwrite-ignore", &opts.overwrite_ignore, N_("update ignored files (default)")), + OPT_STRING(0, "conflict", &conflict_style, N_("style"), + N_("conflict style (merge or diff3)")), + OPT_BOOLEAN('p', "patch", &opts.patch_mode, N_("select hunks interactively")), { OPTION_BOOLEAN, 0, "guess", &dwim_new_local_branch, NULL, - "second guess 'git checkout no-such-branch'", + N_("second guess 'git checkout no-such-branch'"), PARSE_OPT_NOARG | PARSE_OPT_HIDDEN }, OPT_END(), }; memset(&opts, 0, sizeof(opts)); memset(&new, 0, sizeof(new)); + opts.overwrite_ignore = 1; + opts.prefix = prefix; gitmodules_config(); git_config(git_checkout_config, &opts); @@ -962,26 +1048,27 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) argc = parse_options(argc, argv, prefix, options, checkout_usage, PARSE_OPT_KEEP_DASHDASH); - /* 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")); + if (conflict_style) { + opts.merge = 1; /* implied */ + git_xmerge_config("merge.conflictstyle", conflict_style, NULL); + } + + if ((!!opts.new_branch + !!opts.new_branch_force + !!opts.new_orphan_branch) > 1) + die(_("-b, -B and --orphan are mutually exclusive")); - /* copy -B over to -b, so that we can just check the latter */ + /* + * From here on, new_branch will contain the branch to be checked out, + * and new_branch_force and new_orphan_branch will tell us which one of + * -b/-B/--orphan is being used. + */ 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 - || 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")); + if (opts.new_orphan_branch) + opts.new_branch = opts.new_orphan_branch; - /* --track without -b should DWIM */ - if (0 < opts.track && !opts.new_branch) { + /* --track without -b/-B/--orphan should DWIM */ + if (opts.track != BRANCH_TRACK_UNSPECIFIED && !opts.new_branch) { const char *argv0 = argv[0]; if (!argc || !strcmp(argv0, "--")) die (_("--track needs a branch name")); @@ -995,22 +1082,6 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) opts.new_branch = argv0 + 1; } - if (opts.new_orphan_branch) { - if (opts.new_branch) - die(_("--orphan and -b|-B are mutually exclusive")); - if (opts.track > 0) - die(_("--orphan cannot be used with -t")); - opts.new_branch = opts.new_orphan_branch; - } - - if (conflict_style) { - opts.merge = 1; /* implied */ - git_xmerge_config("merge.conflictstyle", conflict_style, NULL); - } - - if (opts.force && opts.merge) - die(_("git checkout: -f and -m are incompatible")); - /* * Extract branch name from command line arguments, so * all that is left is pathspecs. @@ -1025,69 +1096,56 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) * remote branches, erroring out for invalid or ambiguous cases. */ if (argc) { + unsigned char rev[20]; int dwim_ok = - !patch_mode && + !opts.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); + &new, &opts.source_tree, + rev, &opts.new_branch); argv += n; argc -= n; } - if (opts.track == BRANCH_TRACK_UNSPECIFIED) - opts.track = git_branch_track; - if (argc) { - const char **pathspec = get_pathspec(prefix, argv); + opts.pathspec = get_pathspec(prefix, argv); - if (!pathspec) + if (!opts.pathspec) die(_("invalid path specification")); - if (patch_mode) - return interactive_checkout(new.name, pathspec, &opts); - - /* Checkout paths */ - if (opts.new_branch) { - if (argc == 1) { - die(_("git checkout: updating paths is incompatible with switching branches.\nDid you intend to checkout '%s' which can not be resolved as commit?"), argv[0]); - } else { - die(_("git checkout: updating paths is incompatible with switching branches.")); - } - } + /* + * Try to give more helpful suggestion. + * new_branch && argc > 1 will be caught later. + */ + if (opts.new_branch && argc == 1) + die(_("Cannot update paths and switch to branch '%s' at the same time.\n" + "Did you intend to checkout '%s' which can not be resolved as commit?"), + opts.new_branch, argv[0]); if (opts.force_detach) - die(_("git checkout: --detach does not take a path argument")); + die(_("git checkout: --detach does not take a path argument '%s'"), + argv[0]); if (1 < !!opts.writeout_stage + !!opts.force + !!opts.merge) - die(_("git checkout: --ours/--theirs, --force and --merge are incompatible when\nchecking out of the index.")); - - return checkout_paths(source_tree, pathspec, prefix, &opts); + die(_("git checkout: --ours/--theirs, --force and --merge are incompatible when\n" + "checking out of the index.")); } - if (patch_mode) - return interactive_checkout(new.name, NULL, &opts); - if (opts.new_branch) { struct strbuf buf = STRBUF_INIT; - if (strbuf_check_branch_ref(&buf, opts.new_branch)) - die(_("git checkout: we do not like '%s' as a branch name."), - opts.new_branch); - if (ref_exists(buf.buf)) { - opts.branch_exists = 1; - if (!opts.new_branch_force) - die(_("git checkout: branch %s already exists"), - opts.new_branch); - } - strbuf_release(&buf); - } - if (new.name && !new.commit) { - die(_("Cannot switch branch to a non-commit.")); + opts.branch_exists = + validate_new_branchname(opts.new_branch, &buf, + !!opts.new_branch_force, + !!opts.new_branch_force); + + strbuf_release(&buf); } - if (opts.writeout_stage) - die(_("--ours/--theirs is incompatible with switching branches.")); - return switch_branches(&opts, &new); + if (opts.patch_mode || opts.pathspec) + return checkout_paths(&opts, new.name); + else + return checkout_branch(&opts, &new); } diff --git a/builtin/clean.c b/builtin/clean.c index 75697f7111..f4b760bf3d 100644 --- a/builtin/clean.c +++ b/builtin/clean.c @@ -10,16 +10,23 @@ #include "cache.h" #include "dir.h" #include "parse-options.h" +#include "refs.h" #include "string-list.h" #include "quote.h" static int force = -1; /* unset */ static const char *const builtin_clean_usage[] = { - "git clean [-d] [-f] [-n] [-q] [-e <pattern>] [-x | -X] [--] <paths>...", + N_("git clean [-d] [-f] [-n] [-q] [-e <pattern>] [-x | -X] [--] <paths>..."), NULL }; +static const char *msg_remove = N_("Removing %s\n"); +static const char *msg_would_remove = N_("Would remove %s\n"); +static const char *msg_skip_git_dir = N_("Skipping repository %s\n"); +static const char *msg_would_skip_git_dir = N_("Would skip repository %s\n"); +static const char *msg_warn_remove_failed = N_("failed to remove %s"); + static int git_clean_config(const char *var, const char *value, void *cb) { if (!strcmp(var, "clean.requireforce")) @@ -34,11 +41,112 @@ static int exclude_cb(const struct option *opt, const char *arg, int unset) return 0; } +static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag, + int dry_run, int quiet, int *dir_gone) +{ + DIR *dir; + struct strbuf quoted = STRBUF_INIT; + struct dirent *e; + int res = 0, ret = 0, gone = 1, original_len = path->len, len, i; + unsigned char submodule_head[20]; + struct string_list dels = STRING_LIST_INIT_DUP; + + *dir_gone = 1; + + if ((force_flag & REMOVE_DIR_KEEP_NESTED_GIT) && + !resolve_gitlink_ref(path->buf, "HEAD", submodule_head)) { + if (!quiet) { + quote_path_relative(path->buf, strlen(path->buf), "ed, prefix); + printf(dry_run ? _(msg_would_skip_git_dir) : _(msg_skip_git_dir), + quoted.buf); + } + + *dir_gone = 0; + return 0; + } + + dir = opendir(path->buf); + if (!dir) { + /* an empty dir could be removed even if it is unreadble */ + res = dry_run ? 0 : rmdir(path->buf); + if (res) { + quote_path_relative(path->buf, strlen(path->buf), "ed, prefix); + warning(_(msg_warn_remove_failed), quoted.buf); + *dir_gone = 0; + } + return res; + } + + if (path->buf[original_len - 1] != '/') + strbuf_addch(path, '/'); + + len = path->len; + while ((e = readdir(dir)) != NULL) { + struct stat st; + if (is_dot_or_dotdot(e->d_name)) + continue; + + strbuf_setlen(path, len); + strbuf_addstr(path, e->d_name); + if (lstat(path->buf, &st)) + ; /* fall thru */ + else if (S_ISDIR(st.st_mode)) { + if (remove_dirs(path, prefix, force_flag, dry_run, quiet, &gone)) + ret = 1; + if (gone) { + quote_path_relative(path->buf, strlen(path->buf), "ed, prefix); + string_list_append(&dels, quoted.buf); + } else + *dir_gone = 0; + continue; + } else { + res = dry_run ? 0 : unlink(path->buf); + if (!res) { + quote_path_relative(path->buf, strlen(path->buf), "ed, prefix); + string_list_append(&dels, quoted.buf); + } else { + quote_path_relative(path->buf, strlen(path->buf), "ed, prefix); + warning(_(msg_warn_remove_failed), quoted.buf); + *dir_gone = 0; + ret = 1; + } + continue; + } + + /* path too long, stat fails, or non-directory still exists */ + *dir_gone = 0; + ret = 1; + break; + } + closedir(dir); + + strbuf_setlen(path, original_len); + + if (*dir_gone) { + res = dry_run ? 0 : rmdir(path->buf); + if (!res) + *dir_gone = 1; + else { + quote_path_relative(path->buf, strlen(path->buf), "ed, prefix); + warning(_(msg_warn_remove_failed), quoted.buf); + *dir_gone = 0; + ret = 1; + } + } + + if (!*dir_gone && !quiet) { + for (i = 0; i < dels.nr; i++) + printf(dry_run ? _(msg_would_remove) : _(msg_remove), dels.items[i].string); + } + string_list_clear(&dels, 0); + return ret; +} + int cmd_clean(int argc, const char **argv, const char *prefix) { - int i; - int show_only = 0, remove_directories = 0, quiet = 0, ignored = 0; - int ignored_only = 0, config_set = 0, errors = 0; + int i, res; + int dry_run = 0, remove_directories = 0, quiet = 0, ignored = 0; + int ignored_only = 0, config_set = 0, errors = 0, gone = 1; int rm_flags = REMOVE_DIR_KEEP_NESTED_GIT; struct strbuf directory = STRBUF_INIT; struct dir_struct dir; @@ -48,16 +156,16 @@ int cmd_clean(int argc, const char **argv, const char *prefix) const char *qname; char *seen = NULL; struct option options[] = { - OPT__QUIET(&quiet, "do not print names of files removed"), - OPT__DRY_RUN(&show_only, "dry run"), - OPT__FORCE(&force, "force"), + OPT__QUIET(&quiet, N_("do not print names of files removed")), + OPT__DRY_RUN(&dry_run, N_("dry run")), + OPT__FORCE(&force, N_("force")), OPT_BOOLEAN('d', NULL, &remove_directories, - "remove whole directories"), - { OPTION_CALLBACK, 'e', "exclude", &exclude_list, "pattern", - "exclude <pattern>", PARSE_OPT_NONEG, exclude_cb }, - OPT_BOOLEAN('x', NULL, &ignored, "remove ignored files, too"), + N_("remove whole directories")), + { OPTION_CALLBACK, 'e', "exclude", &exclude_list, N_("pattern"), + N_("add <pattern> to ignore rules"), PARSE_OPT_NONEG, exclude_cb }, + OPT_BOOLEAN('x', NULL, &ignored, N_("remove ignored files, too")), OPT_BOOLEAN('X', NULL, &ignored_only, - "remove only ignored files"), + N_("remove only ignored files")), OPT_END() }; @@ -77,7 +185,7 @@ int cmd_clean(int argc, const char **argv, const char *prefix) if (ignored && ignored_only) die(_("-x and -X cannot be used together")); - if (!show_only && !force) { + if (!dry_run && !force) { if (config_set) die(_("clean.requireForce set to true and neither -n nor -f given; " "refusing to clean")); @@ -98,7 +206,8 @@ int cmd_clean(int argc, const char **argv, const char *prefix) 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); @@ -148,38 +257,26 @@ int cmd_clean(int argc, const char **argv, const char *prefix) if (S_ISDIR(st.st_mode)) { strbuf_addstr(&directory, ent->name); - qname = quote_path_relative(directory.buf, directory.len, &buf, prefix); - if (show_only && (remove_directories || - (matches == MATCHED_EXACTLY))) { - printf(_("Would remove %s\n"), qname); - } else if (remove_directories || - (matches == MATCHED_EXACTLY)) { - if (!quiet) - printf(_("Removing %s\n"), qname); - if (remove_dir_recursively(&directory, - rm_flags) != 0) { - warning(_("failed to remove %s"), qname); + if (remove_directories || (matches == MATCHED_EXACTLY)) { + if (remove_dirs(&directory, prefix, rm_flags, dry_run, quiet, &gone)) errors++; + if (gone && !quiet) { + qname = quote_path_relative(directory.buf, directory.len, &buf, prefix); + printf(dry_run ? _(msg_would_remove) : _(msg_remove), qname); } - } else if (show_only) { - printf(_("Would not remove %s\n"), qname); - } else { - printf(_("Not removing %s\n"), qname); } strbuf_reset(&directory); } else { if (pathspec && !matches) continue; - qname = quote_path_relative(ent->name, -1, &buf, prefix); - if (show_only) { - printf(_("Would remove %s\n"), qname); - continue; - } else if (!quiet) { - printf(_("Removing %s\n"), qname); - } - if (unlink(ent->name) != 0) { - warning(_("failed to remove %s"), qname); + res = dry_run ? 0 : unlink(ent->name); + if (res) { + qname = quote_path_relative(ent->name, -1, &buf, prefix); + warning(_(msg_warn_remove_failed), qname); errors++; + } else if (!quiet) { + qname = quote_path_relative(ent->name, -1, &buf, prefix); + printf(dry_run ? _(msg_would_remove) : _(msg_remove), qname); } } } diff --git a/builtin/clone.c b/builtin/clone.c index 5f20082d6d..36ec99db3f 100644 --- a/builtin/clone.c +++ b/builtin/clone.c @@ -33,19 +33,20 @@ * */ static const char * const builtin_clone_usage[] = { - "git clone [options] [--] <repo> [<dir>]", + N_("git clone [options] [--] <repo> [<dir>]"), NULL }; -static int option_no_checkout, option_bare, option_mirror; -static int option_local, option_no_hardlinks, option_shared, option_recursive; +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) @@ -59,41 +60,44 @@ static int opt_parse_reference(const struct option *opt, const char *arg, int un 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, + N_("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"), + N_("don't create a checkout")), + OPT_BOOLEAN(0, "bare", &option_bare, N_("create a bare repository")), { OPTION_BOOLEAN, 0, "naked", &option_bare, NULL, - "create a bare repository", + N_("create a bare repository"), PARSE_OPT_NOARG | PARSE_OPT_HIDDEN }, OPT_BOOLEAN(0, "mirror", &option_mirror, - "create a mirror repository (implies bare)"), - OPT_BOOLEAN('l', "local", &option_local, - "to clone from a local repository"), + N_("create a mirror repository (implies bare)")), + OPT_BOOL('l', "local", &option_local, + N_("to clone from a local repository")), OPT_BOOLEAN(0, "no-hardlinks", &option_no_hardlinks, - "don't use local hardlinks, always copy"), + N_("don't use local hardlinks, always copy")), OPT_BOOLEAN('s', "shared", &option_shared, - "setup as shared repository"), + N_("setup as shared repository")), OPT_BOOLEAN(0, "recursive", &option_recursive, - "initialize submodules in the clone"), + N_("initialize submodules in the clone")), 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, "branch", - "use <branch> instead of 'origin' to track upstream"), - OPT_STRING('b', "branch", &option_branch, "branch", - "checkout <branch> instead of the remote's HEAD"), - OPT_STRING('u', "upload-pack", &option_upload_pack, "path", - "path to git-upload-pack on the remote"), - OPT_STRING(0, "depth", &option_depth, "depth", - "create a shallow clone of that depth"), - OPT_STRING(0, "separate-git-dir", &real_git_dir, "gitdir", - "separate git dir from working tree"), - + N_("initialize submodules in the clone")), + OPT_STRING(0, "template", &option_template, N_("template-directory"), + N_("directory from which templates will be used")), + OPT_CALLBACK(0 , "reference", &option_reference, N_("repo"), + N_("reference repository"), &opt_parse_reference), + OPT_STRING('o', "origin", &option_origin, N_("name"), + N_("use <name> instead of 'origin' to track upstream")), + OPT_STRING('b', "branch", &option_branch, N_("branch"), + N_("checkout <branch> instead of the remote's HEAD")), + OPT_STRING('u', "upload-pack", &option_upload_pack, N_("path"), + N_("path to git-upload-pack on the remote")), + OPT_STRING(0, "depth", &option_depth, N_("depth"), + N_("create a shallow clone of that depth")), + OPT_BOOL(0, "single-branch", &option_single_branch, + N_("clone only one branch, HEAD or --branch")), + OPT_STRING(0, "separate-git-dir", &real_git_dir, N_("gitdir"), + N_("separate git dir from working tree")), + OPT_STRING_LIST('c', "config", &option_config, N_("key=value"), + N_("set config inside the new repository")), OPT_END() }; @@ -103,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; @@ -113,7 +117,7 @@ static char *get_repo_path(const char *repo, int *is_bundle) path = mkpath("%s%s", repo, suffix[i]); if (stat(path, &st)) continue; - if (S_ISDIR(st.st_mode)) { + if (S_ISDIR(st.st_mode) && is_git_directory(path)) { *is_bundle = 0; return xstrdup(absolute_path(path)); } else if (S_ISREG(st.st_mode) && st.st_size > 8) { @@ -228,14 +232,11 @@ static int add_one_reference(struct string_list_item *item, void *cb_data) { char *ref_git; struct strbuf alternate = STRBUF_INIT; - struct remote *remote; - struct transport *transport; - const struct ref *extra; /* 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)); + char *ref_git_git = mkpathdup("%s/.git", ref_git); free(ref_git); ref_git = ref_git_git; } else if (!is_directory(mkpath("%s/objects", ref_git))) @@ -245,14 +246,6 @@ static int add_one_reference(struct string_list_item *item, void *cb_data) strbuf_addf(&alternate, "%s/objects", ref_git); add_to_alternates_file(alternate.buf); strbuf_release(&alternate); - - remote = remote_get(ref_git); - transport = transport_get(remote, ref_git); - for (extra = transport_get_remote_refs(transport); extra; - extra = extra->next) - add_extra_ref(extra->name, extra->old_sha1, 0); - - transport_disconnect(transport); free(ref_git); return 0; } @@ -349,7 +342,7 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest, if (!option_no_hardlinks) { if (!link(src->buf, dest->buf)) continue; - if (option_local) + if (option_local > 0) die_errno(_("failed to create link '%s'"), dest->buf); option_no_hardlinks = 1; } @@ -359,13 +352,8 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest, 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 remote *remote; - struct transport *transport; - if (option_shared) { struct strbuf alt = STRBUF_INIT; strbuf_addf(&alt, "%s/objects", src_repo); @@ -381,13 +369,8 @@ static const struct ref *clone_local(const char *src_repo, 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; } static const char *junk_work_tree; @@ -418,14 +401,57 @@ 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; + + if (option_single_branch) { + struct ref *remote_head = NULL; + + if (!option_branch) + remote_head = guess_remote_head(head, refs, 0); + else { + local_refs = NULL; + tail = &local_refs; + remote_head = copy_ref(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); - get_fetch_map(refs, refspec, &tail, 0); - if (!option_mirror) + if (!option_mirror && !option_single_branch) get_fetch_map(refs, tag_refspec, &tail, 0); return local_refs; @@ -435,11 +461,201 @@ 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"); + } +} + +static void write_refspec_config(const char* src_ref_prefix, + const struct ref* our_head_points_at, + const struct ref* remote_head_points_at, struct strbuf* branch_top) +{ + struct strbuf key = STRBUF_INIT; + struct strbuf value = STRBUF_INIT; + + if (option_mirror || !option_bare) { + if (option_single_branch && !option_mirror) { + if (option_branch) { + if (strstr(our_head_points_at->name, "refs/tags/")) + strbuf_addf(&value, "+%s:%s", our_head_points_at->name, + our_head_points_at->name); + else + strbuf_addf(&value, "+%s:%s%s", our_head_points_at->name, + branch_top->buf, option_branch); + } else if (remote_head_points_at) { + strbuf_addf(&value, "+%s:%s%s", remote_head_points_at->name, + branch_top->buf, + skip_prefix(remote_head_points_at->name, "refs/heads/")); + } + /* + * otherwise, the next "git fetch" will + * simply fetch from HEAD without updating + * any remote tracking branch, which is what + * we want. + */ + } else { + strbuf_addf(&value, "+%s*:%s*", src_ref_prefix, branch_top->buf); + } + /* Configure the remote */ + if (value.len) { + strbuf_addf(&key, "remote.%s.fetch", option_origin); + git_config_set_multivar(key.buf, value.buf, "^$", 0); + strbuf_reset(&key); + + if (option_mirror) { + strbuf_addf(&key, "remote.%s.mirror", option_origin); + git_config_set(key.buf, "true"); + strbuf_reset(&key); + } + } + } + + strbuf_release(&key); + strbuf_release(&value); } int cmd_clone(int argc, const char **argv, const char *prefix) @@ -453,11 +669,13 @@ 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; @@ -476,6 +694,9 @@ int cmd_clone(int argc, const char **argv, const char *prefix) 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; @@ -483,6 +704,8 @@ int cmd_clone(int argc, const char **argv, const char *prefix) if (option_origin) die(_("--bare and --origin %s options are incompatible."), option_origin); + if (real_git_dir) + die(_("--bare and --separate-git-dir are incompatible.")); option_no_checkout = 1; } @@ -498,7 +721,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix) 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.")); @@ -527,7 +750,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix) git_dir = xstrdup(dir); else { work_tree = dir; - git_dir = xstrdup(mkpath("%s/.git", dir)); + git_dir = mkpathdup("%s/.git", dir); } if (!option_bare) { @@ -535,7 +758,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix) if (safe_create_leading_directories_const(work_tree) < 0) die_errno(_("could not create leading directories of '%s'"), work_tree); - if (!dest_exists && mkdir(work_tree, 0755)) + if (!dest_exists && mkdir(work_tree, 0777)) die_errno(_("could not create work tree dir '%s'."), work_tree); set_git_work_tree(work_tree); @@ -550,16 +773,19 @@ int cmd_clone(int argc, const char **argv, const char *prefix) die(_("could not create leading directories of '%s'"), git_dir); set_git_dir_init(git_dir, real_git_dir, 0); - if (real_git_dir) + if (real_git_dir) { git_dir = real_git_dir; + junk_git_dir = real_git_dir; + } if (0 <= option_verbosity) { if (option_bare) - printf(_("Cloning into bare repository %s...\n"), dir); + printf(_("Cloning into bare repository '%s'...\n"), dir); else - printf(_("Cloning into %s...\n"), dir); + 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 @@ -581,20 +807,6 @@ int cmd_clone(int argc, const char **argv, const char *prefix) } strbuf_addf(&value, "+%s*:%s*", src_ref_prefix, branch_top.buf); - - if (option_mirror || !option_bare) { - /* Configure the remote */ - strbuf_addf(&key, "remote.%s.fetch", option_origin); - git_config_set_multivar(key.buf, value.buf, "^$", 0); - strbuf_reset(&key); - - if (option_mirror) { - strbuf_addf(&key, "remote.%s.mirror", option_origin); - git_config_set(key.buf, "true"); - strbuf_reset(&key); - } - } - strbuf_addf(&key, "remote.%s.url", option_origin); git_config_set(key.buf, repo); strbuf_reset(&key); @@ -607,13 +819,10 @@ int cmd_clone(int argc, const char **argv, const char *prefix) 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); @@ -622,49 +831,57 @@ int cmd_clone(int argc, const char **argv, const char *prefix) 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.")); + mapped_refs = NULL; our_head_points_at = NULL; remote_head_points_at = NULL; remote_head = NULL; @@ -674,84 +891,23 @@ 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); - } + write_refspec_config(src_ref_prefix, our_head_points_at, + remote_head_points_at, &branch_top); - if (our_head_points_at) { - /* Local default branch link */ - create_symref("HEAD", our_head_points_at->name, NULL); - if (!option_bare) { - const char *head = skip_prefix(our_head_points_at->name, - "refs/heads/"); - update_ref(reflog_msg.buf, "HEAD", - our_head_points_at->old_sha1, - NULL, 0, DIE_ON_ERR); - install_branch_config(0, head, option_origin, - our_head_points_at->name); - } - } else if (remote_head) { - /* Source had detached HEAD pointing somewhere. */ - if (!option_bare) { - update_ref(reflog_msg.buf, "HEAD", - remote_head->old_sha1, - NULL, REF_NODEREF, DIE_ON_ERR); - our_head_points_at = remote_head; - } - } else { - /* Nothing to checkout out */ - if (!option_no_checkout) - warning(_("remote HEAD refers to nonexistent ref, " - "unable to checkout.\n")); - option_no_checkout = 1; - } + if (is_local) + clone_local(path, git_dir); + else if (refs && complete_refs_before_fetch) + transport_fetch_refs(transport, mapped_refs); - if (transport) { - transport_unlock_pack(transport); - transport_disconnect(transport); - } + update_remote_refs(refs, mapped_refs, remote_head_points_at, + branch_top.buf, 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); - } + update_head(our_head_points_at, remote_head, reflog_msg.buf); + + 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..e125a55fc9 --- /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[] = { + N_("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, N_("name"), N_("lookup config vars")), + OPT_COLUMN(0, "mode", &colopts, N_("layout to use")), + OPT_INTEGER(0, "raw-mode", &colopts, N_("layout to use")), + OPT_INTEGER(0, "width", &copts.width, N_("Maximum width")), + OPT_STRING(0, "indent", &copts.indent, N_("string"), N_("Padding space on left border")), + OPT_INTEGER(0, "nl", &copts.nl, N_("Padding space on right border")), + OPT_INTEGER(0, "padding", &copts.padding, N_("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..eac901a0ee 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,95 @@ 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); + 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_commit(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 (get_sha1(b, sha1)) - die("Not a valid object name %s", b); - assert_sha1_type(sha1, OBJ_COMMIT); - new_parent(lookup_commit(sha1), &parents); + 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_tree(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 9679a99f99..96684108e7 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -26,14 +26,17 @@ #include "unpack-trees.h" #include "quote.h" #include "submodule.h" +#include "gpg-interface.h" +#include "column.h" +#include "sequencer.h" static const char * const builtin_commit_usage[] = { - "git commit [options] [--] <filepattern>...", + N_("git commit [options] [--] <pathspec>..."), NULL }; static const char * const builtin_status_usage[] = { - "git status [options] [--] <filepattern>...", + N_("git status [options] [--] <pathspec>..."), NULL }; @@ -62,8 +65,6 @@ N_("The previous cherry-pick is now empty, possibly due to conflict resolution.\ "\n" "Otherwise, please use 'git reset'\n"); -static unsigned char head_sha1[20]; - static const char *use_message_buffer; static const char commit_editmsg[] = "COMMIT_EDITMSG"; static struct lock_file index_lock; /* real index */ @@ -83,10 +84,13 @@ static const char *template_file; static const char *author_message, *author_message_buffer; static char *edit_message, *use_message; static char *fixup_message, *squash_message; -static int all, edit_flag, also, interactive, patch_interactive, only, amend, signoff; +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 @@ -102,18 +106,17 @@ static enum { static char *cleanup_arg; static enum commit_whence whence; -static int use_editor = 1, initial_commit, include_status = 1; +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_NONE = 0, STATUS_FORMAT_LONG, STATUS_FORMAT_SHORT, STATUS_FORMAT_PORCELAIN -} status_format = STATUS_FORMAT_LONG; -static int status_show_branch; +} status_format; static int opt_parse_m(const struct option *opt, const char *arg, int unset) { @@ -127,57 +130,6 @@ 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, "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_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('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", &status_show_branch, "show branch information"), - OPT_SET_INT(0, "porcelain", &status_format, - "machine-readable output", 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"))) @@ -190,24 +142,6 @@ static void determine_whence(struct wt_status *s) s->whence = whence; } -static const char *whence_s(void) -{ - char *s = ""; - - switch (whence) { - case FROM_COMMIT: - break; - case FROM_MERGE: - s = "merge"; - break; - case FROM_CHERRY_PICK: - s = "cherry-pick"; - break; - } - - return s; -} - static void rollback_index_files(void) { switch (commit_style) { @@ -252,12 +186,18 @@ static int list_paths(struct string_list *list, const char *with_tree, int i; char *m; + if (!pattern) + return 0; + for (i = 0; pattern[i]; i++) ; 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]; @@ -294,13 +234,13 @@ static void add_remove_files(struct string_list *list) } } -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; } @@ -313,7 +253,7 @@ 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")); parse_tree(tree); @@ -332,7 +272,8 @@ 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; @@ -392,6 +333,7 @@ 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")); @@ -408,10 +350,11 @@ static char *prepare_index(int argc, const char **argv, const char *prefix, int * and create commit from the_index. * We still need to refresh the index here. */ - if (!pathspec || !*pathspec) { + if (!only && (!pathspec || !*pathspec)) { 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")); @@ -443,12 +386,16 @@ static char *prepare_index(int argc, const char **argv, const char *prefix, int */ commit_style = COMMIT_PARTIAL; - if (whence != FROM_COMMIT) - die(_("cannot do a partial commit during a %s."), whence_s()); + 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(); @@ -467,7 +414,7 @@ static char *prepare_index(int argc, const char **argv, const char *prefix, int (uintmax_t) getpid()), LOCK_DIE_ON_ERROR); - create_base_index(); + create_base_index(current_head); add_remove_files(&partial); refresh_cache(REFRESH_QUIET); @@ -503,11 +450,12 @@ 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_NONE: case STATUS_FORMAT_LONG: wt_status_print(s); break; @@ -516,19 +464,39 @@ 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 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 const char sign_off_header[] = "Signed-off-by: "; +static int sane_ident_split(struct ident_split *person) +{ + if (!person->name_begin || !person->name_end || + person->name_begin == person->name_end) + return 0; /* no human readable name */ + if (!person->mail_begin || !person->mail_end || + person->mail_begin == person->mail_end) + return 0; /* no usable mail */ + if (!person->date_begin || !person->date_end || + !person->tz_begin || !person->tz_end) + return 0; + return 1; +} 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"); @@ -536,6 +504,7 @@ static void determine_author_info(struct strbuf *author_ident) if (author_message) { const char *a, *lb, *rb, *eol; + size_t len; a = strstr(author_message_buffer, "\nauthor "); if (!a) @@ -556,6 +525,11 @@ static void determine_author_info(struct strbuf *author_ident) (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) { @@ -570,49 +544,13 @@ static void determine_author_info(struct strbuf *author_ident) if (force_date) date = force_date; - strbuf_addstr(author_ident, fmt_ident(name, email, date, - IDENT_ERROR_ON_NO_NAME)); -} - -static int ends_rfc2822_footer(struct strbuf *sb) -{ - int ch; - int hit = 0; - int i, j, k; - int len = sb->len; - int first = 1; - const char *buf = sb->buf; - - for (i = len - 1; i > 0; i--) { - if (hit && buf[i] == '\n') - break; - hit = (buf[i] == '\n'); - } - - while (i < len - 1 && buf[i] == '\n') - i++; - - for (; i < len; i = k) { - for (k = i; k < len && buf[k] != '\n'; k++) - ; /* do nothing */ - k++; - - if ((buf[k] == ' ' || buf[k] == '\t') && !first) - continue; - - first = 0; - - for (j = 0; i + j < len; j++) { - ch = buf[i + j]; - if (ch == ':') - break; - if (isalnum(ch) || - (ch == '-')) - continue; - return 0; - } + strbuf_addstr(author_ident, fmt_ident(name, email, date, IDENT_STRICT)); + if (!split_ident_line(&author, author_ident->buf, author_ident->len) && + sane_ident_split(&author)) { + 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, '@'); } - return 1; } static char *cut_ident_timestamp_part(char *string) @@ -625,6 +563,7 @@ static char *cut_ident_timestamp_part(char *string) } static int prepare_to_commit(const char *index_file, const char *prefix, + struct commit *current_head, struct wt_status *s, struct strbuf *author_ident) { @@ -638,6 +577,9 @@ static int prepare_to_commit(const char *index_file, const char *prefix, 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; @@ -676,7 +618,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix, hook_arg1 = "message"; } else if (use_message) { buffer = strstr(use_message_buffer, "\n\n"); - if (!buffer || buffer[2] == '\0') + if (!use_editor && (!buffer || buffer[2] == '\0')) die(_("commit has empty message")); strbuf_add(&sb, buffer + 2, strlen(buffer + 2)); hook_arg1 = "commit"; @@ -735,21 +677,30 @@ static int prepare_to_commit(const char *index_file, const char *prefix, stripspace(&sb, 0); if (signoff) { - struct strbuf sob = STRBUF_INIT; - int i; - - strbuf_addstr(&sob, sign_off_header); - strbuf_addstr(&sob, fmt_name(getenv("GIT_COMMITTER_NAME"), - getenv("GIT_COMMITTER_EMAIL"))); - strbuf_addch(&sob, '\n'); - for (i = sb.len - 1; i > 0 && sb.buf[i - 1] != '\n'; i--) - ; /* do nothing */ - if (prefixcmp(sb.buf + i, sob.buf)) { - if (!i || !ends_rfc2822_footer(&sb)) - strbuf_addch(&sb, '\n'); - strbuf_addbuf(&sb, &sob); + /* + * See if we have a Conflicts: block at the end. If yes, count + * its size, so we can ignore it. + */ + int ignore_footer = 0; + int i, eol, previous = 0; + const char *nl; + + for (i = 0; i < sb.len; i++) { + nl = memchr(sb.buf + i, '\n', sb.len - i); + if (nl) + eol = nl - sb.buf; + else + eol = sb.len; + if (!prefixcmp(sb.buf + previous, "\nConflicts:\n")) { + ignore_footer = sb.len - previous; + break; + } + while (i < eol) + i++; + previous = eol; } - strbuf_release(&sob); + + append_signoff(&sb, ignore_footer); } if (fwrite(sb.buf, 1, sb.len, s->fp) < sb.len) @@ -757,37 +708,37 @@ static int prepare_to_commit(const char *index_file, const char *prefix, strbuf_release(&sb); - /* This checks and barfs if author is badly specified */ - determine_author_info(author_ident); - /* This checks if committer ident is explicitly given */ - strbuf_addstr(&committer_ident, git_committer_info(0)); + strbuf_addstr(&committer_ident, git_committer_info(IDENT_STRICT)); if (use_editor && include_status) { char *ai_tmp, *ci_tmp; if (whence != FROM_COMMIT) status_printf_ln(s, GIT_COLOR_NORMAL, - _("\n" - "It looks like you may be committing a %s.\n" - "If this is not correct, please remove the file\n" - " %s\n" - "and try again.\n" - ""), - whence_s(), + 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"); - status_printf(s, GIT_COLOR_NORMAL, - _("Please enter the commit message for your changes.")); if (cleanup_mode == CLEANUP_ALL) - status_printf_more(s, GIT_COLOR_NORMAL, - _(" Lines starting\n" - "with '#' will be ignored, and an empty" + 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. */ - status_printf_more(s, GIT_COLOR_NORMAL, - _(" Lines starting\n" + 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" " yourself if you want to.\n" "An empty message aborts the commit.\n")); @@ -804,7 +755,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix, ident_shown++ ? "" : "\n", author_ident->buf); - if (!user_ident_sufficiently_given()) + if (!committer_ident_sufficiently_given()) status_printf_ln(s, GIT_COLOR_NORMAL, _("%s" "Committer: %s"), @@ -846,7 +797,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix, * empty due to conflict resolution, which the user should okay. */ if (!commitable && whence != FROM_MERGE && !allow_empty && - !(amend && is_a_merge(head_sha1))) { + !(amend && is_a_merge(current_head))) { run_status(stdout, index_file, prefix, 0, s); if (amend) fputs(_(empty_amend_advice), stderr); @@ -862,10 +813,7 @@ 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) { + if (update_main_cache_tree(0)) { error(_("Error building trees")); return 0; } @@ -894,27 +842,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++) { @@ -937,6 +868,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; @@ -1002,14 +967,15 @@ static const char *read_commit_message(const char *name) } 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); @@ -1019,19 +985,20 @@ static int parse_and_validate_options(int argc, const char *argv[], 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) + if (amend && !current_head) die(_("You have nothing to amend.")); - if (amend && whence != FROM_COMMIT) - die(_("You are in the middle of a %s -- cannot amend."), whence_s()); + 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) @@ -1046,6 +1013,8 @@ static int parse_and_validate_options(int argc, const char *argv[], 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/--fixup."))); + if (f || message.len) + template_file = NULL; if (edit_message) use_message = edit_message; if (amend && !use_message && !fixup_message) @@ -1091,21 +1060,25 @@ static int parse_and_validate_options(int argc, const char *argv[], if (all && argc > 0) die(_("Paths with -a does not make sense.")); - if (null_termination && status_format == STATUS_FORMAT_LONG) - status_format = STATUS_FORMAT_PORCELAIN; - if (status_format != STATUS_FORMAT_LONG) + if (s->null_termination) { + if (status_format == STATUS_FORMAT_NONE) + status_format = STATUS_FORMAT_PORCELAIN; + else if (status_format == STATUS_FORMAT_LONG) + die(_("--long and -z are incompatible")); + } + if (status_format != STATUS_FORMAT_NONE) dry_run = 1; return argc; } 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(); @@ -1136,6 +1109,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); @@ -1144,7 +1119,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.")) { @@ -1178,29 +1153,33 @@ 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, "be verbose"), + OPT__VERBOSE(&verbose, N_("be verbose")), OPT_SET_INT('s', "short", &status_format, - "show status concisely", STATUS_FORMAT_SHORT), - OPT_BOOLEAN('b', "branch", &status_show_branch, - "show branch information"), + N_("show status concisely"), STATUS_FORMAT_SHORT), + OPT_BOOLEAN('b', "branch", &s.show_branch, + N_("show branch information")), OPT_SET_INT(0, "porcelain", &status_format, - "machine-readable output", + N_("machine-readable output"), STATUS_FORMAT_PORCELAIN), - OPT_BOOLEAN('z', "null", &null_termination, - "terminate entries with NUL"), + OPT_SET_INT(0, "long", &status_format, + N_("show status in long format (default)"), + STATUS_FORMAT_LONG), + OPT_BOOLEAN('z', "null", &s.null_termination, + N_("terminate entries with NUL")), { OPTION_STRING, 'u', "untracked-files", &untracked_files_arg, - "mode", - "show untracked files, optional modes: all, normal, no. (Default: all)", + N_("mode"), + N_("show untracked files, optional modes: all, normal, no. (Default: all)"), PARSE_OPT_OPTARG, NULL, (intptr_t)"all" }, OPT_BOOLEAN(0, "ignored", &show_ignored_in_status, - "show ignored files"), - { OPTION_STRING, 0, "ignore-submodules", &ignore_submodule_arg, "when", - "ignore changes to submodules, optional when: all, dirty, untracked. (Default: all)", + N_("show ignored files")), + { OPTION_STRING, 0, "ignore-submodules", &ignore_submodule_arg, N_("when"), + N_("ignore changes to submodules, optional when: all, dirty, untracked. (Default: all)"), PARSE_OPT_OPTARG, NULL, (intptr_t)"all" }, + OPT_COLUMN(0, "column", &s.colopts, N_("list untracked files in columns")), OPT_END(), }; @@ -1214,9 +1193,14 @@ int cmd_status(int argc, const char **argv, const char *prefix) argc = parse_options(argc, argv, prefix, builtin_status_options, builtin_status_usage, 0); + finalize_colopts(&s.colopts, -1); - if (null_termination && status_format == STATUS_FORMAT_LONG) - status_format = STATUS_FORMAT_PORCELAIN; + if (s.null_termination) { + if (status_format == STATUS_FORMAT_NONE) + status_format = STATUS_FORMAT_PORCELAIN; + else if (status_format == STATUS_FORMAT_LONG) + die(_("--long and -z are incompatible")); + } handle_untracked_files_arg(&s); if (show_ignored_in_status) @@ -1237,18 +1221,15 @@ int cmd_status(int argc, const char **argv, const char *prefix) 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_NONE: case STATUS_FORMAT_LONG: s.verbose = verbose; s.ignore_submodule_arg = ignore_submodule_arg; @@ -1258,13 +1239,14 @@ 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; @@ -1283,7 +1265,7 @@ static void print_summary(const char *prefix, const unsigned char *sha1) strbuf_addstr(&format, "\n Author: "); strbuf_addbuf_percentquote(&format, &author_ident); } - if (!user_ident_sufficiently_given()) { + if (!committer_ident_sufficiently_given()) { strbuf_addstr(&format, "\n Committer: "); strbuf_addbuf_percentquote(&format, &committer_ident); if (advice_implicit_identity) { @@ -1309,6 +1291,7 @@ static void print_summary(const char *prefix, const unsigned char *sha1) 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 : @@ -1329,6 +1312,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); @@ -1337,6 +1321,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); } @@ -1376,58 +1363,117 @@ 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, N_("suppress summary after successful commit")), + OPT__VERBOSE(&verbose, N_("show diff in commit message template")), + + OPT_GROUP(N_("Commit message options")), + OPT_FILENAME('F', "file", &logfile, N_("read message from file")), + OPT_STRING(0, "author", &force_author, N_("author"), N_("override author for commit")), + OPT_STRING(0, "date", &force_date, N_("date"), N_("override date for commit")), + OPT_CALLBACK('m', "message", &message, N_("message"), N_("commit message"), opt_parse_m), + OPT_STRING('c', "reedit-message", &edit_message, N_("commit"), N_("reuse and edit message from specified commit")), + OPT_STRING('C', "reuse-message", &use_message, N_("commit"), N_("reuse message from specified commit")), + OPT_STRING(0, "fixup", &fixup_message, N_("commit"), N_("use autosquash formatted message to fixup specified commit")), + OPT_STRING(0, "squash", &squash_message, N_("commit"), N_("use autosquash formatted message to squash specified commit")), + OPT_BOOLEAN(0, "reset-author", &renew_authorship, N_("the commit is authored by me now (used with -C/-c/--amend)")), + OPT_BOOLEAN('s', "signoff", &signoff, N_("add Signed-off-by:")), + OPT_FILENAME('t', "template", &template_file, N_("use specified template file")), + OPT_BOOL('e', "edit", &edit_flag, N_("force edit of commit")), + OPT_STRING(0, "cleanup", &cleanup_arg, N_("default"), N_("how to strip spaces and #comments from message")), + OPT_BOOLEAN(0, "status", &include_status, N_("include status in commit message template")), + { OPTION_STRING, 'S', "gpg-sign", &sign_commit, N_("key id"), + N_("GPG sign commit"), PARSE_OPT_OPTARG, NULL, (intptr_t) "" }, + /* end commit message options */ + + OPT_GROUP(N_("Commit contents options")), + OPT_BOOLEAN('a', "all", &all, N_("commit all changed files")), + OPT_BOOLEAN('i', "include", &also, N_("add specified files to index for commit")), + OPT_BOOLEAN(0, "interactive", &interactive, N_("interactively add files")), + OPT_BOOLEAN('p', "patch", &patch_interactive, N_("interactively add changes")), + OPT_BOOLEAN('o', "only", &only, N_("commit only specified files")), + OPT_BOOLEAN('n', "no-verify", &no_verify, N_("bypass pre-commit hook")), + OPT_BOOLEAN(0, "dry-run", &dry_run, N_("show what would be committed")), + OPT_SET_INT(0, "short", &status_format, N_("show status concisely"), + STATUS_FORMAT_SHORT), + OPT_BOOLEAN(0, "branch", &s.show_branch, N_("show branch information")), + OPT_SET_INT(0, "porcelain", &status_format, + N_("machine-readable output"), STATUS_FORMAT_PORCELAIN), + OPT_SET_INT(0, "long", &status_format, + N_("show status in long format (default)"), + STATUS_FORMAT_LONG), + OPT_BOOLEAN('z', "null", &s.null_termination, + N_("terminate entries with NUL")), + OPT_BOOLEAN(0, "amend", &amend, N_("amend previous commit")), + OPT_BOOLEAN(0, "no-post-rewrite", &no_post_rewrite, N_("bypass post-rewrite hook")), + { OPTION_STRING, 'u', "untracked-files", &untracked_files_arg, N_("mode"), N_("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, + N_("ok to record an empty change"), + PARSE_OPT_NOARG | PARSE_OPT_HIDDEN }, + { OPTION_BOOLEAN, 0, "allow-empty-message", &allow_empty_message, NULL, + N_("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); + gitmodules_config(); git_config(git_commit_config, &s); determine_whence(&s); + s.colopts = 0; - 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); + 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, &author_ident)) { + 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 (whence == FROM_MERGE) { struct strbuf m = STRBUF_INIT; @@ -1435,16 +1481,18 @@ int cmd_commit(int argc, const char **argv, const char *prefix) 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"), git_path("MERGE_HEAD")); while (strbuf_getline(&m, fp, '\n') != EOF) { - unsigned char sha1[20]; - if (get_sha1_hex(m.buf, sha1) < 0) + struct commit *parent; + + parent = get_merge_parent(m.buf); + if (!parent) die(_("Corrupt MERGE_HEAD file (%s)"), m.buf); - pptr = &commit_list_insert(lookup_commit(sha1), pptr)->next; + pptr = &commit_list_insert(parent, pptr)->next; } fclose(fp); strbuf_release(&m); @@ -1461,7 +1509,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix) reflog_msg = (whence == FROM_CHERRY_PICK) ? "commit (cherry-pick)" : "commit"; - pptr = &commit_list_insert(lookup_commit(head_sha1), pptr)->next; + pptr = &commit_list_insert(current_head, pptr)->next; } /* Finally, get the commit message */ @@ -1481,21 +1529,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")); exit(1); } - if (commit_tree(sb.buf, active_cache_tree->sha1, parents, commit_sha1, - author_ident.buf)) { + 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")); } 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'); @@ -1510,12 +1574,13 @@ int cmd_commit(int argc, const char **argv, const char *prefix) rollback_index_files(); 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")); } 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")); @@ -1532,13 +1597,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 211e118d57..33c9bf9d84 100644 --- a/builtin/config.c +++ b/builtin/config.c @@ -4,7 +4,7 @@ #include "parse-options.h" static const char *const builtin_config_usage[] = { - "git config [options]", + N_("git config [options]"), NULL }; @@ -15,7 +15,6 @@ static int show_keys; static int use_key_regexp; static int do_all; static int do_not_match; -static int seen; static char delim = '='; static char key_delim = ' '; static char term = '\n'; @@ -25,6 +24,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) @@ -48,32 +48,33 @@ static int end_null; #define TYPE_PATH (1<<3) static struct option builtin_config_options[] = { - OPT_GROUP("Config file location"), - OPT_BOOLEAN(0, "global", &use_global_config, "use global config file"), - OPT_BOOLEAN(0, "system", &use_system_config, "use system config file"), - OPT_BOOLEAN(0, "local", &use_local_config, "use repository config file"), - OPT_STRING('f', "file", &given_config_file, "file", "use given config file"), - OPT_GROUP("Action"), - OPT_BIT(0, "get", &actions, "get value: name [value-regex]", ACTION_GET), - OPT_BIT(0, "get-all", &actions, "get all values: key [value-regex]", ACTION_GET_ALL), - OPT_BIT(0, "get-regexp", &actions, "get values for regexp: name-regex [value-regex]", ACTION_GET_REGEXP), - OPT_BIT(0, "replace-all", &actions, "replace all matching variables: name value [value_regex]", ACTION_REPLACE_ALL), - OPT_BIT(0, "add", &actions, "adds a new variable: name value", ACTION_ADD), - OPT_BIT(0, "unset", &actions, "removes a variable: name [value-regex]", ACTION_UNSET), - OPT_BIT(0, "unset-all", &actions, "removes all matches: name [value-regex]", ACTION_UNSET_ALL), - OPT_BIT(0, "rename-section", &actions, "rename section: old-name new-name", ACTION_RENAME_SECTION), - OPT_BIT(0, "remove-section", &actions, "remove a section: name", ACTION_REMOVE_SECTION), - OPT_BIT('l', "list", &actions, "list all", ACTION_LIST), - OPT_BIT('e', "edit", &actions, "opens an editor", ACTION_EDIT), - OPT_STRING(0, "get-color", &get_color_slot, "slot", "find the color configured: [default]"), - OPT_STRING(0, "get-colorbool", &get_colorbool_slot, "slot", "find the color setting: [stdout-is-tty]"), - OPT_GROUP("Type"), - OPT_BIT(0, "bool", &types, "value is \"true\" or \"false\"", TYPE_BOOL), - OPT_BIT(0, "int", &types, "value is decimal number", TYPE_INT), - OPT_BIT(0, "bool-or-int", &types, "value is --bool or --int", TYPE_BOOL_OR_INT), - OPT_BIT(0, "path", &types, "value is a path (file or directory name)", TYPE_PATH), - OPT_GROUP("Other"), - OPT_BOOLEAN('z', "null", &end_null, "terminate values with NUL byte"), + OPT_GROUP(N_("Config file location")), + OPT_BOOLEAN(0, "global", &use_global_config, N_("use global config file")), + OPT_BOOLEAN(0, "system", &use_system_config, N_("use system config file")), + OPT_BOOLEAN(0, "local", &use_local_config, N_("use repository config file")), + OPT_STRING('f', "file", &given_config_file, N_("file"), N_("use given config file")), + OPT_GROUP(N_("Action")), + OPT_BIT(0, "get", &actions, N_("get value: name [value-regex]"), ACTION_GET), + OPT_BIT(0, "get-all", &actions, N_("get all values: key [value-regex]"), ACTION_GET_ALL), + OPT_BIT(0, "get-regexp", &actions, N_("get values for regexp: name-regex [value-regex]"), ACTION_GET_REGEXP), + OPT_BIT(0, "replace-all", &actions, N_("replace all matching variables: name value [value_regex]"), ACTION_REPLACE_ALL), + OPT_BIT(0, "add", &actions, N_("add a new variable: name value"), ACTION_ADD), + OPT_BIT(0, "unset", &actions, N_("remove a variable: name [value-regex]"), ACTION_UNSET), + OPT_BIT(0, "unset-all", &actions, N_("remove all matches: name [value-regex]"), ACTION_UNSET_ALL), + OPT_BIT(0, "rename-section", &actions, N_("rename section: old-name new-name"), ACTION_RENAME_SECTION), + OPT_BIT(0, "remove-section", &actions, N_("remove a section: name"), ACTION_REMOVE_SECTION), + OPT_BIT('l', "list", &actions, N_("list all"), ACTION_LIST), + OPT_BIT('e', "edit", &actions, N_("open an editor"), ACTION_EDIT), + OPT_STRING(0, "get-color", &get_color_slot, N_("slot"), N_("find the color configured: [default]")), + OPT_STRING(0, "get-colorbool", &get_colorbool_slot, N_("slot"), N_("find the color setting: [stdout-is-tty]")), + OPT_GROUP(N_("Type")), + OPT_BIT(0, "bool", &types, N_("value is \"true\" or \"false\""), TYPE_BOOL), + OPT_BIT(0, "int", &types, N_("value is decimal number"), TYPE_INT), + OPT_BIT(0, "bool-or-int", &types, N_("value is --bool or --int"), TYPE_BOOL_OR_INT), + OPT_BIT(0, "path", &types, N_("value is a path (file or directory name)"), TYPE_PATH), + OPT_GROUP(N_("Other")), + OPT_BOOLEAN('z', "null", &end_null, N_("terminate values with NUL byte")), + OPT_BOOL(0, "includes", &respect_includes, N_("respect include directives on lookup")), OPT_END(), }; @@ -93,12 +94,20 @@ static int show_all_config(const char *key_, const char *value_, void *cb) return 0; } -static int show_config(const char *key_, const char *value_, void *cb) +struct strbuf_list { + struct strbuf *items; + int nr; + int alloc; +}; + +static int collect_config(const char *key_, const char *value_, void *cb) { + struct strbuf_list *values = cb; + struct strbuf *buf; char value[256]; 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; @@ -108,14 +117,14 @@ static int show_config(const char *key_, const char *value_, void *cb) (do_not_match ^ !!regexec(regexp, (value_?value_:""), 0, NULL, 0))) return 0; + ALLOC_GROW(values->items, values->nr + 1, values->alloc); + buf = &values->items[values->nr++]; + strbuf_init(buf, 0); + if (show_keys) { - if (value_) - printf("%s%c", key_, key_delim); - else - printf("%s", key_); + strbuf_addstr(buf, key_); + must_print_delim = 1; } - if (seen && !do_all) - dup_error = 1; if (types == TYPE_INT) sprintf(value, "%d", git_config_int(key_, value_?value_:"")); else if (types == TYPE_BOOL) @@ -128,18 +137,22 @@ static int show_config(const char *key_, const char *value_, void *cb) else sprintf(value, "%d", v); } else if (types == TYPE_PATH) { - git_config_pathname(&vptr, key_, value_); + if (git_config_pathname(&vptr, key_, value_) < 0) + return -1; 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 - printf("%s%c", vptr, term); + + if (must_print_delim) + strbuf_addch(buf, key_delim); + strbuf_addstr(buf, vptr); + strbuf_addch(buf, 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 @@ -152,19 +165,9 @@ 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 *global = NULL, *repo_config = NULL; - const char *system_wide = NULL, *local; - - local = config_exclusive_filename; - if (!local) { - const char *home = getenv("HOME"); - local = repo_config = git_pathdup("config"); - if (home) - global = xstrdup(mkpath("%s/.gitconfig", home)); - if (git_config_system()) - system_wide = git_etc_gitconfig(); - } + int ret = CONFIG_GENERIC_ERROR; + struct strbuf_list values = {NULL}; + int i; if (use_key_regexp) { char *tl; @@ -186,12 +189,16 @@ static int get_value(const char *key_, const char *regex_) 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); + free(key_regexp); + key_regexp = NULL; + ret = CONFIG_INVALID_PATTERN; goto free_strings; } } else { - if (git_config_parse_key(key_, &key, NULL)) + if (git_config_parse_key(key_, &key, NULL)) { + ret = CONFIG_INVALID_KEY; goto free_strings; + } } if (regex_) { @@ -203,38 +210,37 @@ static int get_value(const char *key_, const char *regex_) regexp = (regex_t*)xmalloc(sizeof(regex_t)); if (regcomp(regexp, regex_, REG_EXTENDED)) { fprintf(stderr, "Invalid pattern: %s\n", regex_); + free(regexp); + regexp = NULL; + ret = CONFIG_INVALID_PATTERN; goto free_strings; } } - if (do_all && system_wide) - git_config_from_file(show_config, system_wide, NULL); - if (do_all && global) - git_config_from_file(show_config, global, NULL); - if (do_all) - git_config_from_file(show_config, local, NULL); - git_config_from_parameters(show_config, NULL); - if (!do_all && !seen) - git_config_from_file(show_config, local, NULL); - if (!do_all && !seen && global) - git_config_from_file(show_config, global, NULL); - if (!do_all && !seen && system_wide) - git_config_from_file(show_config, system_wide, NULL); + git_config_with_options(collect_config, &values, + given_config_file, respect_includes); + + ret = !values.nr; + + for (i = 0; i < values.nr; i++) { + struct strbuf *buf = values.items + i; + if (do_all || i == values.nr - 1) + fwrite(buf->buf, 1, buf->len, stdout); + strbuf_release(buf); + } + free(values.items); +free_strings: free(key); + if (key_regexp) { + regfree(key_regexp); + free(key_regexp); + } if (regexp) { regfree(regexp); free(regexp); } - if (do_all) - ret = !seen; - else - ret = (seen == 1) ? 0 : seen > 1 ? 2 : 1; - -free_strings: - free(repo_config); - free(global); return ret; } @@ -295,7 +301,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); @@ -303,24 +310,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; } @@ -328,15 +329,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; @@ -349,7 +353,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, @@ -361,27 +365,41 @@ int cmd_config(int argc, const char **argv, const char *prefix) } if (use_global_config) { - char *home = getenv("HOME"); - if (home) { - char *user_config = xstrdup(mkpath("%s/.gitconfig", home)); - config_exclusive_filename = user_config; - } else { + char *user_config = NULL; + char *xdg_config = NULL; + + home_config_paths(&user_config, &xdg_config, "config"); + + if (!user_config) + /* + * It is unknown if HOME/.gitconfig exists, so + * we do not know if we should write to XDG + * location; error out even if XDG_CONFIG_HOME + * is set and points at a sane location. + */ die("$HOME not set"); - } + + if (access_or_warn(user_config, R_OK) && + xdg_config && !access_or_warn(xdg_config, R_OK)) + given_config_file = xdg_config; + else + given_config_file = user_config; } 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'; @@ -418,47 +436,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]); - ret = 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 --set-all to change %s.", argv[0]); + " 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); @@ -479,18 +502,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) @@ -499,7 +526,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) @@ -510,9 +538,7 @@ 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); } diff --git a/builtin/count-objects.c b/builtin/count-objects.c index c37cb98c31..9afaa88f77 100644 --- a/builtin/count-objects.c +++ b/builtin/count-objects.c @@ -66,7 +66,7 @@ static void count_objects(DIR *d, char *path, int len, int verbose, } static char const * const count_objects_usage[] = { - "git count-objects [-v]", + N_("git count-objects [-v]"), NULL }; @@ -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, "be verbose"), + OPT__VERBOSE(&verbose, N_("be verbose")), OPT_END(), }; diff --git a/builtin/credential.c b/builtin/credential.c new file mode 100644 index 0000000000..0412fa00f0 --- /dev/null +++ b/builtin/credential.c @@ -0,0 +1,31 @@ +#include "git-compat-util.h" +#include "credential.h" +#include "builtin.h" + +static const char usage_msg[] = + "git credential [fill|approve|reject]"; + +int cmd_credential(int argc, const char **argv, const char *prefix) +{ + const char *op; + struct credential c = CREDENTIAL_INIT; + + op = argv[1]; + if (!op) + usage(usage_msg); + + if (credential_read(&c, stdin) < 0) + die("unable to read credential from stdin"); + + if (!strcmp(op, "fill")) { + credential_fill(&c); + credential_write(&c, stdout); + } else if (!strcmp(op, "approve")) { + credential_approve(&c); + } else if (!strcmp(op, "reject")) { + credential_reject(&c); + } else { + usage(usage_msg); + } + return 0; +} diff --git a/builtin/describe.c b/builtin/describe.c index 9f63067f50..04c185b1fb 100644 --- a/builtin/describe.c +++ b/builtin/describe.c @@ -12,8 +12,8 @@ #define MAX_TAGS (FLAG_BITS - 1) static const char * const describe_usage[] = { - "git describe [options] <committish>*", - "git describe [options] --dirty", + N_("git describe [options] <committish>*"), + N_("git describe [options] --dirty"), NULL }; @@ -144,7 +144,7 @@ static int get_name(const char *path, const unsigned char *sha1, int flag, void if (!all && !might_be_tag) return 0; - if (!peel_ref(path, peeled) && !is_null_sha1(peeled)) { + if (!peel_ref(path, peeled)) { is_tag = !!hashcmp(sha1, peeled); } else { hashcpy(peeled, sha1); @@ -400,22 +400,22 @@ int cmd_describe(int argc, const char **argv, const char *prefix) { int contains = 0; struct option options[] = { - OPT_BOOLEAN(0, "contains", &contains, "find the tag that comes after the commit"), - OPT_BOOLEAN(0, "debug", &debug, "debug search strategy on stderr"), - OPT_BOOLEAN(0, "all", &all, "use any ref in .git/refs"), - OPT_BOOLEAN(0, "tags", &tags, "use any tag in .git/refs/tags"), - OPT_BOOLEAN(0, "long", &longformat, "always use long format"), + OPT_BOOLEAN(0, "contains", &contains, N_("find the tag that comes after the commit")), + OPT_BOOLEAN(0, "debug", &debug, N_("debug search strategy on stderr")), + OPT_BOOLEAN(0, "all", &all, N_("use any ref in .git/refs")), + OPT_BOOLEAN(0, "tags", &tags, N_("use any tag in .git/refs/tags")), + OPT_BOOLEAN(0, "long", &longformat, N_("always use long format")), OPT__ABBREV(&abbrev), OPT_SET_INT(0, "exact-match", &max_candidates, - "only output exact matches", 0), + N_("only output exact matches"), 0), OPT_INTEGER(0, "candidates", &max_candidates, - "consider <n> most recent tags (default: 10)"), - OPT_STRING(0, "match", &pattern, "pattern", - "only consider tags matching <pattern>"), + N_("consider <n> most recent tags (default: 10)")), + OPT_STRING(0, "match", &pattern, N_("pattern"), + N_("only consider tags matching <pattern>")), OPT_BOOLEAN(0, "always", &always, - "show abbreviated commit object as fallback"), - {OPTION_STRING, 0, "dirty", &dirty, "mark", - "append <mark> on dirty working tree (default: \"-dirty\")", + N_("show abbreviated commit object as fallback")), + {OPTION_STRING, 0, "dirty", &dirty, N_("mark"), + N_("append <mark> on dirty working tree (default: \"-dirty\")"), PARSE_OPT_OPTARG, NULL, (intptr_t) "-dirty"}, OPT_END(), }; diff --git a/builtin/diff-index.c b/builtin/diff-index.c index 2eb32bd9da..1c737f7921 100644 --- a/builtin/diff-index.c +++ b/builtin/diff-index.c @@ -41,9 +41,13 @@ int cmd_diff_index(int argc, const char **argv, const char *prefix) if (rev.pending.nr != 1 || rev.max_count != -1 || rev.min_age != -1 || rev.max_age != -1) usage(diff_cache_usage); - if (!cached) + if (!cached) { setup_work_tree(); - if (read_cache() < 0) { + if (read_cache_preload(rev.diffopt.pathspec.raw) < 0) { + perror("read_cache_preload"); + return -1; + } + } else if (read_cache() < 0) { perror("read_cache"); return -1; } diff --git a/builtin/diff.c b/builtin/diff.c index 69cd5eed78..8c2af6cb43 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]; @@ -28,6 +29,8 @@ static void stuff_change(struct diff_options *opt, unsigned old_mode, unsigned new_mode, const unsigned char *old_sha1, const unsigned char *new_sha1, + int old_sha1_valid, + int new_sha1_valid, const char *old_name, const char *new_name) { @@ -53,8 +56,8 @@ static void stuff_change(struct diff_options *opt, one = alloc_filespec(old_name); two = alloc_filespec(new_name); - fill_filespec(one, old_sha1, old_mode); - fill_filespec(two, new_sha1, new_mode); + fill_filespec(one, old_sha1, old_sha1_valid, old_mode); + fill_filespec(two, new_sha1, new_sha1_valid, new_mode); diff_queue(&diff_queued_diff, one, two); } @@ -83,6 +86,7 @@ static int builtin_diff_b_f(struct rev_info *revs, stuff_change(&revs->diffopt, blob[0].mode, canon_mode(st.st_mode), blob[0].sha1, null_sha1, + 1, 0, path, path); diffcore_std(&revs->diffopt); diff_flush(&revs->diffopt); @@ -107,6 +111,7 @@ static int builtin_diff_blobs(struct rev_info *revs, stuff_change(&revs->diffopt, blob[0].mode, blob[1].mode, blob[0].sha1, blob[1].sha1, + 1, 1, blob[0].name, blob[1].name); diffcore_std(&revs->diffopt); diff_flush(&revs->diffopt); @@ -125,8 +130,6 @@ static int builtin_diff_index(struct rev_info *revs, usage(builtin_diff_usage); argv++; argc--; } - if (!cached) - setup_work_tree(); /* * Make sure there is one revision (i.e. pending object), * and there is no revision filtering parameters. @@ -135,8 +138,14 @@ 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.pathspec.raw) < 0) { - perror("read_cache_preload"); + if (!cached) { + setup_work_tree(); + if (read_cache_preload(revs->diffopt.pathspec.raw) < 0) { + perror("read_cache_preload"); + return -1; + } + } else if (read_cache() < 0) { + perror("read_cache"); return -1; } return run_diff_index(revs, cached); @@ -169,7 +178,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,12 +186,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); - free(parent); + sha1_array_clear(&parents); return 0; } @@ -277,9 +285,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. */ @@ -288,6 +293,10 @@ 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); @@ -297,19 +306,12 @@ int cmd_diff(int argc, const char **argv, const char *prefix) argc = setup_revisions(argc, argv, &rev, NULL); if (!rev.diffopt.output_format) { rev.diffopt.output_format = DIFF_FORMAT_PATCH; - if (diff_setup_done(&rev.diffopt) < 0) - die(_("diff_setup_done failed")); + diff_setup_done(&rev.diffopt); } DIFF_OPT_SET(&rev.diffopt, RECURSIVE); - /* - * If the user asked for our exit code then don't start a - * pager or we would end up reporting its exit code instead. - */ - if (!DIFF_OPT_TST(&rev.diffopt, EXIT_WITH_STATUS) && - check_pager_config("diff") != 0) - setup_pager(); + setup_diff_pager(&rev.diffopt); /* * Do we have --cached and not have a pending object, then @@ -326,7 +328,7 @@ int cmd_diff(int argc, const char **argv, const char *prefix) add_head_to_pending(&rev); if (!rev.pending.nr) { struct tree *tree; - tree = lookup_tree((const unsigned char*)EMPTY_TREE_SHA1_BIN); + tree = lookup_tree(EMPTY_TREE_SHA1_BIN); add_pending_object(&rev, &tree->object, "HEAD"); } break; diff --git a/builtin/fast-export.c b/builtin/fast-export.c index 48bd5e6a80..12220ad8da 100644 --- a/builtin/fast-export.c +++ b/builtin/fast-export.c @@ -19,14 +19,15 @@ #include "quote.h" static const char *fast_export_usage[] = { - "git fast-export [rev-list-opts]", + N_("git fast-export [rev-list-opts]"), NULL }; static int progress; static enum { ABORT, VERBATIM, WARN, STRIP } signed_tag_mode = ABORT; -static enum { ERROR, DROP, REWRITE } tag_of_filtered_mode = ABORT; +static 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; @@ -50,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")) @@ -184,6 +185,8 @@ 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 if (strchr(path, ' ')) + printf("\"%s\"", path); else printf("%s", path); } @@ -609,7 +612,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) @@ -629,24 +632,24 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix) char *export_filename = NULL, *import_filename = NULL; struct option options[] = { OPT_INTEGER(0, "progress", &progress, - "show progress after <n> objects"), - OPT_CALLBACK(0, "signed-tags", &signed_tag_mode, "mode", - "select handling of signed tags", + N_("show progress after <n> objects")), + OPT_CALLBACK(0, "signed-tags", &signed_tag_mode, N_("mode"), + N_("select handling of signed tags"), parse_opt_signed_tag_mode), - OPT_CALLBACK(0, "tag-of-filtered-object", &tag_of_filtered_mode, "mode", - "select handling of tags that tag filtered objects", + OPT_CALLBACK(0, "tag-of-filtered-object", &tag_of_filtered_mode, N_("mode"), + N_("select handling of tags that tag filtered objects"), parse_opt_tag_of_filtered_mode), - OPT_STRING(0, "export-marks", &export_filename, "file", - "Dump marks to this file"), - OPT_STRING(0, "import-marks", &import_filename, "file", - "Import marks from this file"), + OPT_STRING(0, "export-marks", &export_filename, N_("file"), + N_("Dump marks to this file")), + OPT_STRING(0, "import-marks", &import_filename, N_("file"), + N_("Import marks from this file")), OPT_BOOLEAN(0, "fake-missing-tagger", &fake_missing_tagger, - "Fake a tagger when tags lack one"), + N_("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 }, + N_("Output full tree for each commit")), + OPT_BOOLEAN(0, "use-done-feature", &use_done_feature, + N_("Use the done feature to terminate the stream")), + OPT_BOOL(0, "no-data", &no_data, N_("Skip output of blob data")), OPT_END() }; @@ -665,6 +668,9 @@ 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); @@ -692,5 +698,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 3c871c2da8..940ae35dc2 100644 --- a/builtin/fetch-pack.c +++ b/builtin/fetch-pack.c @@ -1,937 +1,137 @@ #include "builtin.h" -#include "refs.h" #include "pkt-line.h" -#include "commit.h" -#include "tag.h" -#include "exec_cmd.h" -#include "pack.h" -#include "sideband.h" #include "fetch-pack.h" -#include "remote.h" -#include "run-command.h" -#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 = 0; -static struct fetch_pack_args args = { - /* .uploadpack = */ "git-upload-pack", -}; static const char fetch_pack_usage[] = -"git fetch-pack [--all] [--quiet|-q] [--keep|-k] [--thin] [--include-tag] [--upload-pack=<git-upload-pack>] [--depth=<n>] [--no-progress] [-v] [<host>:]<directory> [<refs>...]"; - -#define COMPLETE (1U << 0) -#define COMMON (1U << 1) -#define COMMON_REF (1U << 2) -#define SEEN (1U << 3) -#define POPPED (1U << 4) - -static int marked; - -/* - * After sending this many "have"s if we do not get any new ACK , we - * give up traversing our history. - */ -#define MAX_IN_VAIN 256 - -static struct commit_list *rev_list; -static int non_common_revs, multi_ack, use_sideband; - -static void rev_list_push(struct commit *commit, int mark) -{ - if (!(commit->object.flags & mark)) { - commit->object.flags |= mark; - - if (!(commit->object.parsed)) - if (parse_commit(commit)) - return; - - 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) -{ - struct object *o = deref_tag(parse_object(sha1), path, 0); - - if (o && o->type == OBJ_COMMIT) - rev_list_push((struct commit *)o, SEEN); - - return 0; -} - -static int clear_marks(const char *path, const unsigned char *sha1, int flag, void *cb_data) -{ - struct object *o = deref_tag(parse_object(sha1), path, 0); - - if (o && o->type == OBJ_COMMIT) - clear_commit_marks((struct commit *)o, - COMMON | COMMON_REF | SEEN | POPPED); - return 0; -} - -/* - This function marks a rev and its ancestors as common. - In some cases, it is desirable to mark only the ancestors (for example - when only the server does not yet know that they are common). -*/ - -static void mark_common(struct commit *commit, - int ancestors_only, int dont_parse) -{ - if (commit != NULL && !(commit->object.flags & COMMON)) { - struct object *o = (struct object *)commit; - - if (!ancestors_only) - o->flags |= COMMON; - - if (!(o->flags & SEEN)) - rev_list_push(commit, SEEN); - else { - struct commit_list *parents; - - if (!ancestors_only && !(o->flags & POPPED)) - non_common_revs--; - if (!o->parsed && !dont_parse) - if (parse_commit(commit)) - return; - - for (parents = commit->parents; - parents; - parents = parents->next) - mark_common(parents->item, 0, dont_parse); - } - } -} - -/* - Get the next rev to send, ignoring the common. -*/ +"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>...]"; -static const unsigned char *get_rev(void) -{ - struct commit *commit = NULL; - - while (commit == NULL) { - unsigned int mark; - struct commit_list *parents; - - if (rev_list == NULL || non_common_revs == 0) - return NULL; - - commit = rev_list->item; - if (!commit->object.parsed) - parse_commit(commit); - parents = commit->parents; - - commit->object.flags |= POPPED; - if (!(commit->object.flags & COMMON)) - non_common_revs--; - - if (commit->object.flags & COMMON) { - /* do not send "have", and ignore ancestors */ - commit = NULL; - mark = COMMON | SEEN; - } else if (commit->object.flags & COMMON_REF) - /* send "have", and ignore ancestors */ - mark = COMMON | SEEN; - else - /* send "have", also for its ancestors */ - mark = SEEN; - - while (parents) { - if (!(parents->item->object.flags & SEEN)) - rev_list_push(parents->item, mark); - if (mark & COMMON) - mark_common(parents->item, 1, 0); - parents = parents->next; - } - - rev_list = rev_list->next; - } - - return commit->object.sha1; -} - -enum ack_type { - NAK = 0, - ACK, - ACK_continue, - ACK_common, - ACK_ready -}; - -static void consume_shallow_list(int fd) -{ - if (args.stateless_rpc && args.depth > 0) { - /* If we sent a depth we will get back "duplicate" - * shallow and unshallow commands every time there - * is a block of have lines exchanged. - */ - char line[1000]; - while (packet_read_line(fd, line, sizeof(line))) { - if (!prefixcmp(line, "shallow ")) - continue; - if (!prefixcmp(line, "unshallow ")) - continue; - die("git fetch-pack: expected shallow list"); - } - } -} - -static enum ack_type get_ack(int fd, unsigned char *result_sha1) -{ - static char line[1000]; - int len = packet_read_line(fd, line, sizeof(line)); - - if (!len) - die("git fetch-pack: expected ACK/NAK, got EOF"); - if (line[len-1] == '\n') - line[--len] = 0; - if (!strcmp(line, "NAK")) - return NAK; - if (!prefixcmp(line, "ACK ")) { - if (!get_sha1_hex(line+4, result_sha1)) { - if (strstr(line+45, "continue")) - return ACK_continue; - if (strstr(line+45, "common")) - return ACK_common; - if (strstr(line+45, "ready")) - return ACK_ready; - return ACK; - } - } - die("git fetch_pack: expected ACK/NAK, got '%s'", line); -} - -static void send_request(int fd, struct strbuf *buf) -{ - if (args.stateless_rpc) { - send_sideband(fd, -1, buf->buf, buf->len, LARGE_PACKET_MAX); - packet_flush(fd); - } else - safe_write(fd, buf->buf, buf->len); -} - -static void insert_one_alternate_ref(const struct ref *ref, void *unused) -{ - rev_list_insert_ref(NULL, ref->old_sha1, 0, NULL); -} - -static void insert_alternate_refs(void) -{ - for_each_alternate_ref(insert_one_alternate_ref, 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 cmd_fetch_pack(int argc, const char **argv, const char *prefix) { - int fetching; - 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; + int i, ret; + struct ref *ref = NULL; + const char *dest = NULL; + struct string_list sought = STRING_LIST_INIT_DUP; + int fd[2]; + char *pack_lockfile = NULL; + char **pack_lockfile_ptr = NULL; + struct child_process *conn; + struct fetch_pack_args args; - if (args.stateless_rpc && multi_ack == 1) - die("--stateless-rpc requires multi_ack_detailed"); - if (marked) - for_each_ref(clear_marks, NULL); - marked = 1; + packet_trace_identity("fetch-pack"); - for_each_ref(rev_list_insert_ref, NULL); - insert_alternate_refs(); + memset(&args, 0, sizeof(args)); + args.uploadpack = "git-upload-pack"; - fetching = 0; - for ( ; refs ; refs = refs->next) { - unsigned char *remote = refs->old_sha1; - const char *remote_hex; - struct object *o; + for (i = 1; i < argc && *argv[i] == '-'; i++) { + const char *arg = argv[i]; - /* - * If that object is complete (i.e. it is an ancestor of a - * local ref), we tell them we have it but do not have to - * tell them about its ancestors, which they already know - * about. - * - * We use lookup_object here because we are only - * interested in the case we *know* the object is - * reachable and we have already scanned it. - */ - if (((o = lookup_object(remote)) != NULL) && - (o->flags & COMPLETE)) { + if (!prefixcmp(arg, "--upload-pack=")) { + args.uploadpack = arg + 14; continue; } - - remote_hex = sha1_to_hex(remote); - if (!fetching) { - struct strbuf c = STRBUF_INIT; - if (multi_ack == 2) strbuf_addstr(&c, " multi_ack_detailed"); - if (multi_ack == 1) strbuf_addstr(&c, " multi_ack"); - if (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"); - if (args.no_progress) strbuf_addstr(&c, " no-progress"); - if (args.include_tag) strbuf_addstr(&c, " include-tag"); - if (prefer_ofs_delta) strbuf_addstr(&c, " ofs-delta"); - packet_buf_write(&req_buf, "want %s%s\n", remote_hex, c.buf); - strbuf_release(&c); - } else - packet_buf_write(&req_buf, "want %s\n", remote_hex); - fetching++; - } - - if (!fetching) { - strbuf_release(&req_buf); - packet_flush(fd[1]); - return 1; - } - - if (is_repository_shallow()) - write_shallow_commits(&req_buf, 1); - if (args.depth > 0) - packet_buf_write(&req_buf, "deepen %d", args.depth); - packet_buf_flush(&req_buf); - state_len = req_buf.len; - - if (args.depth > 0) { - char line[1024]; - unsigned char sha1[20]; - - send_request(fd[1], &req_buf); - while (packet_read_line(fd[0], line, sizeof(line))) { - if (!prefixcmp(line, "shallow ")) { - if (get_sha1_hex(line + 8, sha1)) - die("invalid shallow line: %s", line); - register_shallow(sha1); - continue; - } - if (!prefixcmp(line, "unshallow ")) { - if (get_sha1_hex(line + 10, sha1)) - die("invalid unshallow line: %s", line); - if (!lookup_object(sha1)) - die("object not found: %s", line); - /* make sure that it is parsed as shallow */ - if (!parse_object(sha1)) - die("error in object: %s", line); - if (unregister_shallow(sha1)) - die("no shallow found: %s", line); - continue; - } - die("expected shallow/unshallow, got %s", line); + if (!prefixcmp(arg, "--exec=")) { + args.uploadpack = arg + 7; + continue; } - } else if (!args.stateless_rpc) - send_request(fd[1], &req_buf); - - if (!args.stateless_rpc) { - /* If we aren't using the stateless-rpc interface - * we don't need to retain the headers. - */ - strbuf_setlen(&req_buf, 0); - state_len = 0; - } - - flushes = 0; - retval = -1; - while ((sha1 = get_rev())) { - packet_buf_write(&req_buf, "have %s\n", sha1_to_hex(sha1)); - if (args.verbose) - fprintf(stderr, "have %s\n", sha1_to_hex(sha1)); - in_vain++; - if (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 == INITIAL_FLUSH) - continue; - - consume_shallow_list(fd[0]); - do { - ack = get_ack(fd[0], result_sha1); - if (args.verbose && ack) - fprintf(stderr, "got ack %d %s\n", ack, - sha1_to_hex(result_sha1)); - switch (ack) { - case ACK: - flushes = 0; - multi_ack = 0; - retval = 0; - goto done; - case ACK_common: - case ACK_ready: - case ACK_continue: { - struct commit *commit = - lookup_commit(result_sha1); - if (!commit) - die("invalid commit %s", sha1_to_hex(result_sha1)); - if (args.stateless_rpc - && ack == ACK_common - && !(commit->object.flags & COMMON)) { - /* We need to replay the have for this object - * on the next RPC request so the peer knows - * it is in common with us. - */ - const char *hex = sha1_to_hex(result_sha1); - packet_buf_write(&req_buf, "have %s\n", hex); - state_len = req_buf.len; - } - mark_common(commit, 0, 1); - retval = 0; - in_vain = 0; - got_continue = 1; - if (ack == ACK_ready) { - rev_list = NULL; - got_ready = 1; - } - break; - } - } - } while (ack); - flushes--; - if (got_continue && MAX_IN_VAIN < in_vain) { - if (args.verbose) - fprintf(stderr, "giving up\n"); - break; /* give up */ - } + if (!strcmp("--quiet", arg) || !strcmp("-q", arg)) { + args.quiet = 1; + continue; } - } -done: - 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) { - multi_ack = 0; - flushes++; - } - strbuf_release(&req_buf); - - consume_shallow_list(fd[0]); - while (flushes || multi_ack) { - int ack = get_ack(fd[0], result_sha1); - if (ack) { - if (args.verbose) - fprintf(stderr, "got ack (%d) %s\n", ack, - sha1_to_hex(result_sha1)); - if (ack == ACK) - return 0; - multi_ack = 1; + if (!strcmp("--keep", arg) || !strcmp("-k", arg)) { + args.lock_pack = args.keep_pack; + args.keep_pack = 1; continue; } - flushes--; - } - /* it is no error to fetch into a completely empty repo */ - return count ? retval : 0; -} - -static struct commit_list *complete; - -static int mark_complete(const char *path, const unsigned char *sha1, int flag, void *cb_data) -{ - struct object *o = parse_object(sha1); - - while (o && o->type == OBJ_TAG) { - struct tag *t = (struct tag *) o; - if (!t->tagged) - break; /* broken repository */ - o->flags |= COMPLETE; - o = parse_object(t->tagged->sha1); - } - if (o && o->type == OBJ_COMMIT) { - struct commit *commit = (struct commit *)o; - if (!(commit->object.flags & COMPLETE)) { - commit->object.flags |= COMPLETE; - commit_list_insert_by_date(commit, &complete); + if (!strcmp("--thin", arg)) { + args.use_thin_pack = 1; + continue; } - } - return 0; -} - -static void mark_recent_complete_commits(unsigned long cutoff) -{ - while (complete && cutoff <= complete->item->date) { - if (args.verbose) - fprintf(stderr, "Marking %s as complete\n", - sha1_to_hex(complete->item->object.sha1)); - pop_most_recent_commit(&complete, COMPLETE); - } -} - -static void filter_refs(struct ref **refs, int nr_match, char **match) -{ - struct ref **return_refs; - struct ref *newlist = NULL; - struct ref **newtail = &newlist; - struct ref *ref, *next; - struct ref *fastarray[32]; - - if (nr_match && !args.fetch_all) { - if (ARRAY_SIZE(fastarray) < nr_match) - return_refs = xcalloc(nr_match, sizeof(struct ref *)); - else { - return_refs = fastarray; - memset(return_refs, 0, sizeof(struct ref *) * nr_match); + if (!strcmp("--include-tag", arg)) { + args.include_tag = 1; + continue; } - } - else - return_refs = NULL; - - for (ref = *refs; ref; ref = next) { - next = ref->next; - if (!memcmp(ref->name, "refs/", 5) && - check_ref_format(ref->name + 5)) - ; /* trash */ - else if (args.fetch_all && - (!args.depth || prefixcmp(ref->name, "refs/tags/") )) { - *newtail = ref; - ref->next = NULL; - newtail = &ref->next; + if (!strcmp("--all", arg)) { + args.fetch_all = 1; continue; } - else { - int order = path_match(ref->name, nr_match, match); - if (order) { - return_refs[order-1] = ref; - continue; /* we will link it later */ - } + if (!strcmp("--stdin", arg)) { + args.stdin_refs = 1; + continue; } - free(ref); - } - - if (!args.fetch_all) { - int i; - for (i = 0; i < nr_match; i++) { - ref = return_refs[i]; - if (ref) { - *newtail = ref; - ref->next = NULL; - newtail = &ref->next; - } + if (!strcmp("-v", arg)) { + args.verbose = 1; + continue; } - if (return_refs != fastarray) - free(return_refs); - } - *refs = newlist; -} - -static int everything_local(struct ref **refs, int nr_match, char **match) -{ - struct ref *ref; - int retval; - unsigned long cutoff = 0; - - save_commit_buffer = 0; - - for (ref = *refs; ref; ref = ref->next) { - struct object *o; - - o = parse_object(ref->old_sha1); - if (!o) + if (!prefixcmp(arg, "--depth=")) { + args.depth = strtol(arg + 8, NULL, 0); continue; - - /* We already have it -- which may mean that we were - * in sync with the other side at some time after - * that (it is OK if we guess wrong here). - */ - if (o->type == OBJ_COMMIT) { - struct commit *commit = (struct commit *)o; - if (!cutoff || cutoff < commit->date) - cutoff = commit->date; } - } - - if (!args.depth) { - for_each_ref(mark_complete, NULL); - if (cutoff) - mark_recent_complete_commits(cutoff); - } - - /* - * Mark all complete remote refs as common refs. - * Don't mark them common yet; the server has to be told so first. - */ - for (ref = *refs; ref; ref = ref->next) { - struct object *o = deref_tag(lookup_object(ref->old_sha1), - NULL, 0); - - if (!o || o->type != OBJ_COMMIT || !(o->flags & COMPLETE)) + if (!strcmp("--no-progress", arg)) { + args.no_progress = 1; continue; - - if (!(o->flags & SEEN)) { - rev_list_push((struct commit *)o, COMMON_REF | SEEN); - - mark_common((struct commit *)o, 1, 1); } - } - - filter_refs(refs, nr_match, match); - - for (retval = 1, ref = *refs; ref ; ref = ref->next) { - const unsigned char *remote = ref->old_sha1; - unsigned char local[20]; - struct object *o; - - o = lookup_object(remote); - if (!o || !(o->flags & COMPLETE)) { - retval = 0; - if (!args.verbose) - continue; - fprintf(stderr, - "want %s (%s)\n", sha1_to_hex(remote), - ref->name); + if (!strcmp("--stateless-rpc", arg)) { + args.stateless_rpc = 1; continue; } - - hashcpy(ref->new_sha1, local); - if (!args.verbose) + if (!strcmp("--lock-pack", arg)) { + args.lock_pack = 1; + pack_lockfile_ptr = &pack_lockfile; continue; - fprintf(stderr, - "already have %s (%s)\n", sha1_to_hex(remote), - ref->name); - } - return retval; -} - -static int sideband_demux(int in, int out, void *data) -{ - int *xd = data; - - int ret = recv_sideband("fetch-pack", xd[0], out); - close(out); - return ret; -} - -static int get_pack(int xd[2], char **pack_lockfile) -{ - struct async demux; - const char *argv[20]; - char keep_arg[256]; - char hdr_arg[256]; - const char **av; - int do_keep = args.keep_pack; - struct child_process cmd; - - memset(&demux, 0, sizeof(demux)); - if (use_sideband) { - /* xd[] is talking with upload-pack; subprocess reads from - * xd[0], spits out band#2 to stderr, and feeds us band#1 - * through demux->out. - */ - demux.proc = sideband_demux; - demux.data = xd; - demux.out = -1; - if (start_async(&demux)) - die("fetch-pack: unable to fork off sideband" - " demultiplexer"); - } - else - demux.out = xd[0]; - - memset(&cmd, 0, sizeof(cmd)); - cmd.argv = argv; - av = argv; - *hdr_arg = 0; - if (!args.keep_pack && unpack_limit) { - struct pack_header header; - - if (read_pack_header(demux.out, &header)) - die("protocol error: bad pack header"); - snprintf(hdr_arg, sizeof(hdr_arg), - "--pack_header=%"PRIu32",%"PRIu32, - ntohl(header.hdr_version), ntohl(header.hdr_entries)); - if (ntohl(header.hdr_entries) < unpack_limit) - do_keep = 0; - else - do_keep = 1; - } - - if (do_keep) { - if (pack_lockfile) - cmd.out = -1; - *av++ = "index-pack"; - *av++ = "--stdin"; - if (!args.quiet && !args.no_progress) - *av++ = "-v"; - if (args.use_thin_pack) - *av++ = "--fix-thin"; - if (args.lock_pack || unpack_limit) { - int s = sprintf(keep_arg, - "--keep=fetch-pack %"PRIuMAX " on ", (uintmax_t) getpid()); - if (gethostname(keep_arg + s, sizeof(keep_arg) - s)) - strcpy(keep_arg + s, "localhost"); - *av++ = keep_arg; } + usage(fetch_pack_usage); } - else { - *av++ = "unpack-objects"; - if (args.quiet) - *av++ = "-q"; - } - if (*hdr_arg) - *av++ = hdr_arg; - *av++ = NULL; - - cmd.in = demux.out; - cmd.git_cmd = 1; - if (start_command(&cmd)) - die("fetch-pack: unable to fork off %s", argv[0]); - if (do_keep && pack_lockfile) { - *pack_lockfile = index_pack_lockfile(cmd.out); - close(cmd.out); - } - - if (finish_command(&cmd)) - die("%s failed", argv[0]); - if (use_sideband && finish_async(&demux)) - die("error in sideband demultiplexer"); - return 0; -} -static struct ref *do_fetch_pack(int fd[2], - const struct ref *orig_ref, - int nr_match, - char **match, - char **pack_lockfile) -{ - struct ref *ref = copy_ref_list(orig_ref); - unsigned char sha1[20]; + if (i < argc) + dest = argv[i++]; + else + usage(fetch_pack_usage); - 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) - fprintf(stderr, "Server supports multi_ack\n"); - multi_ack = 1; - } - if (server_supports("side-band-64k")) { - if (args.verbose) - fprintf(stderr, "Server supports side-band-64k\n"); - use_sideband = 2; - } - else if (server_supports("side-band")) { - if (args.verbose) - fprintf(stderr, "Server supports side-band\n"); - use_sideband = 1; - } - if (server_supports("ofs-delta")) { - if (args.verbose) - fprintf(stderr, "Server supports ofs-delta\n"); - } else - prefer_ofs_delta = 0; - if (everything_local(&ref, nr_match, match)) { - packet_flush(fd[1]); - goto all_done; - } - if (find_common(fd, sha1, ref) < 0) - if (!args.keep_pack) - /* When cloning, it is not unusual to have - * no common commit. + /* + * Copy refs from cmdline to growable list, then append any + * refs from the standard input: + */ + for (; i < argc; i++) + string_list_append(&sought, 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 */ - warning("no common commits"); - - if (args.stateless_rpc) - packet_flush(fd[1]); - if (get_pack(fd, pack_lockfile)) - die("git fetch-pack: fetch failed."); - - all_done: - return ref; -} - -static int remove_duplicates(int nr_heads, char **heads) -{ - int src, dst; - - for (src = dst = 0; src < nr_heads; src++) { - /* If heads[src] is different from any of - * heads[0..dst], push it in. - */ - int i; - for (i = 0; i < dst; i++) { - if (!strcmp(heads[i], heads[src])) - break; - } - if (i < dst) - continue; - if (src != dst) - heads[dst] = heads[src]; - dst++; - } - return dst; -} - -static int fetch_pack_config(const char *var, const char *value, void *cb) -{ - if (strcmp(var, "fetch.unpacklimit") == 0) { - fetch_unpack_limit = git_config_int(var, value); - return 0; - } - - if (strcmp(var, "transfer.unpacklimit") == 0) { - transfer_unpack_limit = git_config_int(var, value); - return 0; - } - - if (strcmp(var, "repack.usedeltabaseoffset") == 0) { - prefer_ofs_delta = git_config_bool(var, value); - return 0; - } - - return git_default_config(var, value, cb); -} - -static struct lock_file lock; - -static void fetch_pack_setup(void) -{ - static int did_setup; - if (did_setup) - return; - git_config(fetch_pack_config, NULL); - if (0 <= transfer_unpack_limit) - unpack_limit = transfer_unpack_limit; - else if (0 <= fetch_unpack_limit) - unpack_limit = fetch_unpack_limit; - did_setup = 1; -} - -int cmd_fetch_pack(int argc, const char **argv, const char *prefix) -{ - int i, ret, nr_heads; - struct ref *ref = NULL; - char *dest = NULL, **heads; - int fd[2]; - char *pack_lockfile = NULL; - char **pack_lockfile_ptr = NULL; - struct child_process *conn; - - packet_trace_identity("fetch-pack"); - - nr_heads = 0; - heads = NULL; - for (i = 1; i < argc; i++) { - const char *arg = argv[i]; - - if (*arg == '-') { - if (!prefixcmp(arg, "--upload-pack=")) { - args.uploadpack = arg + 14; - continue; - } - if (!prefixcmp(arg, "--exec=")) { - args.uploadpack = arg + 7; - continue; - } - if (!strcmp("--quiet", arg) || !strcmp("-q", arg)) { - args.quiet = 1; - continue; - } - if (!strcmp("--keep", arg) || !strcmp("-k", arg)) { - args.lock_pack = args.keep_pack; - args.keep_pack = 1; - continue; - } - if (!strcmp("--thin", arg)) { - args.use_thin_pack = 1; - continue; - } - if (!strcmp("--include-tag", arg)) { - args.include_tag = 1; - continue; - } - if (!strcmp("--all", arg)) { - args.fetch_all = 1; - continue; - } - if (!strcmp("-v", arg)) { - args.verbose = 1; - continue; - } - if (!prefixcmp(arg, "--depth=")) { - args.depth = strtol(arg + 8, NULL, 0); - continue; - } - if (!strcmp("--no-progress", arg)) { - args.no_progress = 1; - continue; - } - if (!strcmp("--stateless-rpc", arg)) { - args.stateless_rpc = 1; - continue; - } - if (!strcmp("--lock-pack", arg)) { - args.lock_pack = 1; - pack_lockfile_ptr = &pack_lockfile; - continue; + static char line[1000]; + for (;;) { + int n = packet_read_line(0, line, sizeof(line)); + if (!n) + break; + if (line[n-1] == '\n') + n--; + string_list_append(&sought, xmemdupz(line, n)); } - usage(fetch_pack_usage); } - dest = (char *)arg; - heads = (char **)(argv + i + 1); - nr_heads = argc - i - 1; - break; + else { + /* read from stdin one ref per line, until EOF */ + struct strbuf line = STRBUF_INIT; + while (strbuf_getline(&line, stdin, '\n') != EOF) + string_list_append(&sought, strbuf_detach(&line, NULL)); + strbuf_release(&line); + } } - 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); + &sought, pack_lockfile_ptr); if (pack_lockfile) { printf("lock %s\n", pack_lockfile); fflush(stdout); @@ -939,21 +139,18 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix) close(fd[0]); close(fd[1]); if (finish_connect(conn)) - ref = NULL; - ret = !ref; + return 1; - if (!ret && nr_heads) { - /* If the heads to pull were given, we should have - * consumed all of them by matching the remote. - * Otherwise, 'git fetch remote no-such-ref' would - * silently succeed without issuing an error. - */ - for (i = 0; i < nr_heads; i++) - if (heads[i] && heads[i][0]) { - error("no such remote ref %s", heads[i]); - ret = 1; - } - } + ret = !ref || sought.nr; + + /* + * If the heads to pull were given, we should have consumed + * all of them by matching the remote. Otherwise, 'git fetch + * remote no-such-ref' would silently succeed without issuing + * an error. + */ + for (i = 0; i < sought.nr; i++) + error("no such remote ref %s", sought.items[i].string); while (ref) { printf("%s %s\n", sha1_to_hex(ref->old_sha1), ref->name); @@ -962,64 +159,3 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix) return ret; } - -struct ref *fetch_pack(struct fetch_pack_args *my_args, - int fd[], struct child_process *conn, - const struct ref *ref, - const char *dest, - int nr_heads, - char **heads, - char **pack_lockfile) -{ - struct stat st; - struct ref *ref_cpy; - - fetch_pack_setup(); - if (&args != my_args) - memcpy(&args, my_args, sizeof(args)); - if (args.depth > 0) { - if (stat(git_path("shallow"), &st)) - st.st_mtime = 0; - } - - if (heads && nr_heads) - nr_heads = remove_duplicates(nr_heads, heads); - if (!ref) { - packet_flush(fd[1]); - die("no matching remote head"); - } - ref_cpy = do_fetch_pack(fd, ref, nr_heads, heads, pack_lockfile); - - if (args.depth > 0) { - struct cache_time mtime; - struct strbuf sb = STRBUF_INIT; - char *shallow = git_path("shallow"); - int fd; - - mtime.sec = st.st_mtime; - mtime.nsec = ST_MTIME_NSEC(st); - if (stat(shallow, &st)) { - if (mtime.sec) - die("shallow file was removed during fetch"); - } else if (st.st_mtime != mtime.sec -#ifdef USE_NSEC - || ST_MTIME_NSEC(st) != mtime.nsec -#endif - ) - die("shallow file was changed during fetch"); - - fd = hold_lock_file_for_update(&lock, shallow, - LOCK_DIE_ON_ERROR); - if (!write_shallow_commits(&sb, 0) - || write_in_full(fd, sb.buf, sb.len) != sb.len) { - unlink_or_warn(shallow); - rollback_lock_file(&lock); - } else { - commit_lock_file(&lock); - } - strbuf_release(&sb); - } - - reprepare_packed_git(); - return ref_cpy; -} diff --git a/builtin/fetch.c b/builtin/fetch.c index 914cb919d0..4b5a89839b 100644 --- a/builtin/fetch.c +++ b/builtin/fetch.c @@ -13,12 +13,14 @@ #include "sigchain.h" #include "transport.h" #include "submodule.h" +#include "connected.h" +#include "argv-array.h" static const char * const builtin_fetch_usage[] = { - "git fetch [<options>] [<repository> [<refspec>...]]", - "git fetch [<options>] <group>", - "git fetch --multiple [<options>] [(<repository> | <group>)...]", - "git fetch --all [<options>]", + N_("git fetch [<options>] [<repository> [<refspec>...]]"), + N_("git fetch [<options>] <group>"), + N_("git fetch --multiple [<options>] [(<repository> | <group>)...]"), + N_("git fetch --all [<options>]"), NULL }; @@ -29,7 +31,7 @@ enum { }; static int all, append, dry_run, force, keep, multiple, prune, update_head_ok, verbosity; -static int progress, recurse_submodules = RECURSE_SUBMODULES_DEFAULT; +static int progress = -1, recurse_submodules = RECURSE_SUBMODULES_DEFAULT; static int tags = TAGS_DEFAULT; static const char *depth; static const char *upload_pack; @@ -55,36 +57,36 @@ static int option_parse_recurse_submodules(const struct option *opt, static struct option builtin_fetch_options[] = { OPT__VERBOSITY(&verbosity), OPT_BOOLEAN(0, "all", &all, - "fetch from all remotes"), + N_("fetch from all remotes")), OPT_BOOLEAN('a', "append", &append, - "append to .git/FETCH_HEAD instead of overwriting"), - OPT_STRING(0, "upload-pack", &upload_pack, "path", - "path to upload pack on remote end"), - OPT__FORCE(&force, "force overwrite of local branch"), + N_("append to .git/FETCH_HEAD instead of overwriting")), + OPT_STRING(0, "upload-pack", &upload_pack, N_("path"), + N_("path to upload pack on remote end")), + OPT__FORCE(&force, N_("force overwrite of local branch")), OPT_BOOLEAN('m', "multiple", &multiple, - "fetch from multiple remotes"), + N_("fetch from multiple remotes")), OPT_SET_INT('t', "tags", &tags, - "fetch all tags and associated objects", TAGS_SET), + N_("fetch all tags and associated objects"), TAGS_SET), OPT_SET_INT('n', NULL, &tags, - "do not fetch all tags (--no-tags)", TAGS_UNSET), + N_("do not fetch all tags (--no-tags)"), TAGS_UNSET), OPT_BOOLEAN('p', "prune", &prune, - "prune remote-tracking branches no longer on remote"), - { OPTION_CALLBACK, 0, "recurse-submodules", NULL, "on-demand", - "control recursive fetching of submodules", + N_("prune remote-tracking branches no longer on remote")), + { OPTION_CALLBACK, 0, "recurse-submodules", NULL, N_("on-demand"), + N_("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"), + N_("dry run")), + OPT_BOOLEAN('k', "keep", &keep, N_("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", - "deepen history of shallow clone"), - { OPTION_STRING, 0, "submodule-prefix", &submodule_prefix, "dir", - "prepend this to submodule path output", PARSE_OPT_HIDDEN }, + N_("allow updating of HEAD ref")), + OPT_BOOL(0, "progress", &progress, N_("force progress reporting")), + OPT_STRING(0, "depth", &depth, N_("depth"), + N_("deepen history of shallow clone")), + { OPTION_STRING, 0, "submodule-prefix", &submodule_prefix, N_("dir"), + N_("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 }, + N_("default mode for recursion"), PARSE_OPT_HIDDEN }, OPT_END() }; @@ -239,6 +241,7 @@ static int s_update_ref(const char *action, static int update_local_ref(struct ref *ref, const char *remote, + const struct ref *remote_ref, struct strbuf *display) { struct commit *current = NULL, *updated; @@ -253,9 +256,8 @@ static int update_local_ref(struct ref *ref, if (!hashcmp(ref->old_sha1, ref->new_sha1)) { if (verbosity > 0) strbuf_addf(display, "= %-*s %-*s -> %s", - TRANSPORT_SUMMARY_WIDTH, - _("[up to date]"), REFCOL_WIDTH, - remote, pretty_ref); + TRANSPORT_SUMMARY(_("[up to date]")), + REFCOL_WIDTH, remote, pretty_ref); return 0; } @@ -269,7 +271,7 @@ static int update_local_ref(struct ref *ref, */ strbuf_addf(display, _("! %-*s %-*s -> %s (can't fetch in current branch)"), - TRANSPORT_SUMMARY_WIDTH, _("[rejected]"), + TRANSPORT_SUMMARY(_("[rejected]")), REFCOL_WIDTH, remote, pretty_ref); return 1; } @@ -280,7 +282,7 @@ static int update_local_ref(struct ref *ref, r = s_update_ref("updating tag", ref, 0); strbuf_addf(display, "%c %-*s %-*s -> %s%s", r ? '!' : '-', - TRANSPORT_SUMMARY_WIDTH, _("[tag update]"), + TRANSPORT_SUMMARY(_("[tag update]")), REFCOL_WIDTH, remote, pretty_ref, r ? _(" (unable to update local ref)") : ""); return r; @@ -292,28 +294,36 @@ 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 { + } else if (!prefixcmp(name, "refs/heads/")) { msg = "storing head"; what = _("[new branch]"); - if ((recurse_submodules != RECURSE_SUBMODULES_OFF) && - (recurse_submodules != RECURSE_SUBMODULES_ON)) - check_for_new_submodule_commits(ref->new_sha1); + } 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); strbuf_addf(display, "%c %-*s %-*s -> %s%s", r ? '!' : '*', - TRANSPORT_SUMMARY_WIDTH, what, + TRANSPORT_SUMMARY(what), REFCOL_WIDTH, remote, pretty_ref, r ? _(" (unable to update local ref)") : ""); return r; } - if (in_merge_bases(current, &updated, 1)) { + if (in_merge_bases(current, updated)) { char quickref[83]; int r; strcpy(quickref, find_unique_abbrev(current->object.sha1, DEFAULT_ABBREV)); @@ -347,13 +357,25 @@ static int update_local_ref(struct ref *ref, return r; } else { strbuf_addf(display, "! %-*s %-*s -> %s %s", - TRANSPORT_SUMMARY_WIDTH, _("[rejected]"), + TRANSPORT_SUMMARY(_("[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) { @@ -364,6 +386,7 @@ static int store_updated_refs(const char *raw_url, const char *remote_name, 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) @@ -373,94 +396,114 @@ static int store_updated_refs(const char *raw_url, const char *remote_name, 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-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(commit ? commit->object.sha1 : - 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, ¬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 (!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.buf); } } - free(url); - fclose(fp); + if (rc & STORE_REF_ERROR_DF_CONFLICT) error(_("some local refs could not be updated; try running\n" " 'git remote prune %s' to remove any old, conflicting " "branches"), remote_name); + + abort: strbuf_release(¬e); + free(url); + fclose(fp); return rc; } @@ -468,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 @@ -495,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) @@ -551,20 +541,20 @@ 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]"), + TRANSPORT_SUMMARY(_("[deleted]")), REFCOL_WIDTH, _("(none)"), prettify_refname(ref->name)); warn_dangling_symref(stderr, dangling_msg, ref->name); } @@ -604,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; /* @@ -745,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 @@ -828,38 +841,39 @@ static int add_remote_or_group(const char *name, struct string_list *list) return 1; } -static void add_options_to_argv(int *argc, const char **argv) +static void add_options_to_argv(struct argv_array *argv) { if (dry_run) - argv[(*argc)++] = "--dry-run"; + argv_array_push(argv, "--dry-run"); if (prune) - argv[(*argc)++] = "--prune"; + argv_array_push(argv, "--prune"); if (update_head_ok) - argv[(*argc)++] = "--update-head-ok"; + argv_array_push(argv, "--update-head-ok"); if (force) - argv[(*argc)++] = "--force"; + argv_array_push(argv, "--force"); if (keep) - argv[(*argc)++] = "--keep"; + argv_array_push(argv, "--keep"); if (recurse_submodules == RECURSE_SUBMODULES_ON) - argv[(*argc)++] = "--recurse-submodules"; + argv_array_push(argv, "--recurse-submodules"); else if (recurse_submodules == RECURSE_SUBMODULES_ON_DEMAND) - argv[(*argc)++] = "--recurse-submodules=on-demand"; + argv_array_push(argv, "--recurse-submodules=on-demand"); + if (tags == TAGS_SET) + argv_array_push(argv, "--tags"); + else if (tags == TAGS_UNSET) + argv_array_push(argv, "--no-tags"); if (verbosity >= 2) - argv[(*argc)++] = "-v"; + argv_array_push(argv, "-v"); if (verbosity >= 1) - argv[(*argc)++] = "-v"; + argv_array_push(argv, "-v"); else if (verbosity < 0) - argv[(*argc)++] = "-q"; + argv_array_push(argv, "-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); + struct argv_array argv = ARGV_ARRAY_INIT; if (!append && !dry_run) { int errcode = truncate_fetch_head(); @@ -867,18 +881,22 @@ static int fetch_multiple(struct string_list *list) return errcode; } + argv_array_pushl(&argv, "fetch", "--append", NULL); + add_options_to_argv(&argv); + for (i = 0; i < list->nr; i++) { const char *name = list->items[i].string; - argv[argc] = name; - argv[argc + 1] = NULL; + argv_array_push(&argv, name); if (verbosity >= 0) printf(_("Fetching %s\n"), name); - if (run_command_v_opt(argv, RUN_GIT_CMD)) { + if (run_command_v_opt(argv.argv, RUN_GIT_CMD)) { error(_("Could not fetch %s"), name); result = 1; } + argv_array_pop(&argv); } + argv_array_clear(&argv); return result; } @@ -929,7 +947,7 @@ static int fetch_one(struct remote *remote, int argc, const char **argv) atexit(unlock_pack); refspec = parse_fetch_refspec(ref_nr, refs); exit_code = do_fetch(transport, refspec, ref_nr); - free(refspec); + free_refspec(ref_nr, refspec); transport_disconnect(transport); transport = NULL; return exit_code; @@ -994,13 +1012,14 @@ 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, + struct argv_array options = ARGV_ARRAY_INIT; + + add_options_to_argv(&options); + result = fetch_populated_submodules(&options, submodule_prefix, recurse_submodules, verbosity < 0); + argv_array_clear(&options); } /* All names were strdup()ed or strndup()ed */ diff --git a/builtin/fmt-merge-msg.c b/builtin/fmt-merge-msg.c index 75816329d6..d9af43c257 100644 --- a/builtin/fmt-merge-msg.c +++ b/builtin/fmt-merge-msg.c @@ -5,32 +5,45 @@ #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>]", + N_("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; }; +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; @@ -42,14 +55,56 @@ static 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; @@ -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,144 @@ 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; + + 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, + struct fmt_merge_msg_opts *opts, + 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; + int limit = opts->shortlog_len; 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 +352,17 @@ 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 */ + if (opts->credit_people) + record_person('c', &committers, commit); continue; - + } + if (!count && opts->credit_people) + /* the 'tip' committer */ + record_person('c', &committers, commit); + if (opts->credit_people) + record_person('a', &authors, commit); count++; if (subjects.nr > limit) continue; @@ -183,11 +377,16 @@ static void shortlog(const char *name, unsigned char *sha1, string_list_append(&subjects, strbuf_detach(&sb, NULL)); } + if (opts->credit_people) + 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 +399,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 = ""; @@ -256,19 +457,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,76 +616,71 @@ 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, 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", + { OPTION_INTEGER, 0, "log", &shortlog_len, N_("n"), + N_("populate log with at most <n> entries from shortlog"), PARSE_OPT_OPTARG, NULL, DEFAULT_MERGE_LOG_LEN }, - { OPTION_INTEGER, 0, "summary", &shortlog_len, "n", - "alias for --log (deprecated)", + { OPTION_INTEGER, 0, "summary", &shortlog_len, N_("n"), + N_("alias for --log (deprecated)"), PARSE_OPT_OPTARG | PARSE_OPT_HIDDEN, NULL, DEFAULT_MERGE_LOG_LEN }, - OPT_STRING('m', "message", &message, "text", - "use <text> as start of message"), - OPT_FILENAME('F', "file", &inpath, "file to read from"), + OPT_STRING('m', "message", &message, N_("text"), + N_("use <text> as start of message")), + OPT_FILENAME('F', "file", &inpath, N_("file to read from")), OPT_END() }; 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 +693,13 @@ 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.credit_people = 1; + 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..7f059c31df 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 = ""; } @@ -922,7 +962,9 @@ static int opt_parse_sort(const struct option *opt, const char *arg, int unset) if (!arg) /* should --no-sort void the list ? */ return -1; - *sort_tail = s = xcalloc(1, sizeof(*s)); + s = xcalloc(1, sizeof(*s)); + s->next = *sort_tail; + *sort_tail = s; if (*arg == '-') { s->reverse = 1; @@ -934,7 +976,7 @@ static int opt_parse_sort(const struct option *opt, const char *arg, int unset) } static char const * const for_each_ref_usage[] = { - "git for-each-ref [options] [<pattern>]", + N_("git for-each-ref [options] [<pattern>]"), NULL }; @@ -949,19 +991,19 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix) struct option opts[] = { OPT_BIT('s', "shell", "e_style, - "quote placeholders suitably for shells", QUOTE_SHELL), + N_("quote placeholders suitably for shells"), QUOTE_SHELL), OPT_BIT('p', "perl", "e_style, - "quote placeholders suitably for perl", QUOTE_PERL), + N_("quote placeholders suitably for perl"), QUOTE_PERL), OPT_BIT(0 , "python", "e_style, - "quote placeholders suitably for python", QUOTE_PYTHON), + N_("quote placeholders suitably for python"), QUOTE_PYTHON), OPT_BIT(0 , "tcl", "e_style, - "quote placeholders suitably for tcl", QUOTE_TCL), + N_("quote placeholders suitably for tcl"), QUOTE_TCL), OPT_GROUP(""), - OPT_INTEGER( 0 , "count", &maxcount, "show only <n> matched refs"), - OPT_STRING( 0 , "format", &format, "format", "format to use for the output"), - OPT_CALLBACK(0 , "sort", sort_tail, "key", - "field name to sort on", &opt_parse_sort), + OPT_INTEGER( 0 , "count", &maxcount, N_("show only <n> matched refs")), + OPT_STRING( 0 , "format", &format, N_("format"), N_("format to use for the output")), + OPT_CALLBACK(0 , "sort", sort_tail, N_("key"), + N_("field name to sort on"), &opt_parse_sort), OPT_END(), }; diff --git a/builtin/fsck.c b/builtin/fsck.c index 5ae0366bc8..bb9a2cd447 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 @@ -137,7 +142,11 @@ static int traverse_one_object(struct object *obj) 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; @@ -145,7 +154,9 @@ static int traverse_reachable(void) entry = pending.objects + --pending.nr; obj = entry->item; result |= traverse_one_object(obj); + display_progress(progress, ++nr); } + stop_progress(&progress); return !!result; } @@ -212,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", @@ -227,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)) @@ -284,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; @@ -334,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.. @@ -515,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(); } @@ -535,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")) @@ -579,21 +605,23 @@ static int fsck_cache_tree(struct cache_tree *it) } static char const * const fsck_usage[] = { - "git fsck [options] [<object>...]", + N_("git fsck [options] [<object>...]"), NULL }; static struct option fsck_opts[] = { - OPT__VERBOSE(&verbose, "be verbose"), - OPT_BOOLEAN(0, "unreachable", &show_unreachable, "show unreachable objects"), - OPT_BOOLEAN(0, "tags", &show_tags, "report tags"), - OPT_BOOLEAN(0, "root", &show_root, "report root nodes"), - OPT_BOOLEAN(0, "cache", &keep_cache_objects, "make index objects head nodes"), - OPT_BOOLEAN(0, "reflogs", &include_reflogs, "make reflogs head nodes (default)"), - OPT_BOOLEAN(0, "full", &check_full, "also consider packs and alternate objects"), - OPT_BOOLEAN(0, "strict", &check_strict, "enable more strict checking"), + OPT__VERBOSE(&verbose, N_("be verbose")), + OPT_BOOLEAN(0, "unreachable", &show_unreachable, N_("show unreachable objects")), + OPT_BOOL(0, "dangling", &show_dangling, N_("show dangling objects")), + OPT_BOOLEAN(0, "tags", &show_tags, N_("report tags")), + OPT_BOOLEAN(0, "root", &show_root, N_("report root nodes")), + OPT_BOOLEAN(0, "cache", &keep_cache_objects, N_("make index objects head nodes")), + OPT_BOOLEAN(0, "reflogs", &include_reflogs, N_("make reflogs head nodes (default)")), + OPT_BOOLEAN(0, "full", &check_full, N_("also consider packs and alternate objects")), + OPT_BOOLEAN(0, "strict", &check_strict, N_("enable more strict checking")), OPT_BOOLEAN(0, "lost-found", &write_lost_and_found, - "write dangling objects in .git/lost-found"), + N_("write dangling objects in .git/lost-found")), + OPT_BOOL(0, "progress", &show_progress, N_("show progress")), OPT_END(), }; @@ -606,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; @@ -625,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 0498094711..6be6c8d65b 100644 --- a/builtin/gc.c +++ b/builtin/gc.c @@ -14,11 +14,12 @@ #include "cache.h" #include "parse-options.h" #include "run-command.h" +#include "argv-array.h" #define FAILED_RUN "failed to run %s" static const char * const builtin_gc_usage[] = { - "git gc [options]", + N_("git gc [options]"), NULL }; @@ -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) { @@ -67,19 +67,6 @@ static int gc_config(const char *var, const char *value, void *cb) 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) { /* @@ -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,21 +172,26 @@ 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, "suppress progress reporting"), - { OPTION_STRING, 0, "prune", &prune_expire, "date", - "prune unreferenced objects", + OPT__QUIET(&quiet, N_("suppress progress reporting")), + { OPTION_STRING, 0, "prune", &prune_expire, N_("date"), + N_("prune unreferenced objects"), PARSE_OPT_OPTARG, NULL, (intptr_t)prune_expire }, - OPT_BOOLEAN(0, "aggressive", &aggressive, "be more thorough (increased runtime)"), - OPT_BOOLEAN(0, "auto", &auto_gc, "enable auto-gc mode"), + OPT_BOOLEAN(0, "aggressive", &aggressive, N_("be more thorough (increased runtime)")), + OPT_BOOLEAN(0, "auto", &auto_gc, N_("enable auto-gc mode")), 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) @@ -203,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) { /* @@ -219,36 +217,33 @@ int cmd_gc(int argc, const char **argv, const char *prefix) */ if (!need_to_gc()) return 0; - if (quiet) - fprintf(stderr, _("Auto packing the repository for optimum performance.\n")); - else + if (!quiet) 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; " diff --git a/builtin/grep.c b/builtin/grep.c index 871afaa3c7..0e1b6c860e 100644 --- a/builtin/grep.c +++ b/builtin/grep.c @@ -17,10 +17,9 @@ #include "grep.h" #include "quote.h" #include "dir.h" -#include "thread-utils.h" static char const * const grep_usage[] = { - "git grep [options] [-e] <pattern> [<rev>...] [[--] <path>...]", + N_("git grep [options] [-e] <pattern> [<rev>...] [[--] <path>...]"), NULL }; @@ -30,25 +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 grep_source source; char done; struct strbuf out; }; @@ -74,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; @@ -93,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 char *path, const void *id) { grep_lock(); @@ -104,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, path, 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); @@ -134,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; @@ -160,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) @@ -189,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); @@ -227,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); @@ -240,6 +209,7 @@ static void start_threads(struct grep_opt *opt) int err; struct grep_opt *o = grep_opt_dup(opt); o->output = strbuf_out; + o->debug = 0; compile_grep_patterns(o); err = pthread_create(&threads[i], NULL, run, o); @@ -274,16 +244,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) { @@ -291,87 +261,29 @@ static int wait_all(void) } #endif -static int grep_config(const char *var, const char *value, void *cb) +static int grep_cmd_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 (!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); - else if (!strcmp(var, "color.grep.context")) - color = opt->color_context; - else if (!strcmp(var, "color.grep.filename")) - color = opt->color_filename; - else if (!strcmp(var, "color.grep.function")) - color = opt->color_function; - else if (!strcmp(var, "color.grep.linenumber")) - color = opt->color_lineno; - else if (!strcmp(var, "color.grep.match")) - color = opt->color_match; - else if (!strcmp(var, "color.grep.selected")) - color = opt->color_selected; - else if (!strcmp(var, "color.grep.separator")) - color = opt->color_sep; - else - return git_color_default_config(var, value, cb); - if (color) { - if (!value) - return config_error_nonbool(var); - color_parse(value, var, color); - } - return 0; + int st = grep_config(var, value, cb); + if (git_color_default_config(var, value, cb) < 0) + st = -1; + return st; } 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; } static int grep_sha1(struct grep_opt *opt, const unsigned char *sha1, - const char *filename, int tree_name_len) + const char *filename, int tree_name_len, + const char *path) { struct strbuf pathbuf = STRBUF_INIT; - char *name; if (opt->relative && opt->prefix_length) { quote_path_relative(filename + tree_name_len, -1, &pathbuf, @@ -381,87 +293,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, path, 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, path, 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 NULL; - } - if (!S_ISREG(st.st_mode)) - return NULL; - *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 NULL; + 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, 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, filename); + strbuf_release(&buf); + hit = grep_source(opt, &gs); + + grep_source_clear(&gs); return hit; } } @@ -513,7 +389,7 @@ static int grep_cache(struct grep_opt *opt, const struct pathspec *pathspec, int if (cached || (ce->ce_flags & CE_VALID) || ce_skip_worktree(ce)) { if (ce_stage(ce)) continue; - hit |= grep_sha1(opt, ce->sha1, ce->name, 0); + hit |= grep_sha1(opt, ce->sha1, ce->name, 0, ce->name); } else hit |= grep_file(opt, ce->name); @@ -531,27 +407,30 @@ static int grep_cache(struct grep_opt *opt, const struct pathspec *pathspec, int } static int grep_tree(struct grep_opt *opt, const struct pathspec *pathspec, - struct tree_desc *tree, struct strbuf *base, int tn_len) + struct tree_desc *tree, struct strbuf *base, int tn_len, + int check_attr) { - int hit = 0, match = 0; + int hit = 0; + enum interesting match = entry_not_interesting; struct name_entry entry; int old_baselen = base->len; while (tree_entry(tree, &entry)) { - int te_len = tree_entry_len(entry.path, entry.sha1); + int te_len = tree_entry_len(&entry); - if (match != 2) { + if (match != all_entries_interesting) { match = tree_entry_interesting(&entry, base, tn_len, pathspec); - if (match < 0) + if (match == all_entries_not_interesting) break; - if (match == 0) + if (match == entry_not_interesting) continue; } strbuf_add(base, entry.path, te_len); if (S_ISREG(entry.mode)) { - hit |= grep_sha1(opt, entry.sha1, base->buf, tn_len); + hit |= grep_sha1(opt, entry.sha1, base->buf, tn_len, + check_attr ? base->buf + tn_len : NULL); } else if (S_ISDIR(entry.mode)) { enum object_type type; @@ -566,7 +445,8 @@ static int grep_tree(struct grep_opt *opt, const struct pathspec *pathspec, strbuf_addch(base, '/'); init_tree_desc(&sub, data, size); - hit |= grep_tree(opt, pathspec, &sub, base, tn_len); + hit |= grep_tree(opt, pathspec, &sub, base, tn_len, + check_attr); free(data); } strbuf_setlen(base, old_baselen); @@ -581,7 +461,7 @@ static int grep_object(struct grep_opt *opt, const struct pathspec *pathspec, struct object *obj, const char *name) { if (obj->type == OBJ_BLOB) - return grep_sha1(opt, obj->sha1, name, 0); + return grep_sha1(opt, obj->sha1, name, 0, NULL); if (obj->type == OBJ_COMMIT || obj->type == OBJ_TREE) { struct tree_desc tree; void *data; @@ -589,8 +469,11 @@ static int grep_object(struct grep_opt *opt, const struct pathspec *pathspec, 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)); @@ -601,7 +484,8 @@ static int grep_object(struct grep_opt *opt, const struct pathspec *pathspec, strbuf_addch(&base, ':'); } init_tree_desc(&tree, data, size); - hit = grep_tree(opt, pathspec, &tree, &base, base.len); + hit = grep_tree(opt, pathspec, &tree, &base, base.len, + obj->type == OBJ_COMMIT); strbuf_release(&base); free(data); return hit; @@ -628,13 +512,15 @@ static int grep_objects(struct grep_opt *opt, const struct pathspec *pathspec, return hit; } -static int grep_directory(struct grep_opt *opt, const struct pathspec *pathspec) +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, pathspec->raw); for (i = 0; i < dir.nr; i++) { @@ -681,15 +567,12 @@ static int file_callback(const struct option *opt, const char *arg, int unset) if (!patterns) 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); } if (!from_stdin) fclose(patterns); @@ -741,7 +624,7 @@ 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"; @@ -753,85 +636,88 @@ int cmd_grep(int argc, const char **argv, const char *prefix) 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; + int pattern_type_arg = GREP_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"), + N_("search in index instead of in the work tree")), + OPT_NEGBIT(0, "no-index", &use_index, + N_("find in contents not managed by git"), 1), + OPT_BOOLEAN(0, "untracked", &untracked, + N_("search in both tracked and untracked files")), + OPT_SET_INT(0, "exclude-standard", &opt_exclude, + N_("search also in ignored files"), 1), OPT_GROUP(""), OPT_BOOLEAN('v', "invert-match", &opt.invert, - "show non-matching lines"), + N_("show non-matching lines")), OPT_BOOLEAN('i', "ignore-case", &opt.ignore_case, - "case insensitive matching"), + N_("case insensitive matching")), OPT_BOOLEAN('w', "word-regexp", &opt.word_regexp, - "match patterns only at word boundaries"), + N_("match patterns only at word boundaries")), OPT_SET_INT('a', "text", &opt.binary, - "process binary files as text", GREP_BINARY_TEXT), + N_("process binary files as text"), GREP_BINARY_TEXT), OPT_SET_INT('I', NULL, &opt.binary, - "don't match patterns in binary files", + N_("don't match patterns in binary files"), GREP_BINARY_NOMATCH), - { OPTION_INTEGER, 0, "max-depth", &opt.max_depth, "depth", - "descend at most <depth> levels", PARSE_OPT_NONEG, + { OPTION_INTEGER, 0, "max-depth", &opt.max_depth, N_("depth"), + N_("descend at most <depth> levels"), PARSE_OPT_NONEG, NULL, 1 }, OPT_GROUP(""), - 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_SET_INT('E', "extended-regexp", &pattern_type_arg, + N_("use extended POSIX regular expressions"), + GREP_PATTERN_TYPE_ERE), + OPT_SET_INT('G', "basic-regexp", &pattern_type_arg, + N_("use basic POSIX regular expressions (default)"), + GREP_PATTERN_TYPE_BRE), + OPT_SET_INT('F', "fixed-strings", &pattern_type_arg, + N_("interpret patterns as fixed strings"), + GREP_PATTERN_TYPE_FIXED), + OPT_SET_INT('P', "perl-regexp", &pattern_type_arg, + N_("use Perl-compatible regular expressions"), + GREP_PATTERN_TYPE_PCRE), OPT_GROUP(""), - 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_BOOLEAN('n', "line-number", &opt.linenum, N_("show line numbers")), + OPT_NEGBIT('h', NULL, &opt.pathname, N_("don't show filenames"), 1), + OPT_BIT('H', NULL, &opt.pathname, N_("show filenames"), 1), OPT_NEGBIT(0, "full-name", &opt.relative, - "show filenames relative to top directory", 1), + N_("show filenames relative to top directory"), 1), OPT_BOOLEAN('l', "files-with-matches", &opt.name_only, - "show only filenames instead of matching lines"), + N_("show only filenames instead of matching lines")), OPT_BOOLEAN(0, "name-only", &opt.name_only, - "synonym for --files-with-matches"), + N_("synonym for --files-with-matches")), OPT_BOOLEAN('L', "files-without-match", &opt.unmatch_name_only, - "show only the names of files without match"), + N_("show only the names of files without match")), OPT_BOOLEAN('z', "null", &opt.null_following_name, - "print NUL after filenames"), + N_("print NUL after filenames")), OPT_BOOLEAN('c', "count", &opt.count, - "show the number of matches instead of matching lines"), - OPT__COLOR(&opt.color, "highlight matches"), + N_("show the number of matches instead of matching lines")), + OPT__COLOR(&opt.color, N_("highlight matches")), + OPT_BOOLEAN(0, "break", &opt.file_break, + N_("print empty line between matches from different files")), + OPT_BOOLEAN(0, "heading", &opt.heading, + N_("show filename only once above matches from same file")), OPT_GROUP(""), - OPT_CALLBACK('C', NULL, &opt, "n", - "show <n> context lines before and after matches", + OPT_CALLBACK('C', "context", &opt, N_("n"), + N_("show <n> context lines before and after matches"), context_callback), - OPT_INTEGER('B', NULL, &opt.pre_context, - "show <n> context lines before matches"), - OPT_INTEGER('A', NULL, &opt.post_context, - "show <n> context lines after matches"), - OPT_NUMBER_CALLBACK(&opt, "shortcut for -C NUM", + OPT_INTEGER('B', "before-context", &opt.pre_context, + N_("show <n> context lines before matches")), + OPT_INTEGER('A', "after-context", &opt.post_context, + N_("show <n> context lines after matches")), + OPT_NUMBER_CALLBACK(&opt, N_("shortcut for -C NUM"), context_callback), OPT_BOOLEAN('p', "show-function", &opt.funcname, - "show a line with the function name before matches"), + N_("show a line with the function name before matches")), + OPT_BOOLEAN('W', "function-context", &opt.funcbody, + N_("show the surrounding function")), OPT_GROUP(""), - OPT_CALLBACK('f', NULL, &opt, "file", - "read patterns from file", file_callback), - { OPTION_CALLBACK, 'e', NULL, &opt, "pattern", - "match <pattern>", PARSE_OPT_NONEG, pattern_callback }, + OPT_CALLBACK('f', NULL, &opt, N_("file"), + N_("read patterns from file"), file_callback), + { OPTION_CALLBACK, 'e', NULL, &opt, N_("pattern"), + N_("match <pattern>"), PARSE_OPT_NONEG, pattern_callback }, { OPTION_CALLBACK, 0, "and", &opt, NULL, - "combine patterns specified with -e", + N_("combine patterns specified with -e"), PARSE_OPT_NOARG | PARSE_OPT_NONEG, and_callback }, OPT_BOOLEAN(0, "or", &dummy, ""), { OPTION_CALLBACK, 0, "not", &opt, NULL, "", @@ -843,16 +729,19 @@ int cmd_grep(int argc, const char **argv, const char *prefix) PARSE_OPT_NOARG | PARSE_OPT_NONEG | PARSE_OPT_NODASH, close_callback }, OPT__QUIET(&opt.status_only, - "indicate hit with exit status without output"), + N_("indicate hit with exit status without output")), OPT_BOOLEAN(0, "all-match", &opt.all_match, - "show only matches from files that match all patterns"), + N_("show only matches from files that match all patterns")), + { OPTION_SET_INT, 0, "debug", &opt.debug, NULL, + N_("show parse tree for grep expression"), + PARSE_OPT_NOARG | PARSE_OPT_HIDDEN, NULL, 1 }, OPT_GROUP(""), { OPTION_STRING, 'O', "open-files-in-pager", &show_in_pager, - "pager", "show matching files in the pager", + N_("pager"), N_("show matching files in the pager"), PARSE_OPT_OPTARG, NULL, (intptr_t)default_pager }, OPT_BOOLEAN(0, "ext-grep", &external_grep_allowed__ignored, - "allow calling of grep(1) (ignored by this build)"), - { OPTION_CALLBACK, 0, "help-all", &options, NULL, "show usage", + N_("allow calling of grep(1) (ignored by this build)")), + { OPTION_CALLBACK, 0, "help-all", &options, NULL, N_("show usage"), PARSE_OPT_HIDDEN | PARSE_OPT_NOARG, help_callback }, OPT_END() }; @@ -864,27 +753,9 @@ int cmd_grep(int argc, const char **argv, const char *prefix) if (argc == 2 && !strcmp(argv[1], "-h")) usage_with_options(grep_usage, options); - memset(&opt, 0, sizeof(opt)); - opt.prefix = prefix; - opt.prefix_length = (prefix && *prefix) ? strlen(prefix) : 0; - opt.relative = 1; - opt.pathname = 1; - opt.pattern_tail = &opt.pattern_list; - opt.header_tail = &opt.header_list; - opt.regflags = REG_NEWLINE; - opt.max_depth = -1; - - strcpy(opt.color_context, ""); - strcpy(opt.color_filename, ""); - strcpy(opt.color_function, ""); - strcpy(opt.color_lineno, ""); - strcpy(opt.color_match, GIT_COLOR_BOLD_RED); - strcpy(opt.color_selected, ""); - 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; + init_grep_defaults(); + git_config(grep_cmd_config, NULL); + grep_init(&opt, prefix); /* * If there is no -- then the paths must exist in the working @@ -900,28 +771,7 @@ 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 */ - } + grep_commit_pattern_type(pattern_type_arg, &opt); if (use_index && !startup_info->have_repository) /* die the same way as if we did it at the beginning */ @@ -962,19 +812,6 @@ int cmd_grep(int argc, const char **argv, const char *prefix) if (!opt.fixed && opt.ignore_case) opt.regflags |= REG_ICASE; -#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); /* Check revs and then paths */ @@ -996,11 +833,28 @@ 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; for (j = i; j < argc; j++) - verify_filename(prefix, argv[j]); + verify_filename(prefix, argv[j], j == i); } paths = get_pathspec(prefix, argv + i); @@ -1031,13 +885,16 @@ 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, &pathspec); + 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(); diff --git a/builtin/hash-object.c b/builtin/hash-object.c index 33911fd5e9..8d184f1a99 100644 --- a/builtin/hash-object.c +++ b/builtin/hash-object.c @@ -57,8 +57,8 @@ static void hash_stdin_paths(const char *type, int write_objects) } static const char * const hash_object_usage[] = { - "git hash-object [-t <type>] [-w] [--path=<file>|--no-filters] [--stdin] [--] <file>...", - "git hash-object --stdin-paths < <list-of-paths>", + N_("git hash-object [-t <type>] [-w] [--path=<file>|--no-filters] [--stdin] [--] <file>..."), + N_("git hash-object --stdin-paths < <list-of-paths>"), NULL }; @@ -69,12 +69,12 @@ static int stdin_paths; static const char *vpath; static const struct option hash_object_options[] = { - OPT_STRING('t', NULL, &type, "type", "object type"), - OPT_BOOLEAN('w', NULL, &write_object, "write the object into the object database"), - OPT_BOOLEAN( 0 , "stdin", &hashstdin, "read the object from stdin"), - OPT_BOOLEAN( 0 , "stdin-paths", &stdin_paths, "read file names from stdin"), - OPT_BOOLEAN( 0 , "no-filters", &no_filters, "store file as is without filters"), - OPT_STRING( 0 , "path", &vpath, "file", "process file as it were from this path"), + OPT_STRING('t', NULL, &type, N_("type"), N_("object type")), + OPT_BOOLEAN('w', NULL, &write_object, N_("write the object into the object database")), + OPT_BOOLEAN( 0 , "stdin", &hashstdin, N_("read the object from stdin")), + OPT_BOOLEAN( 0 , "stdin-paths", &stdin_paths, N_("read file names from stdin")), + OPT_BOOLEAN( 0 , "no-filters", &no_filters, N_("store file as is without filters")), + OPT_STRING( 0 , "path", &vpath, N_("file"), N_("process file as it were from this path")), OPT_END() }; diff --git a/builtin/help.c b/builtin/help.c index 61ff79839b..6067a6134b 100644 --- a/builtin/help.c +++ b/builtin/help.c @@ -6,11 +6,15 @@ #include "cache.h" #include "builtin.h" #include "exec_cmd.h" -#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]; @@ -29,20 +33,23 @@ enum help_format { HELP_FORMAT_WEB }; +static const char *html_path; + 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"), - OPT_SET_INT('m', "man", &help_format, "show man page", HELP_FORMAT_MAN), - OPT_SET_INT('w', "web", &help_format, "show manual in web browser", + OPT_BOOLEAN('a', "all", &show_all, N_("print all available commands")), + OPT_SET_INT('m', "man", &help_format, N_("show man page"), HELP_FORMAT_MAN), + OPT_SET_INT('w', "web", &help_format, N_("show manual in web browser"), HELP_FORMAT_WEB), - OPT_SET_INT('i', "info", &help_format, "show info page", + OPT_SET_INT('i', "info", &help_format, N_("show info page"), HELP_FORMAT_INFO), OPT_END(), }; static const char * const builtin_help_usage[] = { - "git help [--all] [--man|--web|--info] [command]", + N_("git help [--all] [--man|--web|--info] [command]"), NULL }; @@ -54,7 +61,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 +89,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 +102,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 +110,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 +128,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 +156,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 +165,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 +173,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 +213,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 +225,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,12 +258,20 @@ 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); help_format = parse_help_format(value); return 0; } + if (!strcmp(var, "help.htmlpath")) { + if (!value) + return config_error_nonbool(var); + html_path = xstrdup(value); + return 0; + } if (!strcmp(var, "man.viewer")) { if (!value) return config_error_nonbool(var); @@ -271,23 +286,6 @@ static int git_help_config(const char *var, const char *value, void *cb) static struct cmdnames main_cmds, other_cmds; -void list_common_cmds_help(void) -{ - int i, longest = 0; - - for (i = 0; i < ARRAY_SIZE(common_cmds); i++) { - if (longest < strlen(common_cmds[i].name)) - longest = strlen(common_cmds[i].name); - } - - puts("The most commonly used git commands are:"); - for (i = 0; i < ARRAY_SIZE(common_cmds); i++) { - printf(" %s ", common_cmds[i].name); - mput_char(' ', longest - strlen(common_cmds[i].name)); - puts(common_cmds[i].help); - } -} - static int is_git_command(const char *s) { return is_in_cmdlist(&main_cmds, s) || @@ -348,7 +346,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 +363,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,18 +371,21 @@ 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) { struct stat st; - const char *html_path = system_path(GIT_HTML_PATH); + if (!html_path) + html_path = system_path(GIT_HTML_PATH); /* Check that we have a git documentation directory. */ - if (stat(mkpath("%s/git.html", html_path), &st) - || !S_ISREG(st.st_mode)) - die("'%s': not a documentation directory.", html_path); + if (!strstr(html_path, "://")) { + if (stat(mkpath("%s/git.html", html_path), &st) + || !S_ISREG(st.st_mode)) + die("'%s': not a documentation directory.", html_path); + } strbuf_init(page_path, 0); strbuf_addf(page_path, "%s/%s.html", html_path, page); @@ -424,16 +425,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 +444,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 81cdc28b30..ef62124aa4 100644 --- a/builtin/index-pack.c +++ b/builtin/index-pack.c @@ -9,9 +9,11 @@ #include "progress.h" #include "fsck.h" #include "exec_cmd.h" +#include "streaming.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 pack_idx_entry idx; @@ -19,6 +21,8 @@ struct object_entry { unsigned int hdr_size; enum object_type type; enum object_type real_type; + unsigned delta_depth; + int base_object_no; }; union delta_base { @@ -32,6 +36,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_THREAD_SAFE_PREAD) +/* pread() emulation 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; }; /* @@ -50,11 +69,11 @@ struct delta_entry { 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; @@ -66,17 +85,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; @@ -96,7 +187,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; } @@ -133,15 +224,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) @@ -153,14 +247,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; } @@ -169,19 +263,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; } @@ -195,9 +289,9 @@ 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", + die(_("pack version %"PRIu32" unsupported"), ntohl(hdr->hdr_version)); nr_objects = ntohl(hdr->hdr_entries); @@ -215,7 +309,35 @@ static NORETURN 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) @@ -223,15 +345,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); @@ -243,12 +366,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); } @@ -258,34 +381,66 @@ 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) +static int is_delta_type(enum object_type type) +{ + return (type == OBJ_REF_DELTA || type == OBJ_OFS_DELTA); +} + +static void *unpack_entry_data(unsigned long offset, unsigned long size, + enum object_type type, unsigned char *sha1) { + static char fixed_buf[8192]; int status; git_zstream stream; - void *buf = xmalloc(size); + void *buf; + git_SHA_CTX c; + char hdr[32]; + int hdrlen; + + if (!is_delta_type(type)) { + hdrlen = sprintf(hdr, "%s %lu", typename(type), size) + 1; + git_SHA1_Init(&c); + git_SHA1_Update(&c, hdr, hdrlen); + } else + sha1 = NULL; + if (type == OBJ_BLOB && size > big_file_threshold) + buf = fixed_buf; + else + buf = xmalloc(size); memset(&stream, 0, sizeof(stream)); git_inflate_init(&stream); stream.next_out = buf; - stream.avail_out = size; + stream.avail_out = buf == fixed_buf ? sizeof(fixed_buf) : size; do { + unsigned char *last_out = stream.next_out; stream.next_in = fill(1); stream.avail_in = input_len; status = git_inflate(&stream, 0); use(input_len - stream.avail_in); + if (sha1) + git_SHA1_Update(&c, last_out, stream.next_out - last_out); + if (buf == fixed_buf) { + stream.next_out = buf; + stream.avail_out = sizeof(fixed_buf); + } } 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; + if (sha1) + git_SHA1_Final(sha1, &c); + return buf == fixed_buf ? NULL : buf; } -static void *unpack_raw_entry(struct object_entry *obj, union delta_base *delta_base) +static void *unpack_raw_entry(struct object_entry *obj, + union delta_base *delta_base, + unsigned char *sha1) { unsigned char *p; unsigned long size, c; @@ -325,7 +480,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); @@ -333,7 +488,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: @@ -341,16 +496,18 @@ 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; - data = unpack_entry_data(obj->idx.offset, obj->size); + data = unpack_entry_data(obj->idx.offset, obj->size, obj->type, sha1); obj->idx.crc32 = input_crc32; return data; } -static void *get_data_from_pack(struct object_entry *obj) +static void *unpack_data(struct object_entry *obj, + int (*consume)(const unsigned char *, unsigned long, void *), + void *cb_data) { off_t from = obj[0].idx.offset + obj[0].hdr_size; unsigned long len = obj[1].idx.offset - from; @@ -358,38 +515,74 @@ static void *get_data_from_pack(struct object_entry *obj) git_zstream stream; int status; - data = xmalloc(obj->size); + data = xmalloc(consume ? 64*1024 : obj->size); inbuf = xmalloc((len < 64*1024) ? len : 64*1024); memset(&stream, 0, sizeof(stream)); git_inflate_init(&stream); stream.next_out = data; - stream.avail_out = obj->size; + stream.avail_out = consume ? 64*1024 : obj->size; do { 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; stream.avail_in = n; - status = git_inflate(&stream, 0); + if (!consume) + status = git_inflate(&stream, 0); + else { + do { + status = git_inflate(&stream, 0); + if (consume(data, stream.next_out - data, cb_data)) { + free(inbuf); + free(data); + return NULL; + } + stream.next_out = data; + stream.avail_out = 64*1024; + } while (status == Z_OK && stream.avail_in); + } } while (len && status == Z_OK && !stream.avail_in); /* 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); + if (consume) { + free(data); + data = NULL; + } return data; } -static int find_delta(const union delta_base *base) +static void *get_data_from_pack(struct object_entry *obj) +{ + return unpack_data(obj, NULL, NULL); +} + +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; @@ -398,7 +591,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) { @@ -411,9 +605,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; @@ -430,45 +625,130 @@ static void find_delta_children(const union delta_base *base, *last_index = last; } -static void sha1_object(const void *data, unsigned long size, - enum object_type type, unsigned char *sha1) +struct compare_data { + struct object_entry *entry; + struct git_istream *st; + unsigned char *buf; + unsigned long buf_size; +}; + +static int compare_objects(const unsigned char *buf, unsigned long size, + void *cb_data) +{ + struct compare_data *data = cb_data; + + if (data->buf_size < size) { + free(data->buf); + data->buf = xmalloc(size); + data->buf_size = size; + } + + while (size) { + ssize_t len = read_istream(data->st, data->buf, size); + if (len == 0) + die(_("SHA1 COLLISION FOUND WITH %s !"), + sha1_to_hex(data->entry->idx.sha1)); + if (len < 0) + die(_("unable to read %s"), + sha1_to_hex(data->entry->idx.sha1)); + if (memcmp(buf, data->buf, len)) + die(_("SHA1 COLLISION FOUND WITH %s !"), + sha1_to_hex(data->entry->idx.sha1)); + size -= len; + buf += len; + } + return 0; +} + +static int check_collison(struct object_entry *entry) { - hash_sha1_file(data, size, typename(type), sha1); - if (has_sha1_file(sha1)) { + struct compare_data data; + enum object_type type; + unsigned long size; + + if (entry->size <= big_file_threshold || entry->type != OBJ_BLOB) + return -1; + + memset(&data, 0, sizeof(data)); + data.entry = entry; + data.st = open_istream(entry->idx.sha1, &type, &size, NULL); + if (!data.st) + return -1; + if (size != entry->size || type != entry->type) + die(_("SHA1 COLLISION FOUND WITH %s !"), + sha1_to_hex(entry->idx.sha1)); + unpack_data(entry, compare_objects, &data); + close_istream(data.st); + free(data.buf); + return 0; +} + +static void sha1_object(const void *data, struct object_entry *obj_entry, + unsigned long size, enum object_type type, + const unsigned char *sha1) +{ + void *new_data = NULL; + int collision_test_needed; + + assert(data || obj_entry); + + read_lock(); + collision_test_needed = has_sha1_file(sha1); + read_unlock(); + + if (collision_test_needed && !data) { + read_lock(); + if (!check_collison(obj_entry)) + collision_test_needed = 0; + read_unlock(); + } + if (collision_test_needed) { void *has_data; enum object_type has_type; unsigned long has_size; + read_lock(); + has_type = sha1_object_info(sha1, &has_size); + if (has_type != type || has_size != size) + die(_("SHA1 COLLISION FOUND WITH %s !"), sha1_to_hex(sha1)); has_data = read_sha1_file(sha1, &has_type, &has_size); + read_unlock(); + if (!data) + data = new_data = get_data_from_pack(obj_entry); 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); } + 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; void *buf = (void *) data; + if (!buf) + buf = new_data = get_data_from_pack(obj_entry); + /* * we do not need to free the memory here, as the * buf is deleted by the caller. */ obj = parse_object_buffer(sha1, type, size, buf, &eaten); if (!obj) - die("invalid %s", typename(type)); + 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; @@ -480,31 +760,69 @@ static void sha1_object(const void *data, unsigned long size, } obj->flags |= FLAG_CHECKED; } + read_unlock(); } + + free(new_data); } +/* + * 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; } @@ -515,6 +833,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; @@ -522,98 +844,161 @@ 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"); - sha1_object(result->data, result->size, delta_obj->real_type, + bad_object(delta_obj->idx.offset, _("failed to apply delta")); + hash_sha1_file(result->data, result->size, + typename(delta_obj->real_type), delta_obj->idx.sha1); + sha1_object(result->data, NULL, 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); } -/* Parse all objects and return the pack content SHA1 hash */ +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); +} + +#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; + int i, nr_delays = 0; 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); + void *data = unpack_raw_entry(obj, &delta->base, obj->idx.sha1); 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++; + } else if (!data) { + /* large blobs, check later */ + obj->real_type = OBJ_BAD; + nr_delays++; } else - sha1_object(data, obj->size, obj->type, obj->idx.sha1); + sha1_object(data, NULL, obj->size, obj->type, obj->idx.sha1); free(data); display_progress(progress, i+1); } @@ -624,15 +1009,39 @@ 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")); + + for (i = 0; i < nr_objects; i++) { + struct object_entry *obj = &objects[i]; + if (obj->real_type != OBJ_BAD) + continue; + obj->real_type = obj->type; + sha1_object(NULL, obj, obj->size, obj->type, obj->idx.sha1); + nr_delays--; + } + if (nr_delays) + die(_("confusion beyond insanity in parse_pack_objects()")); +} + +/* + * 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; @@ -641,29 +1050,85 @@ 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]; + struct strbuf msg = STRBUF_INIT; + 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); + strbuf_addf(&msg, _("completed with %d local objects"), + nr_objects - nr_objects_initial); + stop_progress_msg(&progress, msg.buf); + strbuf_release(&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) { git_zstream stream; @@ -683,7 +1148,7 @@ static int write_compressed(struct sha1file *f, void *in, unsigned int size) } 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; git_deflate_end(&stream); return size; @@ -752,20 +1217,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); @@ -786,7 +1251,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) { @@ -799,7 +1264,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) { @@ -807,7 +1272,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"; } @@ -820,7 +1285,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); @@ -831,7 +1296,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); @@ -859,24 +1324,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")) @@ -884,9 +1477,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]; @@ -898,10 +1492,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; @@ -910,10 +1524,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; @@ -923,13 +1537,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; @@ -943,11 +1557,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); @@ -957,67 +1571,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 d07554c884..78aa3872dd 100644 --- a/builtin/init-db.c +++ b/builtin/init-db.c @@ -290,6 +290,7 @@ static int create_default_files(const char *template_path) strcpy(path + len, "CoNfIg"); if (!access(path, F_OK)) git_config_set("core.ignorecase", "true"); + probe_utf8_pathname_composition(path, len); } return reinit; @@ -351,7 +352,7 @@ static void separate_git_dir(const char *git_dir) else if (S_ISDIR(st.st_mode)) src = git_link; else - die(_("unable to handle file type %d"), st.st_mode); + 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); @@ -463,7 +464,7 @@ static int shared_callback(const struct option *opt, const char *arg, int unset) } static const char *const init_db_usage[] = { - "git init [-q | --quiet] [--bare] [--template=<template-directory>] [--shared[=<permissions>]] [directory]", + N_("git init [-q | --quiet] [--bare] [--template=<template-directory>] [--shared[=<permissions>]] [directory]"), NULL }; @@ -481,17 +482,17 @@ int cmd_init_db(int argc, const char **argv, const char *prefix) const char *template_dir = NULL; unsigned int flags = 0; const struct option init_db_options[] = { - OPT_STRING(0, "template", &template_dir, "template-directory", - "directory from which templates will be used"), + OPT_STRING(0, "template", &template_dir, N_("template-directory"), + N_("directory from which templates will be used")), OPT_SET_INT(0, "bare", &is_bare_repository_cfg, - "create a bare repository", 1), + N_("create a bare repository"), 1), { OPTION_CALLBACK, 0, "shared", &init_shared_repository, - "permissions", - "specify that the git repository is to be shared amongst several users", + N_("permissions"), + N_("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_BIT('q', "quiet", &flags, N_("be quiet"), INIT_DB_QUIET), + OPT_STRING(0, "separate-git-dir", &real_git_dir, N_("gitdir"), + N_("separate git dir from working tree")), OPT_END() }; diff --git a/builtin/log.c b/builtin/log.c index 5c2af59004..e7b7db1cac 100644 --- a/builtin/log.c +++ b/builtin/log.c @@ -19,6 +19,9 @@ #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; @@ -31,8 +34,8 @@ static const char *fmt_patch_subject_prefix = "PATCH"; static const char *fmt_pretty; static const char * const builtin_log_usage[] = { - "git log [<options>] [<since>..<until>] [[--] <path>...]\n" - " or: git show [options] <object>...", + N_("git log [<options>] [<since>..<until>] [[--] <path>...]\n") + N_(" or: git show [options] <object>..."), NULL }; @@ -72,12 +75,12 @@ static int decorate_callback(const struct option *opt, const char *arg, int unse static void cmd_log_init_defaults(struct rev_info *rev) { - rev->abbrev = DEFAULT_ABBREV; - rev->commit_format = CMIT_FMT_DEFAULT; if (fmt_pretty) get_commit_format(fmt_pretty, rev); rev->verbose_header = 1; DIFF_OPT_SET(&rev->diffopt, RECURSIVE); + rev->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; @@ -94,9 +97,9 @@ static void cmd_log_init_finish(int argc, const char **argv, const char *prefix, 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", + OPT_BOOLEAN(0, "quiet", &quiet, N_("suppress diff output")), + OPT_BOOLEAN(0, "source", &source, N_("show source")), + { OPTION_CALLBACK, 0, "decorate", NULL, NULL, N_("decorate options"), PARSE_OPT_OPTARG, decorate_callback}, OPT_END() }; @@ -106,9 +109,9 @@ static void cmd_log_init_finish(int argc, const char **argv, const char *prefix, PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN | PARSE_OPT_KEEP_DASHDASH); - argc = setup_revisions(argc, argv, rev, opt); if (quiet) rev->diffopt.output_format |= DIFF_FORMAT_NO_OUTPUT; + argc = setup_revisions(argc, argv, rev, opt); /* Any arguments at this point are not recognized */ if (argc > 1) @@ -348,7 +351,8 @@ static int git_log_config(const char *var, const char *value, void *cb) } if (!prefixcmp(var, "color.decorate.")) return parse_decorate_color_config(var, 15, value); - + if (grep_config(var, value, cb) < 0) + return -1; return git_diff_ui_config(var, value, cb); } @@ -357,16 +361,15 @@ int cmd_whatchanged(int argc, const char **argv, const char *prefix) struct rev_info rev; struct setup_revision_opt opt; + init_grep_defaults(); 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; memset(&opt, 0, sizeof(opt)); opt.def = "HEAD"; + opt.revarg_opt = REVARG_COMMITTISH; cmd_log_init(argc, argv, prefix, &rev, &opt); if (!rev.diffopt.output_format) rev.diffopt.output_format = DIFF_FORMAT_RAW; @@ -385,8 +388,13 @@ static void show_tagger(char *buf, int len, struct rev_info *rev) 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; @@ -396,16 +404,16 @@ static int show_object(const unsigned char *sha1, int show_tag_object, 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; - } + 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); @@ -444,21 +452,24 @@ int cmd_show(int argc, const char **argv, const char *prefix) struct pathspec match_all; int i, count, ret = 0; + init_grep_defaults(); 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.no_walk = REVISION_WALK_NO_WALK_SORTED; + rev.diffopt.stat_width = -1; /* Scale to real terminal size */ + memset(&opt, 0, sizeof(opt)); opt.def = "HEAD"; opt.tweak = show_rev_tweak_rev; cmd_log_init(argc, argv, prefix, &rev, &opt); + if (!rev.no_walk) + return cmd_log_walk(&rev); + count = rev.pending.nr; objects = rev.pending.objects; for (i = 0; i < count && !ret; i++) { @@ -466,7 +477,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; @@ -477,7 +488,7 @@ 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; @@ -522,11 +533,9 @@ int cmd_log_reflog(int argc, const char **argv, const char *prefix) struct rev_info rev; struct setup_revision_opt opt; + init_grep_defaults(); 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.verbose_header = 1; @@ -547,15 +556,14 @@ int cmd_log(int argc, const char **argv, const char *prefix) struct rev_info rev; struct setup_revision_opt opt; + init_grep_defaults(); 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)); opt.def = "HEAD"; + opt.revarg_opt = REVARG_COMMITTISH; cmd_log_init(argc, argv, prefix, &rev, &opt); return cmd_log_walk(&rev); } @@ -620,7 +628,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")) { @@ -665,7 +674,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, int quiet) +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; @@ -679,7 +689,7 @@ static int reopen_stdout(struct commit *commit, struct rev_info *rev, int quiet) 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 (!quiet) fprintf(realstdout, "%s\n", filename.buf + outdir_offset); @@ -691,7 +701,7 @@ static int reopen_stdout(struct commit *commit, struct rev_info *rev, int quiet) return 0; } -static void get_patch_ids(struct rev_info *rev, struct patch_ids *ids, const char *prefix) +static void get_patch_ids(struct rev_info *rev, struct patch_ids *ids) { struct rev_info check_rev; struct commit *commit; @@ -712,7 +722,8 @@ static void get_patch_ids(struct rev_info *rev, struct patch_ids *ids, const cha init_patch_ids(ids); /* given a range a..b get all patch ids for b..a */ - init_revisions(&check_rev, prefix); + init_revisions(&check_rev, rev->prefix); + check_rev.max_parents = 1; o1->flags ^= UNINTERESTING; o2->flags ^= UNINTERESTING; add_pending_object(&check_rev, o1, "o1"); @@ -721,10 +732,6 @@ static void get_patch_ids(struct rev_info *rev, struct patch_ids *ids, const cha die(_("revision walk setup failed")); while ((commit = get_revision(&check_rev)) != NULL) { - /* ignore merges */ - if (commit->parents && commit->parents->next) - continue; - add_commit_patch_id(commit, ids); } @@ -739,15 +746,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); } @@ -757,10 +759,24 @@ 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, + const char *branch_name, int quiet) { const char *committer; @@ -772,7 +788,6 @@ static void make_cover_letter(struct rev_info *rev, int use_stdout, 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) @@ -780,31 +795,10 @@ static void make_cover_letter(struct rev_info *rev, int use_stdout, 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, quiet)) + 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, &pp.subject, &pp.after_subject, &need_8bit_cte); @@ -818,6 +812,7 @@ static void make_cover_letter(struct rev_info *rev, int use_stdout, 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); @@ -897,7 +892,7 @@ static const char *set_outdir(const char *prefix, const char *output_directory) } static const char * const builtin_format_patch_usage[] = { - "git format-patch [options] [<since> | <revision range>]", + N_("git format-patch [options] [<since> | <revision range>]"), NULL }; @@ -1017,6 +1012,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; @@ -1038,69 +1062,71 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) 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", + N_("use [PATCH n/m] even with a single patch"), PARSE_OPT_NOARG, numbered_callback }, { OPTION_CALLBACK, 'N', "no-numbered", &numbered, NULL, - "use [PATCH] even with multiple patches", + N_("use [PATCH] even with multiple patches"), PARSE_OPT_NOARG, no_numbered_callback }, - OPT_BOOLEAN('s', "signoff", &do_signoff, "add Signed-off-by:"), + OPT_BOOLEAN('s', "signoff", &do_signoff, N_("add Signed-off-by:")), OPT_BOOLEAN(0, "stdout", &use_stdout, - "print patches to standard out"), + N_("print patches to standard out")), OPT_BOOLEAN(0, "cover-letter", &cover_letter, - "generate a cover letter"), + N_("generate a cover letter")), OPT_BOOLEAN(0, "numbered-files", &numbered_files, - "use simple number sequence for output file names"), - OPT_STRING(0, "suffix", &fmt_patch_suffix, "sfx", - "use <sfx> instead of '.patch'"), + N_("use simple number sequence for output file names")), + OPT_STRING(0, "suffix", &fmt_patch_suffix, N_("sfx"), + N_("use <sfx> instead of '.patch'")), OPT_INTEGER(0, "start-number", &start_number, - "start numbering patches at <n> instead of 1"), - { OPTION_CALLBACK, 0, "subject-prefix", &rev, "prefix", - "Use [<prefix>] instead of [PATCH]", + N_("start numbering patches at <n> instead of 1")), + { OPTION_CALLBACK, 0, "subject-prefix", &rev, N_("prefix"), + N_("Use [<prefix>] instead of [PATCH]"), PARSE_OPT_NONEG, subject_prefix_callback }, { OPTION_CALLBACK, 'o', "output-directory", &output_directory, - "dir", "store resulting files in <dir>", + N_("dir"), N_("store resulting files in <dir>"), PARSE_OPT_NONEG, output_directory_callback }, { OPTION_CALLBACK, 'k', "keep-subject", &rev, NULL, - "don't strip/add [PATCH]", + N_("don't strip/add [PATCH]"), PARSE_OPT_NOARG | PARSE_OPT_NONEG, keep_callback }, OPT_BOOLEAN(0, "no-binary", &no_binary_diff, - "don't output binary diffs"), + N_("don't output binary diffs")), OPT_BOOLEAN(0, "ignore-if-in-upstream", &ignore_if_in_upstream, - "don't include a patch matching a commit upstream"), + N_("don't include a patch matching a commit upstream")), { OPTION_BOOLEAN, 'p', "no-stat", &use_patch_format, NULL, - "show patch format instead of default (patch + stat)", + N_("show patch format instead of default (patch + stat)"), PARSE_OPT_NONEG | PARSE_OPT_NOARG }, - OPT_GROUP("Messaging"), - { OPTION_CALLBACK, 0, "add-header", NULL, "header", - "add email header", 0, header_callback }, - { OPTION_CALLBACK, 0, "to", NULL, "email", "add To: header", + OPT_GROUP(N_("Messaging")), + { OPTION_CALLBACK, 0, "add-header", NULL, N_("header"), + N_("add email header"), 0, header_callback }, + { OPTION_CALLBACK, 0, "to", NULL, N_("email"), N_("add To: header"), 0, to_callback }, - { OPTION_CALLBACK, 0, "cc", NULL, "email", "add Cc: header", + { OPTION_CALLBACK, 0, "cc", NULL, N_("email"), N_("add Cc: header"), 0, cc_callback }, - OPT_STRING(0, "in-reply-to", &in_reply_to, "message-id", - "make first mail a reply to <message-id>"), - { OPTION_CALLBACK, 0, "attach", &rev, "boundary", - "attach the patch", PARSE_OPT_OPTARG, + OPT_STRING(0, "in-reply-to", &in_reply_to, N_("message-id"), + N_("make first mail a reply to <message-id>")), + { OPTION_CALLBACK, 0, "attach", &rev, N_("boundary"), + N_("attach the patch"), PARSE_OPT_OPTARG, attach_callback }, - { OPTION_CALLBACK, 0, "inline", &rev, "boundary", - "inline the patch", + { OPTION_CALLBACK, 0, "inline", &rev, N_("boundary"), + N_("inline the patch"), PARSE_OPT_OPTARG | PARSE_OPT_NONEG, inline_callback }, - { OPTION_CALLBACK, 0, "thread", &thread, "style", - "enable message threading, styles: shallow, deep", + { OPTION_CALLBACK, 0, "thread", &thread, N_("style"), + N_("enable message threading, styles: shallow, deep"), PARSE_OPT_OPTARG, thread_callback }, - OPT_STRING(0, "signature", &signature, "signature", - "add a signature"), + OPT_STRING(0, "signature", &signature, N_("signature"), + N_("add a signature")), OPT_BOOLEAN(0, "quiet", &quiet, - "don't print the patch filenames"), + N_("don't print the patch filenames")), OPT_END() }; extra_hdr.strdup_strings = 1; extra_to.strdup_strings = 1; extra_cc.strdup_strings = 1; + init_grep_defaults(); git_config(git_format_config, NULL); init_revisions(&rev, prefix); rev.commit_format = CMIT_FMT_EMAIL; @@ -1111,6 +1137,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) rev.subject_prefix = fmt_patch_subject_prefix; memset(&s_r_opt, 0, sizeof(s_r_opt)); s_r_opt.def = "HEAD"; + s_r_opt.revarg_opt = REVARG_COMMITTISH; if (default_attach) { rev.mime_boundary = default_attach; @@ -1130,7 +1157,7 @@ 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); @@ -1228,8 +1255,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 @@ -1245,16 +1280,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) { @@ -1264,7 +1309,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) if (hashcmp(o[0].item->sha1, o[1].item->sha1) == 0) return 0; } - get_patch_ids(&rev, &ids, prefix); + get_patch_ids(&rev, &ids); } if (!use_stdout) @@ -1305,7 +1350,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, quiet); + origin, nr, list, head, branch_name, quiet); total++; start_number--; } @@ -1350,8 +1395,8 @@ 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, quiet)) + 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); @@ -1377,6 +1422,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); @@ -1400,7 +1446,7 @@ static int add_pending_commit(const char *arg, struct rev_info *revs, int flags) } static const char * const cherry_usage[] = { - "git cherry [-v] [<upstream> [<head> [<limit>]]]", + N_("git cherry [-v] [<upstream> [<head> [<limit>]]]"), NULL }; @@ -1434,7 +1480,7 @@ int cmd_cherry(int argc, const char **argv, const char *prefix) struct option options[] = { OPT__ABBREV(&abbrev), - OPT__VERBOSE(&verbose, "be verbose"), + OPT__VERBOSE(&verbose, N_("be verbose")), OPT_END() }; @@ -1465,10 +1511,7 @@ int cmd_cherry(int argc, const char **argv, const char *prefix) } init_revisions(&revs, prefix); - revs.diff = 1; - revs.combine_merges = 0; - revs.ignore_merges = 1; - DIFF_OPT_SET(&revs.diffopt, RECURSIVE); + revs.max_parents = 1; if (add_pending_commit(head, &revs, 0)) die(_("Unknown commit %s"), head); @@ -1482,7 +1525,7 @@ int cmd_cherry(int argc, const char **argv, const char *prefix) return 0; } - get_patch_ids(&revs, &ids, prefix); + get_patch_ids(&revs, &ids); if (limit && add_pending_commit(limit, &revs, UNINTERESTING)) die(_("Unknown commit %s"), limit); @@ -1491,10 +1534,6 @@ int cmd_cherry(int argc, const char **argv, const char *prefix) if (prepare_revision_walk(&revs)) die(_("revision walk setup failed")); while ((commit = get_revision(&revs)) != NULL) { - /* ignore merges */ - if (commit->parents && commit->parents->next) - continue; - commit_list_insert(commit, &list); } diff --git a/builtin/ls-files.c b/builtin/ls-files.c index 468bb13c81..b5434af0c8 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; @@ -429,7 +405,7 @@ int report_path_error(const char *ps_matched, const char **pathspec, const char } static const char * const ls_files_usage[] = { - "git ls-files [options] [<file>...]", + N_("git ls-files [options] [<file>...]"), NULL }; @@ -481,57 +457,57 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix) struct dir_struct dir; struct option builtin_ls_files_options[] = { { 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_BOOLEAN('t', NULL, &show_tag, - "identify the file status with tags"), + N_("identify the file status with tags")), OPT_BOOLEAN('v', NULL, &show_valid_bit, - "use lowercase letters for 'assume unchanged' files"), + N_("use lowercase letters for 'assume unchanged' files")), OPT_BOOLEAN('c', "cached", &show_cached, - "show cached files in the output (default)"), + N_("show cached files in the output (default)")), OPT_BOOLEAN('d', "deleted", &show_deleted, - "show deleted files in the output"), + N_("show deleted files in the output")), OPT_BOOLEAN('m', "modified", &show_modified, - "show modified files in the output"), + N_("show modified files in the output")), OPT_BOOLEAN('o', "others", &show_others, - "show other files in the output"), + N_("show other files in the output")), OPT_BIT('i', "ignored", &dir.flags, - "show ignored files in the output", + N_("show ignored files in the output"), DIR_SHOW_IGNORED), OPT_BOOLEAN('s', "stage", &show_stage, - "show staged contents' object name in the output"), + N_("show staged contents' object name in the output")), OPT_BOOLEAN('k', "killed", &show_killed, - "show files on the filesystem that need to be removed"), + N_("show files on the filesystem that need to be removed")), OPT_BIT(0, "directory", &dir.flags, - "show 'other' directories' name only", + N_("show 'other' directories' name only"), DIR_SHOW_OTHER_DIRECTORIES), OPT_NEGBIT(0, "empty-directory", &dir.flags, - "don't show empty directories", + N_("don't show empty directories"), DIR_HIDE_EMPTY_DIRECTORIES), OPT_BOOLEAN('u', "unmerged", &show_unmerged, - "show unmerged files in the output"), + N_("show unmerged files in the output")), OPT_BOOLEAN(0, "resolve-undo", &show_resolve_undo, - "show resolve-undo information"), - { OPTION_CALLBACK, 'x', "exclude", &dir.exclude_list[EXC_CMDL], "pattern", - "skip files matching pattern", + N_("show resolve-undo information")), + { OPTION_CALLBACK, 'x', "exclude", &dir.exclude_list[EXC_CMDL], N_("pattern"), + N_("skip files matching pattern"), 0, option_parse_exclude }, - { OPTION_CALLBACK, 'X', "exclude-from", &dir, "file", - "exclude patterns are read from <file>", + { OPTION_CALLBACK, 'X', "exclude-from", &dir, N_("file"), + N_("exclude patterns are read from <file>"), 0, option_parse_exclude_from }, - OPT_STRING(0, "exclude-per-directory", &dir.exclude_per_dir, "file", - "read additional per-directory exclude patterns in <file>"), + OPT_STRING(0, "exclude-per-directory", &dir.exclude_per_dir, N_("file"), + N_("read additional per-directory exclude patterns in <file>")), { OPTION_CALLBACK, 0, "exclude-standard", &dir, NULL, - "add the standard git exclusions", + N_("add the standard git exclusions"), PARSE_OPT_NOARG, option_parse_exclude_standard }, { OPTION_SET_INT, 0, "full-name", &prefix_len, NULL, - "make the output relative to the project top directory", + N_("make the output relative to the project top directory"), PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL }, OPT_BOOLEAN(0, "error-unmatch", &error_unmatch, - "if any <file> is not in the index, treat this as an error"), - OPT_STRING(0, "with-tree", &with_tree, "tree-ish", - "pretend that paths removed since <tree-ish> are still present"), + N_("if any <file> is not in the index, treat this as an error")), + OPT_STRING(0, "with-tree", &with_tree, N_("tree-ish"), + N_("pretend that paths removed since <tree-ish> are still present")), OPT__ABBREV(&abbrev), - OPT_BOOLEAN(0, "debug", &debug_mode, "show debugging data"), + OPT_BOOLEAN(0, "debug", &debug_mode, N_("show debugging data")), OPT_END() }; @@ -580,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) { diff --git a/builtin/ls-remote.c b/builtin/ls-remote.c index 10223092a9..25e83cfe9d 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] [--exit-code] [<repository> [<refs>...]]"; +" [-q|--quiet] [--exit-code] [--get-url] [<repository> [<refs>...]]"; /* * Is there one among the list of patterns that match the tail part @@ -43,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]; diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c index 6b666e1e87..235c17cc01 100644 --- a/builtin/ls-tree.c +++ b/builtin/ls-tree.c @@ -24,7 +24,7 @@ static int chomp_prefix; static const char *ls_tree_prefix; static const char * const ls_tree_usage[] = { - "git ls-tree [<options>] <tree-ish> [<path>...]", + N_("git ls-tree [<options>] <tree-ish> [<path>...]"), NULL }; @@ -122,25 +122,25 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix) struct tree *tree; int i, full_tree = 0; const struct option ls_tree_options[] = { - OPT_BIT('d', NULL, &ls_options, "only show trees", + OPT_BIT('d', NULL, &ls_options, N_("only show trees"), LS_TREE_ONLY), - OPT_BIT('r', NULL, &ls_options, "recurse into subtrees", + OPT_BIT('r', NULL, &ls_options, N_("recurse into subtrees"), LS_RECURSIVE), - OPT_BIT('t', NULL, &ls_options, "show trees when recursing", + OPT_BIT('t', NULL, &ls_options, N_("show trees when recursing"), LS_SHOW_TREES), OPT_SET_INT('z', NULL, &line_termination, - "terminate entries with NUL byte", 0), - OPT_BIT('l', "long", &ls_options, "include object size", + N_("terminate entries with NUL byte"), 0), + OPT_BIT('l', "long", &ls_options, N_("include object size"), LS_SHOW_SIZE), - OPT_BIT(0, "name-only", &ls_options, "list only filenames", + OPT_BIT(0, "name-only", &ls_options, N_("list only filenames"), LS_NAME_ONLY), - OPT_BIT(0, "name-status", &ls_options, "list only filenames", + OPT_BIT(0, "name-status", &ls_options, N_("list only filenames"), LS_NAME_ONLY), OPT_SET_INT(0, "full-name", &chomp_prefix, - "use full path names", 0), + N_("use full path names"), 0), OPT_BOOLEAN(0, "full-tree", &full_tree, - "list entire tree; not just current directory " - "(implies --full-name)"), + N_("list entire tree; not just current directory " + "(implies --full-name)")), OPT__ABBREV(&abbrev), OPT_END() }; diff --git a/builtin/mailinfo.c b/builtin/mailinfo.c index bfb32b7233..24a772d8e1 100644 --- a/builtin/mailinfo.c +++ b/builtin/mailinfo.c @@ -19,9 +19,6 @@ static struct strbuf email = STRBUF_INIT; static enum { TE_DONTCARE, TE_QP, TE_BASE64 } transfer_encoding; -static enum { - TYPE_TEXT, TYPE_OTHER -} message_type; static struct strbuf charset = STRBUF_INIT; static int patch_lines; @@ -160,10 +157,9 @@ static int slurp_attr(const char *line, const char *name, struct strbuf *attr) const char *ends, *ap = strcasestr(line, name); size_t sz; - if (!ap) { - strbuf_setlen(attr, 0); + strbuf_setlen(attr, 0); + if (!ap) return 0; - } ap += strlen(name); if (*ap == '"') { ap++; @@ -185,8 +181,6 @@ static void handle_content_type(struct strbuf *line) struct strbuf *boundary = xmalloc(sizeof(struct strbuf)); strbuf_init(boundary, line->len); - if (!strcasestr(line->buf, "text/")) - message_type = TYPE_OTHER; if (slurp_attr(line->buf, "boundary=", boundary)) { strbuf_insert(boundary, 0, "--", 2); if (++content_top > &content[MAX_BOUNDARIES]) { @@ -232,7 +226,9 @@ static void cleanup_subject(struct strbuf *subject) case 'r': case 'R': if (subject->len <= at + 3) break; - if (!memcmp(subject->buf + at + 1, "e:", 2)) { + if ((subject->buf[at + 1] == 'e' || + subject->buf[at + 1] == 'E') && + subject->buf[at + 2] == ':') { strbuf_remove(subject, at, 3); continue; } @@ -250,8 +246,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; @@ -472,37 +477,14 @@ static struct strbuf *decode_b_segment(const struct strbuf *b_seg) return out; } -/* - * When there is no known charset, guess. - * - * Right now we assume that if the target is UTF-8 (the default), - * and it already looks like UTF-8 (which includes US-ASCII as its - * subset, of course) then that is what it is and there is nothing - * to do. - * - * Otherwise, we default to assuming it is Latin1 for historical - * reasons. - */ -static const char *guess_charset(const struct strbuf *line, const char *target_charset) -{ - if (is_encoding_utf8(target_charset)) { - if (is_utf8(line->buf)) - return NULL; - } - return "ISO8859-1"; -} - static void convert_to_utf8(struct strbuf *line, const char *charset) { char *out; - if (!charset || !*charset) { - charset = guess_charset(line, metainfo_charset); - if (!charset) - return; - } + if (!charset || !*charset) + return; - if (!strcasecmp(metainfo_charset, charset)) + if (same_encoding(metainfo_charset, charset)) return; out = reencode_string(line->buf, metainfo_charset, charset); if (!out) @@ -671,7 +653,6 @@ again: /* set some defaults */ transfer_encoding = TE_DONTCARE; strbuf_reset(&charset); - message_type = TYPE_TEXT; /* slurp in this section's info */ while (read_one_header_line(&line, fin)) @@ -885,11 +866,6 @@ static void handle_body(void) strbuf_insert(&line, 0, prev.buf, prev.len); strbuf_reset(&prev); - /* binary data most likely doesn't have newlines */ - if (message_type != TYPE_TEXT) { - handle_filter(&line); - break; - } /* * This is a decoded line that may contain * multiple new lines. Pass only one chunk diff --git a/builtin/merge-base.c b/builtin/merge-base.c index 4f30f1b0c8..1bc7991048 100644 --- a/builtin/merge-base.c +++ b/builtin/merge-base.c @@ -23,9 +23,10 @@ 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] <commit> <commit>...", - "git merge-base [-a|--all] --octopus <commit>...", - "git merge-base --independent <commit>...", + N_("git merge-base [-a|--all] <commit> <commit>..."), + N_("git merge-base [-a|--all] --octopus <commit>..."), + N_("git merge-base --independent <commit>..."), + N_("git merge-base --is-ancestor <commit> <commit>"), NULL }; @@ -70,6 +71,20 @@ static int handle_octopus(int count, const char **args, int reduce, int show_all return 0; } +static int handle_is_ancestor(int argc, const char **argv) +{ + struct commit *one, *two; + + if (argc != 2) + die("--is-ancestor takes exactly two commits"); + one = get_commit_reference(argv[0]); + two = get_commit_reference(argv[1]); + if (in_merge_bases(one, two)) + return 0; + else + return 1; +} + int cmd_merge_base(int argc, const char **argv, const char *prefix) { struct commit **rev; @@ -77,11 +92,14 @@ int cmd_merge_base(int argc, const char **argv, const char *prefix) int show_all = 0; int octopus = 0; int reduce = 0; + int is_ancestor = 0; struct option options[] = { - OPT_BOOLEAN('a', "all", &show_all, "output all common ancestors"), - OPT_BOOLEAN(0, "octopus", &octopus, "find ancestors for a single n-way merge"), - OPT_BOOLEAN(0, "independent", &reduce, "list revs not reachable from others"), + OPT_BOOLEAN('a', "all", &show_all, N_("output all common ancestors")), + OPT_BOOLEAN(0, "octopus", &octopus, N_("find ancestors for a single n-way merge")), + OPT_BOOLEAN(0, "independent", &reduce, N_("list revs not reachable from others")), + OPT_BOOLEAN(0, "is-ancestor", &is_ancestor, + N_("is the first one ancestor of the other?")), OPT_END() }; @@ -89,6 +107,10 @@ int cmd_merge_base(int argc, const char **argv, const char *prefix) argc = parse_options(argc, argv, prefix, options, merge_base_usage, 0); if (!octopus && !reduce && argc < 2) usage_with_options(merge_base_usage, options); + if (is_ancestor && (show_all | octopus | reduce)) + die("--is-ancestor cannot be used with other options"); + if (is_ancestor) + return handle_is_ancestor(argc, argv); if (reduce && (show_all || octopus)) die("--independent cannot be used with other options"); diff --git a/builtin/merge-file.c b/builtin/merge-file.c index 237abd3c0b..c0570f2407 100644 --- a/builtin/merge-file.c +++ b/builtin/merge-file.c @@ -5,7 +5,7 @@ #include "parse-options.h" static const char *const merge_file_usage[] = { - "git merge-file [options] [-L name1 [-L orig [-L name2]]] file1 orig_file file2", + N_("git merge-file [options] [-L name1 [-L orig [-L name2]]] file1 orig_file file2"), NULL }; @@ -30,19 +30,19 @@ int cmd_merge_file(int argc, const char **argv, const char *prefix) 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), - OPT_SET_INT(0, "ours", &xmp.favor, "for conflicts, use our version", + OPT_BOOLEAN('p', "stdout", &to_stdout, N_("send results to standard output")), + OPT_SET_INT(0, "diff3", &xmp.style, N_("use a diff3 based merge"), XDL_MERGE_DIFF3), + OPT_SET_INT(0, "ours", &xmp.favor, N_("for conflicts, use our version"), XDL_MERGE_FAVOR_OURS), - OPT_SET_INT(0, "theirs", &xmp.favor, "for conflicts, use their version", + OPT_SET_INT(0, "theirs", &xmp.favor, N_("for conflicts, use their version"), XDL_MERGE_FAVOR_THEIRS), - OPT_SET_INT(0, "union", &xmp.favor, "for conflicts, use a union version", + OPT_SET_INT(0, "union", &xmp.favor, N_("for conflicts, use a union version"), XDL_MERGE_FAVOR_UNION), OPT_INTEGER(0, "marker-size", &xmp.marker_size, - "for conflicts, use this marker size"), - OPT__QUIET(&quiet, "do not warn about conflicts"), - OPT_CALLBACK('L', NULL, names, "name", - "set labels for file1/orig_file/file2", &label_cb), + N_("for conflicts, use this marker size")), + OPT__QUIET(&quiet, N_("do not warn about conflicts")), + OPT_CALLBACK('L', NULL, names, N_("name"), + N_("set labels for file1/orig_file/file2"), &label_cb), OPT_END(), }; @@ -63,7 +63,7 @@ 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) @@ -76,7 +76,7 @@ int cmd_merge_file(int argc, const char **argv, const char *prefix) 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 2338832587..be5e514324 100644 --- a/builtin/merge-index.c +++ b/builtin/merge-index.c @@ -42,7 +42,7 @@ static int merge_entry(int pos, const char *path) return found; } -static void merge_file(const char *path) +static void merge_one_path(const char *path) { int pos = cache_name_pos(path, strlen(path)); @@ -102,7 +102,7 @@ int cmd_merge_index(int argc, const char **argv, const char *prefix) } die("git merge-index: unknown option %s", arg); } - merge_file(arg); + merge_one_path(arg); } if (err && !quiet) die("merge program failed"); diff --git a/builtin/merge-tree.c b/builtin/merge-tree.c index 897a563bc6..bc912e399e 100644 --- a/builtin/merge-tree.c +++ b/builtin/merge-tree.c @@ -3,17 +3,15 @@ #include "xdiff-interface.h" #include "blob.h" #include "exec_cmd.h" -#include "merge-file.h" +#include "merge-blobs.h" static const char merge_tree_usage[] = "git merge-tree <base-tree> <branch1> <branch2>"; -static int resolve_directories = 1; struct merge_list { struct merge_list *next; struct merge_list *link; /* other stages for this object */ - unsigned int stage : 2, - flags : 30; + unsigned int stage : 2; unsigned int mode; const char *path; struct blob *blob; @@ -27,7 +25,7 @@ static void add_merge_entry(struct merge_list *entry) merge_result_end = &entry->next; } -static void merge_trees(struct tree_desc t[3], const char *base); +static void merge_trees_recursive(struct tree_desc t[3], const char *base, int df_conflict); static const char *explanation(struct merge_list *entry) { @@ -76,7 +74,7 @@ static void *result(struct merge_list *entry, unsigned long *size) their = NULL; if (entry) their = entry->blob; - return merge_file(path, base, our, their, size); + return merge_blobs(path, base, our, their, size); } static void *origin(struct merge_list *entry, unsigned long *size) @@ -174,17 +172,17 @@ static char *traverse_path(const struct traverse_info *info, const struct name_e return make_traverse_path(path, info, n); } -static void resolve(const struct traverse_info *info, struct name_entry *branch1, struct name_entry *result) +static void resolve(const struct traverse_info *info, struct name_entry *ours, struct name_entry *result) { struct merge_list *orig, *final; const char *path; - /* If it's already branch1, don't bother showing it */ - if (!branch1) + /* If it's already ours, don't bother showing it */ + if (!ours) return; path = traverse_path(info, result); - orig = create_entry(2, branch1->mode, branch1->sha1, path); + orig = create_entry(2, ours->mode, ours->sha1, path); final = create_entry(0, result->mode, result->sha1, path); final->link = orig; @@ -192,34 +190,35 @@ static void resolve(const struct traverse_info *info, struct name_entry *branch1 add_merge_entry(final); } -static int unresolved_directory(const struct traverse_info *info, struct name_entry n[3]) +static void unresolved_directory(const struct traverse_info *info, struct name_entry n[3], + int df_conflict) { char *newbase; struct name_entry *p; struct tree_desc t[3]; void *buf0, *buf1, *buf2; - if (!resolve_directories) - return 0; - p = n; - if (!p->mode) { - p++; - if (!p->mode) - p++; + for (p = n; p < n + 3; p++) { + if (p->mode && S_ISDIR(p->mode)) + break; } - if (!S_ISDIR(p->mode)) - return 0; + if (n + 3 <= p) + return; /* there is no tree here */ + newbase = traverse_path(info, p); - buf0 = fill_tree_descriptor(t+0, n[0].sha1); - buf1 = fill_tree_descriptor(t+1, n[1].sha1); - buf2 = fill_tree_descriptor(t+2, n[2].sha1); - merge_trees(t, newbase); + +#define ENTRY_SHA1(e) (((e)->mode && S_ISDIR((e)->mode)) ? (e)->sha1 : NULL) + buf0 = fill_tree_descriptor(t+0, ENTRY_SHA1(n + 0)); + buf1 = fill_tree_descriptor(t+1, ENTRY_SHA1(n + 1)); + buf2 = fill_tree_descriptor(t+2, ENTRY_SHA1(n + 2)); +#undef ENTRY_SHA1 + + merge_trees_recursive(t, newbase, df_conflict); free(buf0); free(buf1); free(buf2); free(newbase); - return 1; } @@ -242,18 +241,26 @@ static struct merge_list *link_entry(unsigned stage, const struct traverse_info static void unresolved(const struct traverse_info *info, struct name_entry n[3]) { struct merge_list *entry = NULL; + int i; + unsigned dirmask = 0, mask = 0; + + for (i = 0; i < 3; i++) { + mask |= (1 << i); + if (n[i].mode && S_ISDIR(n[i].mode)) + dirmask |= (1 << i); + } - if (unresolved_directory(info, n)) + unresolved_directory(info, n, dirmask && (dirmask != mask)); + + if (dirmask == mask) return; - /* - * Do them in reverse order so that the resulting link - * list has the stages in order - link_entry adds new - * links at the front. - */ - entry = link_entry(3, info, n + 2, entry); - entry = link_entry(2, info, n + 1, entry); - entry = link_entry(1, info, n + 0, entry); + if (n[2].mode && !S_ISDIR(n[2].mode)) + entry = link_entry(3, info, n + 2, entry); + if (n[1].mode && !S_ISDIR(n[1].mode)) + entry = link_entry(2, info, n + 1, entry); + if (n[0].mode && !S_ISDIR(n[0].mode)) + entry = link_entry(1, info, n + 0, entry); add_merge_entry(entry); } @@ -292,20 +299,29 @@ static int threeway_callback(int n, unsigned long mask, unsigned long dirmask, s /* Same in both? */ if (same_entry(entry+1, entry+2)) { if (entry[0].sha1) { + /* Modified identically */ resolve(info, NULL, entry+1); return mask; } + /* "Both added the same" is left unresolved */ } if (same_entry(entry+0, entry+1)) { if (entry[2].sha1 && !S_ISDIR(entry[2].mode)) { + /* We did not touch, they modified -- take theirs */ resolve(info, entry+1, entry+2); return mask; } + /* + * If we did not touch a directory but they made it + * into a file, we fall through and unresolved() + * recurses down. Likewise for the opposite case. + */ } if (same_entry(entry+0, entry+2)) { if (entry[1].sha1 && !S_ISDIR(entry[1].mode)) { + /* We modified, they did not touch -- take ours */ resolve(info, NULL, entry+1); return mask; } @@ -315,15 +331,21 @@ static int threeway_callback(int n, unsigned long mask, unsigned long dirmask, s return mask; } -static void merge_trees(struct tree_desc t[3], const char *base) +static void merge_trees_recursive(struct tree_desc t[3], const char *base, int df_conflict) { struct traverse_info info; setup_traverse_info(&info, base); + info.data = &df_conflict; info.fn = threeway_callback; traverse_trees(3, t, &info); } +static void merge_trees(struct tree_desc t[3], const char *base) +{ + merge_trees_recursive(t, base, 0); +} + static void *get_tree_descriptor(struct tree_desc *desc, const char *rev) { unsigned char sha1[20]; diff --git a/builtin/merge.c b/builtin/merge.c index 325891edb6..9307e9c726 100644 --- a/builtin/merge.c +++ b/builtin/merge.c @@ -26,6 +26,8 @@ #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) @@ -38,19 +40,18 @@ struct strategy { }; static const char * const builtin_merge_usage[] = { - "git merge [options] [<commit>...]", - "git merge [options] <msg> HEAD <commit>", - "git merge --abort", + N_("git merge [options] [<commit>...]"), + N_("git merge [options] <msg> HEAD <commit>"), + N_("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; @@ -63,6 +64,7 @@ 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 }, @@ -178,34 +180,39 @@ static int option_parse_n(const struct option *opt, static struct option builtin_merge_options[] = { { OPTION_CALLBACK, 'n', NULL, NULL, NULL, - "do not show a diffstat at the end of the merge", + N_("do not show a diffstat at the end of the merge"), PARSE_OPT_NOARG, option_parse_n }, OPT_BOOLEAN(0, "stat", &show_diffstat, - "show a diffstat at the end of the merge"), - OPT_BOOLEAN(0, "summary", &show_diffstat, "(synonym to --stat)"), - { OPTION_INTEGER, 0, "log", &shortlog_len, "n", - "add (at most <n>) entries from shortlog to merge commit message", + N_("show a diffstat at the end of the merge")), + OPT_BOOLEAN(0, "summary", &show_diffstat, N_("(synonym to --stat)")), + { OPTION_INTEGER, 0, "log", &shortlog_len, N_("n"), + N_("add (at most <n>) entries from shortlog to merge commit message"), PARSE_OPT_OPTARG, NULL, DEFAULT_MERGE_LOG_LEN }, OPT_BOOLEAN(0, "squash", &squash, - "create a single commit instead of doing a merge"), + N_("create a single commit instead of doing a merge")), OPT_BOOLEAN(0, "commit", &option_commit, - "perform a commit if the merge succeeds (default)"), + N_("perform a commit if the merge succeeds (default)")), + OPT_BOOL('e', "edit", &option_edit, + N_("edit message before committing")), OPT_BOOLEAN(0, "ff", &allow_fast_forward, - "allow fast-forward (default)"), + N_("allow fast-forward (default)")), OPT_BOOLEAN(0, "ff-only", &fast_forward_only, - "abort if fast-forward is not possible"), + N_("abort if fast-forward is not possible")), OPT_RERERE_AUTOUPDATE(&allow_rerere_auto), - OPT_CALLBACK('s', "strategy", &use_strategies, "strategy", - "merge strategy to use", option_parse_strategy), - OPT_CALLBACK('X', "strategy-option", &xopts, "option=value", - "option for selected merge strategy", option_parse_x), - OPT_CALLBACK('m', "message", &merge_msg, "message", - "merge commit message (for a non-fast-forward merge)", + OPT_CALLBACK('s', "strategy", &use_strategies, N_("strategy"), + N_("merge strategy to use"), option_parse_strategy), + OPT_CALLBACK('X', "strategy-option", &xopts, N_("option=value"), + N_("option for selected merge strategy"), option_parse_x), + OPT_CALLBACK('m', "message", &merge_msg, N_("message"), + N_("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), + N_("abort the current in-progress merge")), + OPT_SET_INT(0, "progress", &show_progress, N_("force progress reporting"), 1), + { OPTION_STRING, 'S', "gpg-sign", &sign_commit, N_("key id"), + N_("GPG sign commit"), PARSE_OPT_OPTARG, NULL, (intptr_t) "" }, + OPT_BOOLEAN(0, "overwrite-ignore", &overwrite_ignore, N_("update ignored files (default)")), OPT_END() }; @@ -217,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; @@ -236,11 +243,12 @@ static void save_state(void) if (finish_command(&cp) || len < 0) die(_("stash failed")); - else if (!len) - return; + 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); + return 0; } static void read_empty(unsigned const char *sha1, int verbose) @@ -278,7 +286,8 @@ static void reset_hard(unsigned const char *sha1, int verbose) 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 }; @@ -308,25 +317,25 @@ static void finish_up_to_date(const char *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); + 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); @@ -355,9 +364,12 @@ static void squash_message(void) 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")); @@ -368,7 +380,7 @@ 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")); @@ -387,13 +399,12 @@ 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")); + diff_setup_done(&opts); diff_tree_sha1(head, new_head, "", &opts); diffcore_std(&opts); diff_flush(&opts); @@ -408,8 +419,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; @@ -420,7 +431,7 @@ 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); @@ -430,6 +441,11 @@ 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-tracking branch '%s' of .\n", sha1_to_hex(branch_head), remote); @@ -468,10 +484,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); @@ -481,14 +497,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")); + filename); strbuf_getline(&line, fp, '\n'); fclose(fp); ptr = strstr(line.buf, "\tnot-for-merge\t"); @@ -499,7 +517,7 @@ 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); @@ -527,6 +545,8 @@ static void parse_branch_merge_options(char *bmo) 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")) { @@ -543,15 +563,7 @@ 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; - return 0; - } else if (!strcmp(k, "merge.ff")) { + else if (!strcmp(k, "merge.ff")) { int boolval = git_config_maybe_bool(k, v); if (0 <= boolval) { allow_fast_forward = boolval; @@ -564,6 +576,13 @@ static int git_merge_config(const char *k, const char *v, void *cb) 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); } @@ -609,61 +628,9 @@ static void write_tree_trivial(unsigned char *sha1) 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, size_t xopts_nr, - const char **xopts, struct commit_list *common, - const char *head_arg, struct commit_list *remotes) -{ - const char **args; - int i = 0, x = 0, ret; - struct commit_list *j; - struct strbuf buf = STRBUF_INIT; - - args = xmalloc((4 + xopts_nr + commit_list_count(common) + - commit_list_count(remotes)) * sizeof(char *)); - strbuf_addf(&buf, "merge-%s", strategy); - args[i++] = buf.buf; - for (x = 0; x < xopts_nr; x++) { - char *s = xmalloc(strlen(xopts[x])+2+1); - strcpy(s, "--"); - strcpy(s+2, xopts[x]); - args[i++] = s; - } - for (j = common; j; j = j->next) - args[i++] = xstrdup(merge_argument(j->item)); - args[i++] = "--"; - args[i++] = head_arg; - for (j = remotes; j; j = j->next) - args[i++] = xstrdup(merge_argument(j->item)); - args[i] = NULL; - ret = run_command_v_opt(args, RUN_GIT_CMD); - strbuf_release(&buf); - i = 1; - for (x = 0; x < xopts_nr; x++) - free((void *)args[i++]); - for (j = common; j; j = j->next) - free((void *)args[i++]); - i += 2; - for (j = remotes; j; j = j->next) - free((void *)args[i++]); - free(args); - discard_cache(); - if (read_cache() < 0) - die(_("failed to read the cache")); - resolve_undo_clear(); - - return ret; -} - static 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)); @@ -703,13 +670,13 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common, 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) || @@ -742,54 +709,6 @@ static int count_unmerged_entries(void) return ret; } -int checkout_fast_forward(const unsigned char *head, const unsigned char *remote) -{ - struct tree *trees[MAX_UNPACK_TREES]; - struct unpack_trees_options opts; - struct tree_desc t[MAX_UNPACK_TREES]; - int i, fd, nr_trees = 0; - struct dir_struct dir; - struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file)); - - refresh_cache(REFRESH_QUIET); - - fd = hold_locked_index(lock_file, 1); - - memset(&trees, 0, sizeof(trees)); - memset(&opts, 0, sizeof(opts)); - memset(&t, 0, sizeof(t)); - memset(&dir, 0, sizeof(dir)); - dir.flags |= DIR_SHOW_IGNORED; - dir.exclude_per_dir = ".gitignore"; - opts.dir = &dir; - - opts.head_idx = 1; - opts.src_index = &the_index; - opts.dst_index = &the_index; - opts.update = 1; - opts.verbose_update = 1; - opts.merge = 1; - opts.fn = twoway_merge; - setup_unpack_trees_porcelain(&opts, "merge"); - - trees[nr_trees] = parse_tree_indirect(head); - if (!trees[nr_trees++]) - return -1; - trees[nr_trees] = parse_tree_indirect(remote); - if (!trees[nr_trees++]) - return -1; - for (i = 0; i < nr_trees; i++) { - parse_tree(trees[i]); - init_tree_desc(t+i, trees[i]->buffer, trees[i]->size); - } - if (unpack_trees(nr_trees, t, &opts)) - return -1; - if (write_cache(fd, active_cache, active_nr) || - commit_locked_index(lock_file)) - die(_("unable to write new index file")); - return 0; -} - static void split_merge_strategies(const char *string, struct strategy **list, int *nr, int *alloc) { @@ -834,77 +753,111 @@ static void add_strategies(const char *string, unsigned attr) } -static void write_merge_msg(void) +static void write_merge_msg(struct strbuf *msg) { - int fd = open(git_path("MERGE_MSG"), O_WRONLY | O_CREAT, 0666); + 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"), - 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")); + 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(void) +static void read_merge_msg(struct strbuf *msg) { - strbuf_reset(&merge_msg); - if (strbuf_read_file(&merge_msg, git_path("MERGE_MSG"), 0) < 0) - die_errno(_("Could not read from '%s'"), git_path("MERGE_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 run_prepare_commit_msg(void) +static void write_merge_state(struct commit_list *); +static void abort_commit(struct commit_list *remoteheads, const char *err_msg) { - write_merge_msg(); - run_hook(get_index_file(), "prepare-commit-msg", - git_path("MERGE_MSG"), "merge", NULL, NULL); - read_merge_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 int merge_trivial(void) +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); + if (run_hook(get_index_file(), "prepare-commit-msg", + git_path("MERGE_MSG"), "merge", NULL, NULL)) + abort_commit(remoteheads, 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); + parent->item = head; parent->next = xmalloc(sizeof(*parent->next)); parent->next->item = remoteheads->item; parent->next->next = NULL; - run_prepare_commit_msg(); - 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'); - run_prepare_commit_msg(); - 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; @@ -912,13 +865,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]; @@ -938,7 +892,8 @@ static int suggest_conflicts(int renormalizing) 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) { @@ -1007,16 +962,119 @@ static int setup_with_upstream(const char ***argv) 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 (argc == 2 && !strcmp(argv[1], "-h")) usage_with_options(builtin_merge_usage, builtin_merge_options); @@ -1025,22 +1083,22 @@ int cmd_merge(int argc, const char **argv, const char *prefix) * 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; @@ -1053,7 +1111,8 @@ int cmd_merge(int argc, const char **argv, const char *prefix) die(_("There is no merge to abort (MERGE_HEAD missing).")); /* Invoke 'git reset --merge' */ - return cmd_reset(nargc, nargv, prefix); + ret = cmd_reset(nargc, nargv, prefix); + goto done; } if (read_cache_unmerged()) @@ -1092,9 +1151,12 @@ int cmd_merge(int argc, const char **argv, const char *prefix) die(_("You cannot combine --no-ff with --ff-only.")); if (!abort_current_merge) { - if (!argc && default_to_upstream) - argc = setup_with_upstream(&argv); - else if (argc == 1 && !strcmp(argv[0], "-")) + 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) @@ -1110,13 +1172,15 @@ 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. @@ -1130,13 +1194,14 @@ int cmd_merge(int argc, const char **argv, const char *prefix) 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); + 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]); - read_empty(remote_head->sha1, 0); - update_ref("initial pull", "HEAD", remote_head->sha1, NULL, 0, - DIE_ON_ERR); - return 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; @@ -1144,52 +1209,57 @@ 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; + opts.credit_people = (0 < option_edit); + + 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); @@ -1202,38 +1272,40 @@ 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"), @@ -1244,17 +1316,22 @@ 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, + overwrite_ignore)) { + 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) ; /* @@ -1269,11 +1346,14 @@ 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); + 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(); + head_commit->object.sha1, + remoteheads->item->object.sha1)) { + ret = merge_trivial(head_commit, remoteheads); + goto done; + } printf(_("Nope.\n")); } } else { @@ -1292,8 +1372,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; @@ -1302,7 +1381,7 @@ 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; } } @@ -1310,7 +1389,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) 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 @@ -1320,21 +1399,18 @@ 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(); + restore_state(head_commit->object.sha1, stash); } if (use_strategies_nr != 1) printf(_("Trying merge strategy %s...\n"), @@ -1346,7 +1422,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; /* @@ -1387,66 +1464,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")); else 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(); + 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'); - write_merge_msg(); - 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); - } + finish(head_commit, remoteheads, NULL, NULL); + else + write_merge_state(remoteheads); - if (merge_was_ok) { + if (merge_was_ok) fprintf(stderr, _("Automatic merge went well; " "stopped before committing as requested\n")); - return 0; - } else - return suggest_conflicts(option_renormalize); + else + ret = suggest_conflicts(option_renormalize); + +done: + free(branch_to_free); + return ret; } diff --git a/builtin/mktree.c b/builtin/mktree.c index 098395fda1..f92ba404ab 100644 --- a/builtin/mktree.c +++ b/builtin/mktree.c @@ -60,10 +60,11 @@ 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[] = { - "git mktree [-z] [--missing] [--batch]", + N_("git mktree [-z] [--missing] [--batch]"), NULL }; @@ -149,9 +150,9 @@ int cmd_mktree(int ac, const char **av, const char *prefix) int got_eof = 0; const struct option option[] = { - OPT_SET_INT('z', NULL, &line_termination, "input is NUL terminated", '\0'), - OPT_SET_INT( 0 , "missing", &allow_missing, "allow missing objects", 1), - OPT_SET_INT( 0 , "batch", &is_batch_mode, "allow creation of more than one tree", 1), + OPT_SET_INT('z', NULL, &line_termination, N_("input is NUL terminated"), '\0'), + OPT_SET_INT( 0 , "missing", &allow_missing, N_("allow missing objects"), 1), + OPT_SET_INT( 0 , "batch", &is_batch_mode, N_("allow creation of more than one tree"), 1), OPT_END() }; diff --git a/builtin/mv.c b/builtin/mv.c index 40f33ca4d0..034fec92a1 100644 --- a/builtin/mv.c +++ b/builtin/mv.c @@ -11,7 +11,7 @@ #include "parse-options.h" static const char * const builtin_mv_usage[] = { - "git mv [options] <source>... <destination>", + N_("git mv [options] <source>... <destination>"), NULL }; @@ -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,9 +59,10 @@ 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, "dry run"), - OPT__FORCE(&force, "force move/rename even if target exists"), - OPT_BOOLEAN('k', NULL, &ignore_errors, "skip move/rename errors"), + OPT__VERBOSE(&verbose, N_("be verbose")), + OPT__DRY_RUN(&show_only, N_("dry run")), + OPT__FORCE(&force, N_("force move/rename even if target exists")), + OPT_BOOLEAN('k', NULL, &ignore_errors, N_("skip move/rename errors")), OPT_END(), }; const char **source, **destination, **dest_path; @@ -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; } @@ -172,7 +177,8 @@ int cmd_mv(int argc, const char **argv, const char *prefix) * 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"); diff --git a/builtin/name-rev.c b/builtin/name-rev.c index e8862b5de2..6238247974 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>... )", + N_("git name-rev [options] <commit>..."), + N_("git name-rev [options] --all"), + N_("git name-rev [options] --stdin"), NULL }; @@ -224,16 +226,16 @@ int cmd_name_rev(int argc, const char **argv, const char *prefix) int all = 0, transform_stdin = 0, allow_undefined = 1, always = 0; struct name_ref_data data = { 0, 0, NULL }; struct option opts[] = { - OPT_BOOLEAN(0, "name-only", &data.name_only, "print only names (no SHA-1)"), - OPT_BOOLEAN(0, "tags", &data.tags_only, "only use tags to name the commits"), - OPT_STRING(0, "refs", &data.ref_filter, "pattern", - "only use refs matching <pattern>"), + OPT_BOOLEAN(0, "name-only", &data.name_only, N_("print only names (no SHA-1)")), + OPT_BOOLEAN(0, "tags", &data.tags_only, N_("only use tags to name the commits")), + OPT_STRING(0, "refs", &data.ref_filter, N_("pattern"), + N_("only use refs matching <pattern>")), OPT_GROUP(""), - OPT_BOOLEAN(0, "all", &all, "list all commits reachable from all refs"), - OPT_BOOLEAN(0, "stdin", &transform_stdin, "read from stdin"), - OPT_BOOLEAN(0, "undefined", &allow_undefined, "allow to print `undefined` names"), + OPT_BOOLEAN(0, "all", &all, N_("list all commits reachable from all refs")), + OPT_BOOLEAN(0, "stdin", &transform_stdin, N_("read from stdin")), + OPT_BOOLEAN(0, "undefined", &allow_undefined, N_("allow to print `undefined` names")), OPT_BOOLEAN(0, "always", &always, - "show abbreviated commit object as fallback"), + N_("show abbreviated commit object as fallback")), OPT_END(), }; diff --git a/builtin/notes.c b/builtin/notes.c index f8e437db01..453457adb9 100644 --- a/builtin/notes.c +++ b/builtin/notes.c @@ -19,72 +19,75 @@ #include "string-list.h" #include "notes-merge.h" +static void commit_notes(struct notes_tree *t, const char *msg); +static combine_notes_fn parse_combine_notes_fn(const char *v); + static const char * const git_notes_usage[] = { - "git notes [--ref <notes_ref>] [list [<object>]]", - "git notes [--ref <notes_ref>] add [-f] [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]", - "git notes [--ref <notes_ref>] copy [-f] <from-object> <to-object>", - "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>] 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", + N_("git notes [--ref <notes_ref>] [list [<object>]]"), + N_("git notes [--ref <notes_ref>] add [-f] [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]"), + N_("git notes [--ref <notes_ref>] copy [-f] <from-object> <to-object>"), + N_("git notes [--ref <notes_ref>] append [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]"), + N_("git notes [--ref <notes_ref>] edit [<object>]"), + N_("git notes [--ref <notes_ref>] show [<object>]"), + N_("git notes [--ref <notes_ref>] merge [-v | -q] [-s <strategy> ] <notes_ref>"), + N_("git notes merge --commit [-v | -q]"), + N_("git notes merge --abort [-v | -q]"), + N_("git notes [--ref <notes_ref>] remove [<object>...]"), + N_("git notes [--ref <notes_ref>] prune [-n | -v]"), + N_("git notes [--ref <notes_ref>] get-ref"), NULL }; static const char * const git_notes_list_usage[] = { - "git notes [list [<object>]]", + N_("git notes [list [<object>]]"), NULL }; static const char * const git_notes_add_usage[] = { - "git notes add [<options>] [<object>]", + N_("git notes add [<options>] [<object>]"), NULL }; static const char * const git_notes_copy_usage[] = { - "git notes copy [<options>] <from-object> <to-object>", - "git notes copy --stdin [<from-object> <to-object>]...", + N_("git notes copy [<options>] <from-object> <to-object>"), + N_("git notes copy --stdin [<from-object> <to-object>]..."), NULL }; static const char * const git_notes_append_usage[] = { - "git notes append [<options>] [<object>]", + N_("git notes append [<options>] [<object>]"), NULL }; static const char * const git_notes_edit_usage[] = { - "git notes edit [<object>]", + N_("git notes edit [<object>]"), NULL }; static const char * const git_notes_show_usage[] = { - "git notes show [<object>]", + N_("git notes show [<object>]"), 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>]", + N_("git notes merge [<options>] <notes_ref>"), + N_("git notes merge --commit [<options>]"), + N_("git notes merge --abort [<options>]"), NULL }; static const char * const git_notes_remove_usage[] = { - "git notes remove [<object>]", + N_("git notes remove [<object>]"), NULL }; static const char * const git_notes_prune_usage[] = { - "git notes prune [<options>]", + N_("git notes prune [<options>]"), NULL }; static const char * const git_notes_get_ref_usage[] = { - "git notes get-ref", + N_("git notes get-ref"), NULL }; @@ -288,7 +291,7 @@ static int parse_reedit_arg(const struct option *opt, const char *arg, int unset return parse_reuse_arg(opt, arg, unset); } -void commit_notes(struct notes_tree *t, const char *msg) +static void commit_notes(struct notes_tree *t, const char *msg) { struct strbuf buf = STRBUF_INIT; unsigned char commit_sha1[20]; @@ -301,18 +304,18 @@ void commit_notes(struct notes_tree *t, const char *msg) 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 */ - create_notes_commit(t, NULL, buf.buf + 7, commit_sha1); + 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); } -combine_notes_fn parse_combine_notes_fn(const char *v) +static combine_notes_fn parse_combine_notes_fn(const char *v) { if (!strcasecmp(v, "overwrite")) return combine_notes_overwrite; @@ -531,19 +534,19 @@ 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", - "note contents as a string", PARSE_OPT_NONEG, + { OPTION_CALLBACK, 'm', "message", &msg, N_("message"), + N_("note contents as a string"), PARSE_OPT_NONEG, parse_msg_arg}, - { OPTION_CALLBACK, 'F', "file", &msg, "file", - "note contents in a file", PARSE_OPT_NONEG, + { OPTION_CALLBACK, 'F', "file", &msg, N_("file"), + N_("note contents in a file"), PARSE_OPT_NONEG, parse_file_arg}, - { OPTION_CALLBACK, 'c', "reedit-message", &msg, "object", - "reuse and edit specified note object", PARSE_OPT_NONEG, + { OPTION_CALLBACK, 'c', "reedit-message", &msg, N_("object"), + N_("reuse and edit specified note object"), PARSE_OPT_NONEG, parse_reedit_arg}, - { OPTION_CALLBACK, 'C', "reuse-message", &msg, "object", - "reuse specified note object", PARSE_OPT_NONEG, + { OPTION_CALLBACK, 'C', "reuse-message", &msg, N_("object"), + N_("reuse specified note object"), PARSE_OPT_NONEG, parse_reuse_arg}, - OPT__FORCE(&force, "replace existing notes"), + OPT__FORCE(&force, N_("replace existing notes")), OPT_END() }; @@ -611,11 +614,11 @@ static int copy(int argc, const char **argv, const char *prefix) struct notes_tree *t; const char *rewrite_cmd = NULL; struct option options[] = { - 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 " - "--stdin)"), + OPT__FORCE(&force, N_("replace existing notes")), + OPT_BOOLEAN(0, "stdin", &from_stdin, N_("read objects from stdin")), + OPT_STRING(0, "for-rewrite", &rewrite_cmd, N_("command"), + N_("load rewriting config for <command> (implies " + "--stdin)")), OPT_END() }; @@ -688,17 +691,17 @@ 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", - "note contents as a string", PARSE_OPT_NONEG, + { OPTION_CALLBACK, 'm', "message", &msg, N_("message"), + N_("note contents as a string"), PARSE_OPT_NONEG, parse_msg_arg}, - { OPTION_CALLBACK, 'F', "file", &msg, "file", - "note contents in a file", PARSE_OPT_NONEG, + { OPTION_CALLBACK, 'F', "file", &msg, N_("file"), + N_("note contents in a file"), PARSE_OPT_NONEG, parse_file_arg}, - { OPTION_CALLBACK, 'c', "reedit-message", &msg, "object", - "reuse and edit specified note object", PARSE_OPT_NONEG, + { OPTION_CALLBACK, 'c', "reedit-message", &msg, N_("object"), + N_("reuse and edit specified note object"), PARSE_OPT_NONEG, parse_reedit_arg}, - { OPTION_CALLBACK, 'C', "reuse-message", &msg, "object", - "reuse specified note object", PARSE_OPT_NONEG, + { OPTION_CALLBACK, 'C', "reuse-message", &msg, N_("object"), + N_("reuse specified note object"), PARSE_OPT_NONEG, parse_reuse_arg}, OPT_END() }; @@ -804,6 +807,8 @@ static int merge_commit(struct notes_merge_options *o) 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, @@ -825,7 +830,8 @@ static int merge_commit(struct notes_merge_options *o) t = xcalloc(1, sizeof(struct notes_tree)); init_notes(t, "NOTES_MERGE_PARTIAL", combine_notes_overwrite, 0); - o->local_ref = resolve_ref("NOTES_MERGE_REF", sha1, 0, NULL); + 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"); @@ -843,7 +849,9 @@ static int merge_commit(struct notes_merge_options *o) free_notes(t); strbuf_release(&msg); - return merge_abort(o); + ret = merge_abort(o); + free(local_ref_to_free); + return ret; } static int merge(int argc, const char **argv, const char *prefix) @@ -856,19 +864,19 @@ static int merge(int argc, const char **argv, const char *prefix) int verbosity = 0, result; const char *strategy = NULL; struct option options[] = { - OPT_GROUP("General options"), + OPT_GROUP(N_("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"), + OPT_GROUP(N_("Merge options")), + OPT_STRING('s', "strategy", &strategy, N_("strategy"), + N_("resolve notes conflicts using the given strategy " + "(manual/ours/theirs/union/cat_sort_uniq)")), + OPT_GROUP(N_("Committing unmerged notes")), { OPTION_BOOLEAN, 0, "commit", &do_commit, NULL, - "finalize notes merge by committing unmerged notes", + N_("finalize notes merge by committing unmerged notes"), PARSE_OPT_NOARG | PARSE_OPT_NONEG }, - OPT_GROUP("Aborting notes merge resolution"), + OPT_GROUP(N_("Aborting notes merge resolution")), { OPTION_BOOLEAN, 0, "abort", &do_abort, NULL, - "abort notes merge", + N_("abort notes merge"), PARSE_OPT_NOARG | PARSE_OPT_NONEG }, OPT_END() }; @@ -975,10 +983,10 @@ static int remove_cmd(int argc, const char **argv, const char *prefix) int from_stdin = 0; struct option options[] = { OPT_BIT(0, "ignore-missing", &flag, - "attempt to remove non-existent note is not an error", + N_("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"), + N_("read object names from the standard input")), OPT_END() }; struct notes_tree *t; @@ -1059,8 +1067,8 @@ int cmd_notes(int argc, const char **argv, const char *prefix) int result; const char *override_notes_ref = NULL; struct option options[] = { - OPT_STRING(0, "ref", &override_notes_ref, "notes_ref", - "use notes from <notes_ref>"), + OPT_STRING(0, "ref", &override_notes_ref, N_("notes_ref"), + N_("use notes from <notes_ref>")), OPT_END() }; diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c index 8bfe3a6ffb..f069462cb0 100644 --- a/builtin/pack-objects.c +++ b/builtin/pack-objects.c @@ -16,18 +16,14 @@ #include "list-objects.h" #include "progress.h" #include "refs.h" +#include "streaming.h" #include "thread-utils.h" -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[] = { + N_("git pack-objects --stdout [options...] [< ref-list | < object-list]"), + N_("git pack-objects [options...] base-name [< ref-list | < object-list]"), + NULL +}; struct object_entry { struct pack_idx_entry idx; @@ -51,6 +47,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 */ }; /* @@ -66,14 +64,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; @@ -95,6 +95,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 @@ -150,6 +151,46 @@ static unsigned long do_compress(void **pptr, unsigned long size) return stream.total_out; } +static unsigned long write_large_blob_data(struct git_istream *st, struct sha1file *f, + const unsigned char *sha1) +{ + git_zstream stream; + unsigned char ibuf[1024 * 16]; + unsigned char obuf[1024 * 16]; + unsigned long olen = 0; + + memset(&stream, 0, sizeof(stream)); + git_deflate_init(&stream, pack_compression_level); + + for (;;) { + ssize_t readlen; + int zret = Z_OK; + readlen = read_istream(st, ibuf, sizeof(ibuf)); + if (readlen == -1) + die(_("unable to read %s"), sha1_to_hex(sha1)); + + stream.next_in = ibuf; + stream.avail_in = readlen; + while ((stream.avail_in || readlen == 0) && + (zret == Z_OK || zret == Z_BUF_ERROR)) { + stream.next_out = obuf; + stream.avail_out = sizeof(obuf); + zret = git_deflate(&stream, readlen ? 0 : Z_FINISH); + sha1write(f, obuf, stream.next_out - obuf); + olen += stream.next_out - obuf; + } + if (stream.avail_in) + die(_("deflate error (%d)"), zret); + if (readlen == 0) { + if (zret != Z_STREAM_END) + die(_("deflate error (%d)"), zret); + break; + } + } + git_deflate_end(&stream); + return olen; +} + /* * we are going to reuse the existing object data as is. make * sure it is not corrupt. @@ -199,22 +240,199 @@ static void copy_pack_data(struct sha1file *f, } } -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; + struct git_istream *st = NULL; + + if (!usable_delta) { + if (entry->type == OBJ_BLOB && + entry->size > big_file_threshold && + (st = open_istream(entry->idx.sha1, &type, &size, NULL)) != NULL) + buf = NULL; + else { + 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 (st) /* large blob case, just assume we don't compress well */ + datalen = size; + else 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) { + if (st) + close_istream(st); + 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) { + if (st) + close_istream(st); + free(buf); + return 0; + } + sha1write(f, header, hdrlen); + sha1write(f, entry->delta->idx.sha1, 20); + hdrlen += 20; + } else { + if (limit && hdrlen + datalen + 20 >= limit) { + if (st) + close_istream(st); + free(buf); + return 0; + } + sha1write(f, header, hdrlen); + } + if (st) { + datalen = write_large_blob_data(st, f, entry->idx.sha1); + close_istream(st); + } else { + 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; @@ -242,11 +460,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 */ @@ -255,174 +473,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; @@ -430,7 +545,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) @@ -438,37 +717,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); } @@ -490,20 +763,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 @@ -511,28 +772,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)); } @@ -545,6 +805,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, @@ -633,7 +894,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; @@ -667,6 +928,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; } @@ -838,7 +1103,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; @@ -1145,7 +1410,7 @@ static void get_object_details(void) for (i = 0; i < nr_objects; i++) { struct object_entry *entry = sorted_by_offset[i]; check_object(entry); - if (big_file_threshold <= entry->size) + if (big_file_threshold < entry->size) entry->no_try_delta = 1; } @@ -1768,7 +2033,6 @@ static int add_ref_tag(const char *path, const unsigned char *sha1, int flag, vo if (!prefixcmp(path, "refs/tags/") && /* is a tag? */ !peel_ref(path, peeled) && /* peelable? */ - !is_null_sha1(peeled) && /* annotated tag? */ locate_object_entry(peeled)) /* object packed? */ add_object_entry(sha1, OBJ_TAG, NULL, 0); return 0; @@ -1889,14 +2153,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); @@ -1941,7 +2201,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); @@ -2070,6 +2332,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"); @@ -2106,7 +2372,7 @@ static void get_object_list(int ac, const char **av) } die("not a rev '%s'", line); } - if (handle_revision_arg(line, &revs, flags, 1)) + if (handle_revision_arg(line, &revs, flags, REVARG_CANNOT_BE_FILENAME)) die("bad revision '%s'", line); } @@ -2121,203 +2387,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, + N_("do not show progress meter"), 0), + OPT_SET_INT(0, "progress", &progress, + N_("show progress meter"), 1), + OPT_SET_INT(0, "all-progress", &progress, + N_("show progress meter during object writing phase"), 2), + OPT_BOOL(0, "all-progress-implied", + &all_progress_implied, + N_("similar to --all-progress when progress meter is shown")), + { OPTION_CALLBACK, 0, "index-version", NULL, N_("version[,offset]"), + N_("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, + N_("maximum size of each output pack file")), + OPT_BOOL(0, "local", &local, + N_("ignore borrowed objects from alternate object store")), + OPT_BOOL(0, "incremental", &incremental, + N_("ignore packed objects")), + OPT_INTEGER(0, "window", &window, + N_("limit pack window by objects")), + OPT_ULONG(0, "window-memory", &window_memory_limit, + N_("limit pack window by memory in addition to object limit")), + OPT_INTEGER(0, "depth", &depth, + N_("maximum length of delta chain allowed in the resulting pack")), + OPT_BOOL(0, "reuse-delta", &reuse_delta, + N_("reuse existing deltas")), + OPT_BOOL(0, "reuse-object", &reuse_object, + N_("reuse existing objects")), + OPT_BOOL(0, "delta-base-offset", &allow_ofs_delta, + N_("use OFS_DELTA objects")), + OPT_INTEGER(0, "threads", &delta_search_threads, + N_("use threads when searching for best delta matches")), + OPT_BOOL(0, "non-empty", &non_empty, + N_("do not create an empty pack output")), + OPT_BOOL(0, "revs", &use_internal_rev_list, + N_("read revision arguments from standard input")), + { OPTION_SET_INT, 0, "unpacked", &rev_list_unpacked, NULL, + N_("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, + N_("include objects reachable from any reference"), + PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, 1 }, + { OPTION_SET_INT, 0, "reflog", &rev_list_reflog, NULL, + N_("include objects referred by reflog entries"), + PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, 1 }, + OPT_BOOL(0, "stdout", &pack_to_stdout, + N_("output pack to stdout")), + OPT_BOOL(0, "include-tag", &include_tag, + N_("include tag objects that refer to objects to be packed")), + OPT_BOOL(0, "keep-unreachable", &keep_unreachable, + N_("keep unreachable objects")), + { OPTION_CALLBACK, 0, "unpack-unreachable", NULL, N_("time"), + N_("unpack unreachable objects newer than <time>"), + PARSE_OPT_OPTARG, option_parse_unpack_unreachable }, + OPT_BOOL(0, "thin", &thin, + N_("create thin packs")), + OPT_BOOL(0, "honor-pack-keep", &ignore_packed_keep, + N_("ignore packs that have companion .keep file")), + OPT_INTEGER(0, "compression", &pack_compression_level, + N_("pack compression level")), + OPT_SET_INT(0, "keep-true-parents", &grafts_replace_parents, + N_("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 f5c6afc5dd..649c3aaa93 100644 --- a/builtin/pack-redundant.c +++ b/builtin/pack-redundant.c @@ -301,14 +301,14 @@ static void pll_free(struct pll *l) */ static struct pll * get_permutations(struct pack_list *list, int n) { - struct pll *subset, *ret = NULL, *new_pll = NULL, *pll; + struct pll *subset, *ret = NULL, *new_pll = NULL; if (list == NULL || pack_list_size(list) < n || n == 0) return NULL; if (n == 1) { while (list) { - new_pll = xmalloc(sizeof(pll)); + new_pll = xmalloc(sizeof(*new_pll)); new_pll->pl = NULL; pack_list_insert(&new_pll->pl, list); new_pll->next = ret; @@ -321,7 +321,7 @@ static struct pll * get_permutations(struct pack_list *list, int n) while (list->next) { subset = get_permutations(list->next, n - 1); while (subset) { - new_pll = xmalloc(sizeof(pll)); + new_pll = xmalloc(sizeof(*new_pll)); new_pll->pl = subset->pl; pack_list_insert(&new_pll->pl, list); new_pll->next = ret; diff --git a/builtin/pack-refs.c b/builtin/pack-refs.c index 39a9d89fbd..b5a0f88eb8 100644 --- a/builtin/pack-refs.c +++ b/builtin/pack-refs.c @@ -3,7 +3,7 @@ #include "pack-refs.h" static char const * const pack_refs_usage[] = { - "git pack-refs [options]", + N_("git pack-refs [options]"), NULL }; @@ -11,8 +11,8 @@ int cmd_pack_refs(int argc, const char **argv, const char *prefix) { unsigned int flags = PACK_REFS_PRUNE; struct option opts[] = { - OPT_BIT(0, "all", &flags, "pack everything", PACK_REFS_ALL), - OPT_BIT(0, "prune", &flags, "prune loose refs (default)", PACK_REFS_PRUNE), + OPT_BIT(0, "all", &flags, N_("pack everything"), PACK_REFS_ALL), + OPT_BIT(0, "prune", &flags, N_("prune loose refs (default)"), PACK_REFS_PRUNE), OPT_END(), }; if (parse_options(argc, argv, prefix, opts, pack_refs_usage, 0)) diff --git a/builtin/patch-id.c b/builtin/patch-id.c index f821eb3f0b..3cfe02d5a5 100644 --- a/builtin/patch-id.c +++ b/builtin/patch-id.c @@ -56,13 +56,13 @@ static int scan_hunk_header(const char *p, int *p_before, int *p_after) return 1; } -static 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; @@ -133,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..83382c1fe1 100644 --- a/builtin/prune-packed.c +++ b/builtin/prune-packed.c @@ -4,7 +4,7 @@ #include "parse-options.h" static const char * const prune_packed_usage[] = { - "git prune-packed [-n|--dry-run] [-q|--quiet]", + N_("git prune-packed [-n|--dry-run] [-q|--quiet]"), NULL }; @@ -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); } @@ -73,8 +73,8 @@ int cmd_prune_packed(int argc, const char **argv, const char *prefix) { int opts = isatty(2) ? VERBOSE : 0; const struct option prune_packed_options[] = { - OPT_BIT('n', "dry-run", &opts, "dry run", DRY_RUN), - OPT_NEGBIT('q', "quiet", &opts, "be quiet", VERBOSE), + OPT_BIT('n', "dry-run", &opts, N_("dry run"), DRY_RUN), + OPT_NEGBIT('q', "quiet", &opts, N_("be quiet"), VERBOSE), OPT_END() }; diff --git a/builtin/prune.c b/builtin/prune.c index e65690ba37..8cb8b9186a 100644 --- a/builtin/prune.c +++ b/builtin/prune.c @@ -5,15 +5,17 @@ #include "builtin.h" #include "reachable.h" #include "parse-options.h" +#include "progress.h" #include "dir.h" static const char * const prune_usage[] = { - "git prune [-n] [-v] [--expire <time>] [--] [<head>...]", + N_("git prune [-n] [-v] [--expire <time>] [--] [<head>...]"), NULL }; 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) { @@ -23,7 +25,8 @@ static int prune_tmp_object(const char *path, const char *filename) return error("Could not stat '%s'", fullpath); if (st.st_mtime > expire) return 0; - printf("Removing stale temporary file %s\n", fullpath); + if (show_only || verbose) + printf("Removing stale temporary file %s\n", fullpath); if (!show_only) unlink_or_warn(fullpath); return 0; @@ -83,9 +86,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,11 +127,13 @@ 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__DRY_RUN(&show_only, "do not remove, show only"), - OPT__VERBOSE(&verbose, "report pruned objects"), + OPT__DRY_RUN(&show_only, N_("do not remove, show only")), + OPT__VERBOSE(&verbose, N_("report pruned objects")), + OPT_BOOL(0, "progress", &show_progress, N_("show progress")), OPT_DATE(0, "expire", &expire, - "expire objects older than <time>"), + N_("expire objects older than <time>")), OPT_END() }; char *s; @@ -152,12 +157,19 @@ 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); remove_temporary_files(get_object_directory()); - s = xstrdup(mkpath("%s/pack", get_object_directory())); + s = mkpathdup("%s/pack", get_object_directory()); remove_temporary_files(s); free(s); return 0; diff --git a/builtin/push.c b/builtin/push.c index 9cebf9ea23..db9ba30b08 100644 --- a/builtin/push.c +++ b/builtin/push.c @@ -8,9 +8,10 @@ #include "remote.h" #include "transport.h" #include "parse-options.h" +#include "submodule.h" static const char * const push_usage[] = { - "git push [<options>] [<repository> [<refspec>...]]", + N_("git push [<options>] [<repository> [<refspec>...]]"), NULL, }; @@ -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) { @@ -64,7 +66,54 @@ static void set_refspecs(const char **refs, int nr) } } -static void setup_push_upstream(struct remote *remote) +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); @@ -75,7 +124,7 @@ static void setup_push_upstream(struct remote *remote) "\n" " git push %s HEAD:<name-of-remote-branch>\n"), remote->name); - if (!branch->merge_nr || !branch->merge) + 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" @@ -86,20 +135,60 @@ static void setup_push_upstream(struct remote *remote) if (branch->merge_nr != 1) 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 char warn_unspecified_push_default_msg[] = +N_("push.default is unset; its implicit value is changing in\n" + "Git 2.0 from 'matching' to 'simple'. To squelch this message\n" + "and maintain the current behavior after the default changes, use:\n" + "\n" + " git config --global push.default matching\n" + "\n" + "To squelch this message and adopt the new behavior now, use:\n" + "\n" + " git config --global push.default simple\n" + "\n" + "See 'git help config' and search for 'push.default' for further information.\n" + "(the 'simple' mode was introduced in Git 1.7.11. Use the similar mode\n" + "'current' instead of 'simple' if you sometimes use older versions of Git)"); + +static void warn_unspecified_push_default_configuration(void) +{ + static int warn_once; + + if (warn_once++) + return; + warning("%s\n", _(warn_unspecified_push_default_msg)); +} + static void setup_default_push_refspecs(struct remote *remote) { switch (push_default) { default: + case PUSH_DEFAULT_UNSPECIFIED: + default_matching_used = 1; + warn_unspecified_push_default_configuration(); + /* fallthru */ case PUSH_DEFAULT_MATCHING: add_refspec(":"); break; + case PUSH_DEFAULT_SIMPLE: + setup_push_upstream(remote, 1); + break; + case PUSH_DEFAULT_UPSTREAM: - setup_push_upstream(remote); + setup_push_upstream(remote, 0); break; case PUSH_DEFAULT_CURRENT: @@ -113,6 +202,45 @@ static void setup_default_push_refspecs(struct remote *remote) } } +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 variable\n" + "to 'simple', '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; @@ -134,14 +262,21 @@ static int push_with_options(struct transport *transport, int flags) 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; @@ -195,13 +330,7 @@ static int do_push(const char *repo, int flags) 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 = @@ -219,6 +348,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; @@ -227,21 +379,26 @@ int cmd_push(int argc, const char **argv, const char *prefix) const char *repo = NULL; /* default repository */ struct option options[] = { OPT__VERBOSITY(&verbosity), - OPT_STRING( 0 , "repo", &repo, "repository", "repository"), - OPT_BIT( 0 , "all", &flags, "push all refs", TRANSPORT_PUSH_ALL), - OPT_BIT( 0 , "mirror", &flags, "mirror all refs", + OPT_STRING( 0 , "repo", &repo, N_("repository"), N_("repository")), + OPT_BIT( 0 , "all", &flags, N_("push all refs"), TRANSPORT_PUSH_ALL), + OPT_BIT( 0 , "mirror", &flags, N_("mirror all refs"), (TRANSPORT_PUSH_MIRROR|TRANSPORT_PUSH_FORCE)), - OPT_BOOLEAN( 0, "delete", &deleterefs, "delete refs"), - OPT_BOOLEAN( 0 , "tags", &tags, "push tags (can't be used with --all or --mirror)"), - 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), - 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", + OPT_BOOLEAN( 0, "delete", &deleterefs, N_("delete refs")), + OPT_BOOLEAN( 0 , "tags", &tags, N_("push tags (can't be used with --all or --mirror)")), + OPT_BIT('n' , "dry-run", &flags, N_("dry run"), TRANSPORT_PUSH_DRY_RUN), + OPT_BIT( 0, "porcelain", &flags, N_("machine-readable output"), TRANSPORT_PUSH_PORCELAIN), + OPT_BIT('f', "force", &flags, N_("force updates"), TRANSPORT_PUSH_FORCE), + { OPTION_CALLBACK, 0, "recurse-submodules", &flags, N_("check"), + N_("control recursive pushing of submodules"), + PARSE_OPT_OPTARG, option_parse_recurse_submodules }, + OPT_BOOLEAN( 0 , "thin", &thin, N_("use thin pack")), + OPT_STRING( 0 , "receive-pack", &receivepack, "receive-pack", N_("receive pack program")), + OPT_STRING( 0 , "exec", &receivepack, "receive-pack", N_("receive pack program")), + OPT_BIT('u', "set-upstream", &flags, N_("set upstream for git pull/status"), TRANSPORT_PUSH_SET_UPSTREAM), - OPT_BOOLEAN(0, "progress", &progress, "force progress reporting"), + OPT_BOOL(0, "progress", &progress, N_("force progress reporting")), + OPT_BIT(0, "prune", &flags, N_("prune locally removed refs"), + TRANSPORT_PUSH_PRUNE), OPT_END() }; diff --git a/builtin/read-tree.c b/builtin/read-tree.c index df6c4c8819..042ac1b84f 100644 --- a/builtin/read-tree.c +++ b/builtin/read-tree.c @@ -33,7 +33,7 @@ static int list_tree(unsigned char *sha1) } static const char * const read_tree_usage[] = { - "git read-tree [[-m [--trivial] [--aggressive] | --reset | --prefix=<prefix>] [-u [--exclude-per-directory=<gitignore>] | -i]] [--no-sparse-checkout] [--index-output=<file>] (--empty | <tree-ish1> [<tree-ish2> [<tree-ish3>]])", + N_("git read-tree [[-m [--trivial] [--aggressive] | --reset | --prefix=<prefix>] [-u [--exclude-per-directory=<gitignore>] | -i]] [--no-sparse-checkout] [--index-output=<file>] (--empty | <tree-ish1> [<tree-ish2> [<tree-ish3>]])"), NULL }; @@ -104,37 +104,37 @@ 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, N_("file"), + N_("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, "be verbose"), - OPT_GROUP("Merging"), + N_("only empty the index"), 1), + OPT__VERBOSE(&opts.verbose_update, N_("be verbose")), + OPT_GROUP(N_("Merging")), OPT_SET_INT('m', NULL, &opts.merge, - "perform a merge in addition to a read", 1), + N_("perform a merge in addition to a read"), 1), OPT_SET_INT(0, "trivial", &opts.trivial_merges_only, - "3-way merge if no file level merging required", 1), + N_("3-way merge if no file level merging required"), 1), OPT_SET_INT(0, "aggressive", &opts.aggressive, - "3-way merge in presence of adds and removes", 1), + N_("3-way merge in presence of adds and removes"), 1), OPT_SET_INT(0, "reset", &opts.reset, - "same as -m, but discard unmerged entries", 1), - { OPTION_STRING, 0, "prefix", &opts.prefix, "<subdirectory>/", - "read the tree into the index under <subdirectory>/", + N_("same as -m, but discard unmerged entries"), 1), + { OPTION_STRING, 0, "prefix", &opts.prefix, N_("<subdirectory>/"), + N_("read the tree into the index under <subdirectory>/"), PARSE_OPT_NONEG | PARSE_OPT_LITERAL_ARGHELP }, OPT_SET_INT('u', NULL, &opts.update, - "update working tree with merge result", 1), + N_("update working tree with merge result"), 1), { OPTION_CALLBACK, 0, "exclude-per-directory", &opts, - "gitignore", - "allow explicitly ignored files to be overwritten", + N_("gitignore"), + N_("allow explicitly ignored files to be overwritten"), 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"), + N_("don't check the working tree after merging"), 1), + OPT__DRY_RUN(&opts.dry_run, N_("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), + N_("skip applying sparse checkout filter"), 1), OPT_SET_INT(0, "debug-unpack", &opts.debug_unpack, - "debug unpack-trees", 1), + N_("debug unpack-trees"), 1), OPT_END() }; diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c index e1a687ad07..ff781febca 100644 --- a/builtin/receive-pack.c +++ b/builtin/receive-pack.c @@ -11,6 +11,8 @@ #include "transport.h" #include "string-list.h" #include "sha1-array.h" +#include "connected.h" +#include "version.h" static const char receive_pack_usage[] = "git receive-pack <git-dir>"; @@ -25,16 +27,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) @@ -79,6 +84,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; @@ -107,31 +117,66 @@ 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", + packet_write(1, "%s %s%c%s%s agent=%s\n", sha1_to_hex(sha1), path, 0, - " report-status delete-refs side-band-64k", - prefer_ofs_delta ? " ofs-delta" : ""); + " report-status delete-refs side-band-64k quiet", + prefer_ofs_delta ? " ofs-delta" : "", + git_user_agent_sanitized()); 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 */ @@ -189,21 +234,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; @@ -231,15 +270,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) @@ -247,6 +284,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"; @@ -333,17 +415,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; @@ -371,7 +458,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; @@ -393,7 +480,6 @@ static const char *update(struct command *cmd) !prefixcmp(name, "refs/heads/")) { struct object *old_object, *new_object; struct commit *old_commit, *new_commit; - struct commit_list *bases, *ent; old_object = parse_object(old_sha1); new_object = parse_object(new_sha1); @@ -406,12 +492,7 @@ static const char *update(struct command *cmd) } old_commit = (struct commit *)old_object; new_commit = (struct commit *)new_object; - bases = get_merge_bases(old_commit, new_commit, 1); - for (ent = bases; ent; ent = ent->next) - if (!hashcmp(old_sha1, ent->item->object.sha1)) - break; - free_commit_list(bases); - if (!ent) { + if (!in_merge_bases(old_commit, new_commit)) { rp_error("denying non-fast-forward %s" " (you should pull first)", name); return "non-fast-forward"; @@ -424,17 +505,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"; @@ -456,7 +542,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++; } @@ -467,7 +553,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); @@ -491,17 +577,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; @@ -540,12 +638,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; @@ -553,23 +695,37 @@ static void execute_commands(struct command *commands, const char *unpacker_erro if (unpacker_error) { for (cmd = commands; cmd; cmd = cmd->next) - cmd->error_string = "n/a (unpacker error)"; + cmd->error_string = "unpacker error"; 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->error_string) + continue; - for (cmd = commands; cmd; cmd = cmd->next) - if (!cmd->skip_update) - cmd->error_string = update(cmd); + if (cmd->skip_update) + continue; + + cmd->error_string = update(cmd); + } } static struct command *read_head_info(void) @@ -599,10 +755,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); @@ -636,11 +795,16 @@ static const char *parse_pack_header(struct pack_header *hdr) static const char *pack_lockfile; -static const char *unpack(void) +static const char *unpack(int err_fd) { 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) @@ -651,13 +815,21 @@ static const char *unpack(void) if (ntohl(hdr.hdr_entries) < unpack_limit) { int code, i = 0; - const char *unpacker[4]; + struct child_process child; + 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; - code = run_command_v_opt(unpacker, RUN_GIT_CMD); + memset(&child, 0, sizeof(child)); + child.argv = unpacker; + child.no_stdout = 1; + child.err = err_fd; + child.git_cmd = 1; + code = run_command(&child); if (!code) return NULL; return "unpack-objects abnormal exit"; @@ -673,7 +845,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; @@ -682,6 +854,7 @@ static const char *unpack(void) memset(&ip, 0, sizeof(ip)); ip.argv = keeper; ip.out = -1; + ip.err = err_fd; ip.git_cmd = 1; status = start_command(&ip); if (status) { @@ -698,6 +871,26 @@ static const char *unpack(void) } } +static const char *unpack_with_sideband(void) +{ + struct async muxer; + const char *ret; + + if (!use_sideband) + return unpack(0); + + memset(&muxer, 0, sizeof(muxer)); + muxer.proc = copy_to_sideband; + muxer.in = -1; + if (start_async(&muxer)) + return NULL; + + ret = unpack(muxer.in); + + finish_async(&muxer); + return ret; +} + static void report(struct command *commands, const char *unpack_status) { struct command *cmd; @@ -732,25 +925,6 @@ static int delete_only(struct command *commands) return 1; } -static void add_one_alternate_sha1(const unsigned char sha1[20], void *unused) -{ - add_extra_ref(".have", sha1, 0); -} - -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 add_alternate_refs(void) -{ - struct sha1_array sa = SHA1_ARRAY_INIT; - for_each_alternate_ref(collect_one_alternate_ref, &sa); - sha1_array_for_each_unique(&sa, add_one_alternate_sha1, NULL); - sha1_array_clear(&sa); -} - int cmd_receive_pack(int argc, const char **argv, const char *prefix) { int advertise_refs = 0; @@ -766,6 +940,11 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix) const char *arg = *argv++; if (*arg == '-') { + if (!strcmp(arg, "--quiet")) { + quiet = 1; + continue; + } + if (!strcmp(arg, "--advertise-refs")) { advertise_refs = 1; continue; @@ -800,12 +979,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; @@ -814,19 +988,20 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix) const char *unpack_status = NULL; if (!delete_only(commands)) - unpack_status = unpack(); + unpack_status = unpack_with_sideband(); execute_commands(commands, unpack_status); if (pack_lockfile) 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[] = { "gc", "--auto", "--quiet", NULL, }; - run_command_v_opt(argv_gc_auto, RUN_GIT_CMD); + int opt = RUN_GIT_CMD | RUN_COMMAND_STDOUT_TO_STDERR; + run_command_v_opt(argv_gc_auto, opt); } if (auto_update_server_info) update_server_info(0); diff --git a/builtin/reflog.c b/builtin/reflog.c index 3a9c80f3db..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'); } diff --git a/builtin/remote.c b/builtin/remote.c index 9c746af460..937484d7c7 100644 --- a/builtin/remote.c +++ b/builtin/remote.c @@ -8,66 +8,66 @@ #include "refs.h" static const char * const builtin_remote_usage[] = { - "git remote [-v | --verbose]", - "git remote add [-t <branch>] [-m <master>] [-f] [--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-url --add <name> <newurl>", - "git remote set-url --delete <name> <url>", + N_("git remote [-v | --verbose]"), + N_("git remote add [-t <branch>] [-m <master>] [-f] [--tags|--no-tags] [--mirror=<fetch|push>] <name> <url>"), + N_("git remote rename <old> <new>"), + N_("git remote remove <name>"), + N_("git remote set-head <name> (-a | -d | <branch>)"), + N_("git remote [-v | --verbose] show [-n] <name>"), + N_("git remote prune [-n | --dry-run] <name>"), + N_("git remote [-v | --verbose] update [-p | --prune] [(<group> | <remote>)...]"), + N_("git remote set-branches [--add] <name> <branch>..."), + N_("git remote set-url [--push] <name> <newurl> [<oldurl>]"), + N_("git remote set-url --add <name> <newurl>"), + N_("git remote set-url --delete <name> <url>"), NULL }; static const char * const builtin_remote_add_usage[] = { - "git remote add [<options>] <name> <url>", + N_("git remote add [<options>] <name> <url>"), NULL }; static const char * const builtin_remote_rename_usage[] = { - "git remote rename <old> <new>", + N_("git remote rename <old> <new>"), NULL }; static const char * const builtin_remote_rm_usage[] = { - "git remote rm <name>", + N_("git remote remove <name>"), NULL }; static const char * const builtin_remote_sethead_usage[] = { - "git remote set-head <name> (-a | -d | <branch>])", + N_("git remote set-head <name> (-a | -d | <branch>)"), NULL }; static const char * const builtin_remote_setbranches_usage[] = { - "git remote set-branches <name> <branch>...", - "git remote set-branches --add <name> <branch>...", + N_("git remote set-branches <name> <branch>..."), + N_("git remote set-branches --add <name> <branch>..."), NULL }; static const char * const builtin_remote_show_usage[] = { - "git remote show [<options>] <name>", + N_("git remote show [<options>] <name>"), NULL }; static const char * const builtin_remote_prune_usage[] = { - "git remote prune [<options>] <name>", + N_("git remote prune [<options>] <name>"), NULL }; static const char * const builtin_remote_update_usage[] = { - "git remote update [<options>] [<group> | <remote>]...", + N_("git remote update [<options>] [<group> | <remote>]..."), NULL }; static const char * const builtin_remote_seturl_usage[] = { - "git remote set-url [--push] <name> <newurl> [<oldurl>]", - "git remote set-url --add <name> <newurl>", - "git remote set-url --delete <name> <url>", + N_("git remote set-url [--push] <name> <newurl> [<oldurl>]"), + N_("git remote set-url --add <name> <newurl>"), + N_("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; } @@ -137,8 +127,8 @@ static int add_branch(const char *key, const char *branchname, } static const char mirror_advice[] = -"--mirror is dangerous and deprecated; please\n" -"\t use --mirror=fetch or --mirror=push instead"; +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) { @@ -146,7 +136,7 @@ static int parse_mirror_opt(const struct option *opt, const char *arg, int not) if (not) *mirror = MIRROR_NONE; else if (!arg) { - warning("%s", mirror_advice); + warning("%s", _(mirror_advice)); *mirror = MIRROR_BOTH; } else if (!strcmp(arg, "fetch")) @@ -154,7 +144,7 @@ static int parse_mirror_opt(const struct option *opt, const char *arg, int not) else if (!strcmp(arg, "push")) *mirror = MIRROR_PUSH; else - return error("unknown mirror argument: %s", arg); + return error(_("unknown mirror argument: %s"), arg); return 0; } @@ -170,17 +160,17 @@ static int add(int argc, const char **argv) int i; struct option options[] = { - OPT_BOOLEAN('f', "fetch", &fetch, "fetch the remote branches"), + OPT_BOOLEAN('f', "fetch", &fetch, N_("fetch the remote branches")), OPT_SET_INT(0, "tags", &fetch_tags, - "import all tags and associated objects when fetching", + N_("import all tags and associated objects when fetching"), 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('m', "master", &master, "branch", "master branch"), - { OPTION_CALLBACK, 0, "mirror", &mirror, "push|fetch", - "set up remote as a mirror to push to or fetch from", + N_("or do not fetch any tag at all (--no-tags)"), TAGS_UNSET), + OPT_STRING_LIST('t', "track", &track, N_("branch"), + N_("branch(es) to track")), + OPT_STRING('m', "master", &master, N_("branch"), N_("master branch")), + { OPTION_CALLBACK, 0, "mirror", &mirror, N_("push|fetch"), + N_("set up remote as a mirror to push to or fetch from"), PARSE_OPT_OPTARG, parse_mirror_opt }, OPT_END() }; @@ -192,9 +182,9 @@ static int add(int argc, const char **argv) usage_with_options(builtin_remote_add_usage, options); if (mirror && master) - die("specifying a master branch makes no sense with --mirror"); + 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"); + die(_("specifying branches to track makes sense only with fetch mirrors")); name = argv[0]; url = argv[1]; @@ -202,11 +192,11 @@ static int add(int argc, const char **argv) 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)) @@ -250,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); @@ -306,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, ' '); @@ -346,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)); @@ -399,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) { @@ -447,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); @@ -455,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; @@ -544,7 +534,7 @@ static int add_branch_for_removal(const char *refname, } /* don't delete non-remote-tracking refs */ - if (prefixcmp(refname, "refs/remotes")) { + if (prefixcmp(refname, "refs/remotes/")) { /* advise user how to delete local branches */ if (!prefixcmp(refname, "refs/heads/")) string_list_append(branches->skipped, @@ -583,7 +573,7 @@ static int read_remote_branches(const char *refname, 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 @@ -602,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); @@ -646,30 +636,30 @@ 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; @@ -684,13 +674,13 @@ static int mv(int argc, const char **argv) strlen(rename.old), rename.new, strlen(rename.new)); } else - warning("Not updating non-default fetch respec\n" - "\t%s\n" - "\tPlease update the configuration manually if necessary.", + 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(); @@ -701,7 +691,7 @@ 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); } } } @@ -719,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; @@ -738,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; @@ -757,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; } @@ -771,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; } @@ -799,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++) { @@ -840,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 branch outside the refs/remotes/ hierarchy was not removed;\n" - "to delete it, use:\n" : - "Note: Some branches outside the refs/remotes/ hierarchy were not removed;\n" - "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); @@ -896,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(); @@ -949,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"); @@ -997,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, @@ -1053,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; } @@ -1090,7 +1088,7 @@ static int show(int argc, const char **argv) { int no_query = 0, result = 0, query_flag = 0; struct option options[] = { - OPT_BOOLEAN('n', NULL, &no_query, "do not query remotes"), + OPT_BOOLEAN('n', NULL, &no_query, N_("do not query remotes")), OPT_END() }; struct ref_states states; @@ -1117,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; @@ -1128,18 +1126,18 @@ static int show(int argc, const char **argv) url_nr = states.remote->url_nr; } for (i = 0; i < url_nr; i++) - printf(" Push URL: %s\n", url[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); } @@ -1150,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); @@ -1161,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); @@ -1195,9 +1196,9 @@ static int set_head(int argc, const char **argv) struct option options[] = { OPT_BOOLEAN('a', "auto", &opt_a, - "set refs/remotes/<name>/HEAD according to remote"), + N_("set refs/remotes/<name>/HEAD according to remote")), OPT_BOOLEAN('d', "delete", &opt_d, - "delete refs/remotes/<name>/HEAD"), + N_("delete refs/remotes/<name>/HEAD")), OPT_END() }; argc = parse_options(argc, argv, NULL, options, builtin_remote_sethead_usage, @@ -1212,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); @@ -1224,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); @@ -1250,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, "dry run"), + OPT__DRY_RUN(&dry_run, N_("dry run")), OPT_END() }; @@ -1271,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++) { @@ -1291,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); } @@ -1314,7 +1318,7 @@ static int update(int argc, const char **argv) int i, prune = 0; struct option options[] = { OPT_BOOLEAN('p', "prune", &prune, - "prune remotes after fetching"), + N_("prune remotes after fetching")), OPT_END() }; const char **fetch_argv; @@ -1380,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)) { @@ -1400,15 +1404,15 @@ static int set_branches(int argc, const char **argv) { int add_mode = 0; struct option options[] = { - OPT_BOOLEAN('\0', "add", &add_mode, "add branch"), + OPT_BOOLEAN('\0', "add", &add_mode, N_("add branch")), OPT_END() }; 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; @@ -1429,18 +1433,18 @@ static int set_url(int argc, const char **argv) struct strbuf name_buf = STRBUF_INIT; struct option options[] = { OPT_BOOLEAN('\0', "push", &push_mode, - "manipulate push URLs"), + N_("manipulate push URLs")), OPT_BOOLEAN('\0', "add", &add_mode, - "add URL"), + N_("add URL")), OPT_BOOLEAN('\0', "delete", &delete_mode, - "delete URLs"), + N_("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); @@ -1454,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) { @@ -1480,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)) @@ -1488,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); @@ -1562,7 +1566,7 @@ static int show_all(void) int cmd_remote(int argc, const char **argv, const char *prefix) { struct option options[] = { - OPT__VERBOSE(&verbose, "be verbose; must be placed before a subcommand"), + OPT__VERBOSE(&verbose, N_("be verbose; must be placed before a subcommand")), OPT_END() }; int result; @@ -1576,7 +1580,7 @@ int cmd_remote(int argc, const char **argv, const char *prefix) result = add(argc, argv); else if (!strcmp(argv[0], "rename")) result = mv(argc, argv); - else if (!strcmp(argv[0], "rm")) + else if (!strcmp(argv[0], "rm") || !strcmp(argv[0], "remove")) result = rm(argc, argv); else if (!strcmp(argv[0], "set-head")) result = set_head(argc, argv); @@ -1591,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..398ccd5eaa 100644 --- a/builtin/replace.c +++ b/builtin/replace.c @@ -14,9 +14,9 @@ #include "parse-options.h" static const char * const git_replace_usage[] = { - "git replace [-f] <object> <replacement>", - "git replace -d <object>...", - "git replace -l [<pattern>]", + N_("git replace [-f] <object> <replacement>"), + N_("git replace -d <object>..."), + N_("git replace -l [<pattern>]"), NULL }; @@ -46,24 +46,27 @@ typedef int (*each_replace_name_fn)(const char *name, const char *ref, static int for_each_replace_name(const char **argv, each_replace_name_fn fn) { - const char **p; + const char **p, *full_hex; char ref[PATH_MAX]; int had_error = 0; unsigned char sha1[20]; for (p = argv; *p; p++) { - if (snprintf(ref, sizeof(ref), "refs/replace/%s", *p) - >= sizeof(ref)) { - error("replace ref name too long: %.*s...", 50, *p); + if (get_sha1(*p, sha1)) { + error("Failed to resolve '%s' as a valid ref.", *p); had_error = 1; continue; } - if (!resolve_ref(ref, sha1, 1, NULL)) { - error("replace ref '%s' not found.", *p); + full_hex = sha1_to_hex(sha1); + snprintf(ref, sizeof(ref), "refs/replace/%s", full_hex); + /* read_ref() may reuse the buffer */ + full_hex = ref + strlen("refs/replace/"); + if (read_ref(ref, sha1)) { + error("replace ref '%s' not found.", full_hex); had_error = 1; continue; } - if (fn(*p, ref, sha1)) + if (fn(full_hex, ref, sha1)) had_error = 1; } return had_error; @@ -94,10 +97,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); @@ -115,9 +118,9 @@ int cmd_replace(int argc, const char **argv, const char *prefix) { int list = 0, delete = 0, force = 0; struct option options[] = { - OPT_BOOLEAN('l', NULL, &list, "list replace refs"), - OPT_BOOLEAN('d', NULL, &delete, "delete replace refs"), - OPT_BOOLEAN('f', NULL, &force, "replace the ref if it exists"), + OPT_BOOLEAN('l', NULL, &list, N_("list replace refs")), + OPT_BOOLEAN('d', NULL, &delete, N_("delete replace refs")), + OPT_BOOLEAN('f', NULL, &force, N_("replace the ref if it exists")), OPT_END() }; diff --git a/builtin/rerere.c b/builtin/rerere.c index 08213c7c0b..dc1708e6d6 100644 --- a/builtin/rerere.c +++ b/builtin/rerere.c @@ -8,7 +8,7 @@ #include "xdiff-interface.h" static const char * const rerere_usage[] = { - "git rerere [clear | forget path... | status | remaining | diff | gc]", + N_("git rerere [clear | forget path... | status | remaining | diff | gc]"), NULL, }; @@ -53,7 +53,7 @@ int cmd_rerere(int argc, const char **argv, const char *prefix) struct option options[] = { OPT_SET_INT(0, "rerere-autoupdate", &autoupdate, - "register clean resolutions in index", 1), + N_("register clean resolutions in index"), 1), OPT_END(), }; diff --git a/builtin/reset.c b/builtin/reset.c index 811e8e252c..915cc9f86f 100644 --- a/builtin/reset.c +++ b/builtin/reset.c @@ -22,9 +22,9 @@ #include "cache-tree.h" static const char * const git_reset_usage[] = { - "git reset [--mixed | --soft | --hard | --merge | --keep] [-q] [<commit>]", - "git reset [-q] <commit> [--] <paths>...", - "git reset --patch [<commit>] [--] [<paths>...]", + N_("git reset [--mixed | --soft | --hard | --merge | --keep] [-q] [<commit>]"), + N_("git reset [-q] <commit> [--] <paths>..."), + N_("git reset --patch [<commit>] [--] [<paths>...]"), NULL }; @@ -43,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)); @@ -84,6 +85,12 @@ static int reset_index_file(const unsigned char *sha1, int reset_type, int quiet 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.")); @@ -228,17 +235,17 @@ int cmd_reset(int argc, const char **argv, const char *prefix) struct commit *commit; struct strbuf msg = STRBUF_INIT; const struct option options[] = { - OPT__QUIET(&quiet, "be quiet, only report errors"), + OPT__QUIET(&quiet, N_("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), + N_("reset HEAD and index"), MIXED), + OPT_SET_INT(0, "soft", &reset_type, N_("reset only HEAD"), SOFT), OPT_SET_INT(0, "hard", &reset_type, - "reset HEAD, index and working tree", HARD), + N_("reset HEAD, index and working tree"), HARD), OPT_SET_INT(0, "merge", &reset_type, - "reset HEAD, index and working tree", MERGE), + N_("reset HEAD, index and working tree"), MERGE), OPT_SET_INT(0, "keep", &reset_type, - "reset HEAD but keep local changes", KEEP), - OPT_BOOLEAN('p', "patch", &patch_mode, "select hunks interactively"), + N_("reset HEAD but keep local changes"), KEEP), + OPT_BOOLEAN('p', "patch", &patch_mode, N_("select hunks interactively")), OPT_END() }; @@ -269,7 +276,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix) * Otherwise, argv[i] could be either <rev> or <paths> and * has to be unambiguous. */ - else if (!get_sha1(argv[i], sha1)) { + else if (!get_sha1_committish(argv[i], sha1)) { /* * Ok, argv[i] looks like a rev; it should not * be a filename. @@ -278,13 +285,19 @@ int cmd_reset(int argc, const char **argv, const char *prefix) rev = argv[i++]; } else { /* Otherwise we treat this as a filename */ - verify_filename(prefix, argv[i]); + verify_filename(prefix, argv[i], 1); } } - if (get_sha1(rev, sha1)) + if (get_sha1_committish(rev, sha1)) die(_("Failed to resolve '%s' as a valid ref."), rev); + /* + * NOTE: As "git reset $treeish -- $path" should be usable on + * any tree-ish, this is not strictly correct. We are not + * moving the HEAD to any commit; we are merely resetting the + * entries in the index to that of a treeish. + */ commit = lookup_commit_reference(sha1); if (!commit) die(_("Could not parse object '%s'."), rev); diff --git a/builtin/rev-list.c b/builtin/rev-list.c index 56727e8c1d..67701be551 100644 --- a/builtin/rev-list.c +++ b/builtin/rev-list.c @@ -52,6 +52,11 @@ 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) { @@ -104,6 +109,7 @@ 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; + ctx.date_mode_explicit = revs->date_mode_explicit; ctx.fmt = revs->commit_format; pretty_print_commit(&ctx, commit, &buf); if (revs->graph) { @@ -168,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) @@ -198,62 +201,6 @@ static void show_edge(struct commit *commit) printf("-%s\n", sha1_to_hex(commit->object.sha1)); } -static inline int log2i(int n) -{ - int log2 = 0; - - for (; n > 1; n >>= 1) - log2++; - - return log2; -} - -static inline int exp2i(int n) -{ - return 1 << n; -} - -/* - * Estimate the number of bisect steps left (after the current step) - * - * For any x between 0 included and 2^n excluded, the probability for - * n - 1 steps left looks like: - * - * P(2^n + x) == (2^n - x) / (2^n + x) - * - * and P(2^n + x) < 0.5 means 2^n < 3x - */ -int estimate_bisect_steps(int all) -{ - int n, x, e; - - if (all < 3) - return 0; - - n = log2i(all); - e = exp2i(n); - x = all - e; - - return (e < 3 * x) ? n : n - 1; -} - -void print_commit_list(struct commit_list *list, - const char *format_cur, - const char *format_last) -{ - for ( ; list; list = list->next) { - const char *format = list->next ? format_cur : format_last; - printf(format, sha1_to_hex(list->item->object.sha1)); - } -} - -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); @@ -266,12 +213,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, @@ -299,9 +246,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); @@ -320,7 +264,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); @@ -333,7 +276,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]; @@ -352,7 +296,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; } @@ -403,10 +347,7 @@ 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 && revs.cherry_mark) diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c index 4c19f844a9..f267a1d3b5 100644 --- a/builtin/rev-parse.c +++ b/builtin/rev-parse.c @@ -195,6 +195,12 @@ static int anti_reference(const char *refname, const unsigned char *sha1, int fl return 0; } +static int show_abbrev(const unsigned char *sha1, void *cb_data) +{ + show_rev(NORMAL, sha1, NULL); + return 0; +} + static void show_datestring(const char *flag, const char *datestr) { static char buffer[100]; @@ -224,6 +230,7 @@ static int try_difference(const char *arg) const char *next; const char *this; int symmetric; + static const char head_by_default[] = "HEAD"; if (!(dotdot = strstr(arg, ".."))) return 0; @@ -235,10 +242,21 @@ static int try_difference(const char *arg) next += symmetric; if (!*next) - next = "HEAD"; + next = head_by_default; if (dotdot == arg) - this = "HEAD"; - if (!get_sha1(this, sha1) && !get_sha1(next, end)) { + this = head_by_default; + + if (this == head_by_default && next == head_by_default && + !symmetric) { + /* + * Just ".."? That is not a range but the + * pathspec for the parent directory. + */ + *dotdot = '.'; + return 0; + } + + if (!get_sha1_committish(this, sha1) && !get_sha1_committish(next, end)) { show_rev(NORMAL, end, next); show_rev(symmetric ? NORMAL : REVERSED, sha1, this); if (symmetric) { @@ -278,7 +296,7 @@ static int try_parent_shorthands(const char *arg) return 0; *dotdot = 0; - if (get_sha1(arg, sha1)) + if (get_sha1_committish(arg, sha1)) return 0; if (!parents_only) @@ -318,15 +336,15 @@ static int cmd_parseopt(int argc, const char **argv, const char *prefix) { static int keep_dashdash = 0, stop_at_non_option = 0; static char const * const parseopt_usage[] = { - "git rev-parse --parseopt [options] -- [<args>...]", + N_("git rev-parse --parseopt [options] -- [<args>...]"), NULL }; static struct option parseopt_opts[] = { OPT_BOOLEAN(0, "keep-dashdash", &keep_dashdash, - "keep the `--` passed as an arg"), + N_("keep the `--` passed as an arg")), OPT_BOOLEAN(0, "stop-at-non-option", &stop_at_non_option, - "stop parsing after the " - "first non-option argument"), + N_("stop parsing after the " + "first non-option argument")), OPT_END(), }; @@ -443,11 +461,11 @@ static void die_no_single_rev(int quiet) } static const char builtin_rev_parse_usage[] = -"git rev-parse --parseopt [options] -- [<args>...]\n" -" or: git rev-parse --sq-quote [<arg>...]\n" -" or: git rev-parse [options] [<arg>...]\n" -"\n" -"Run \"git rev-parse --parseopt -h\" for more information on the first usage."; +N_("git rev-parse --parseopt [options] -- [<args>...]\n" + " or: git rev-parse --sq-quote [<arg>...]\n" + " or: git rev-parse [options] [<arg>...]\n" + "\n" + "Run \"git rev-parse --parseopt -h\" for more information on the first usage."); int cmd_rev_parse(int argc, const char **argv, const char *prefix) { @@ -468,6 +486,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); @@ -478,7 +504,7 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) if (as_is) { if (show_file(arg) && as_is < 2) - verify_filename(prefix, arg); + verify_filename(prefix, arg, 0); continue; } if (!strcmp(arg,"-n")) { @@ -581,6 +607,10 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) for_each_ref(show_reference, NULL); continue; } + if (!prefixcmp(arg, "--disambiguate=")) { + for_each_abbrev(arg + 15, show_abbrev, NULL); + continue; + } if (!strcmp(arg, "--bisect")) { for_each_ref_in("refs/bisect/bad", show_reference, NULL); for_each_ref_in("refs/bisect/good", anti_reference, NULL); @@ -626,6 +656,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")) { @@ -724,7 +756,7 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) as_is = 1; if (!show_file(arg)) continue; - verify_filename(prefix, arg); + verify_filename(prefix, arg, 1); } if (verify) { if (revs_count == 1) { diff --git a/builtin/revert.c b/builtin/revert.c index 1f27c63343..c5e36b94c0 100644 --- a/builtin/revert.c +++ b/builtin/revert.c @@ -1,18 +1,11 @@ #include "cache.h" #include "builtin.h" -#include "object.h" -#include "commit.h" -#include "tag.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. @@ -26,580 +19,222 @@ */ static const char * const revert_usage[] = { - "git revert [options] <commit-ish>", + N_("git revert [options] <commit-ish>"), + N_("git revert <subcommand>"), NULL }; static const char * const cherry_pick_usage[] = { - "git cherry-pick [options] <commit-ish>", + N_("git cherry-pick [options] <commit-ish>"), + N_("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; - -/* Merge strategy. */ -static const char *strategy; -static const char **xopts; -static size_t xopts_nr, xopts_alloc; - -#define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION" - -static char *get_encoding(const char *message); +static const char *action_name(const struct replay_opts *opts) +{ + return opts->action == REPLAY_REVERT ? "revert" : "cherry-pick"; +} -static const char * const *revert_or_cherry_pick_usage(void) +static const char * const *revert_or_cherry_pick_usage(struct replay_opts *opts) { - return action == REVERT ? revert_usage : cherry_pick_usage; + return opts->action == REPLAY_REVERT ? revert_usage : cherry_pick_usage; } static int option_parse_x(const struct option *opt, const char *arg, int unset) { + struct replay_opts **opts_ptr = opt->value; + struct replay_opts *opts = *opts_ptr; + if (unset) return 0; - ALLOC_GROW(xopts, xopts_nr + 1, xopts_alloc); - xopts[xopts_nr++] = xstrdup(arg); + ALLOC_GROW(opts->xopts, opts->xopts_nr + 1, opts->xopts_alloc); + opts->xopts[opts->xopts_nr++] = xstrdup(arg); return 0; } -static void parse_args(int argc, const char **argv) +static void verify_opt_compatible(const char *me, const char *base_opt, ...) { - 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"), - { OPTION_BOOLEAN, 'r', NULL, &noop, NULL, "no-op (backward compatibility)", - PARSE_OPT_NOARG | PARSE_OPT_HIDDEN, NULL, 0 }, - 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_CALLBACK('X', "strategy-option", &xopts, "option", - "option for merge strategy", option_parse_x), - OPT_END(), - OPT_END(), - OPT_END(), - }; + const char *this_opt; + va_list ap; - 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")); + va_start(ap, base_opt); + while ((this_opt = va_arg(ap, const char *))) { + if (va_arg(ap, int)) + break; } + va_end(ap); - 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; -} - -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) -{ - 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); - - subject_len = find_commit_subject(out->message, &subject); - - 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'; - return 0; -} - -static void free_message(struct commit_message *msg) -{ - free(msg->parent_label); - free(msg->reencoded_message); + if (this_opt) + die(_("%s: %s cannot be used with %s"), me, this_opt, base_opt); } -static char *get_encoding(const char *message) +static void verify_opt_mutually_compatible(const char *me, ...) { - const char *p = message, *eol; + const char *opt1, *opt2 = NULL; + va_list ap; - 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++; + va_start(ap, me); + while ((opt1 = va_arg(ap, const char *))) { + if (va_arg(ap, int)) + break; } - 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 write_cherry_pick_head(void) -{ - int fd; - struct strbuf buf = STRBUF_INIT; - - strbuf_addf(&buf, "%s\n", sha1_to_hex(commit->object.sha1)); - - fd = open(git_path("CHERRY_PICK_HEAD"), O_WRONLY | O_CREAT, 0666); - if (fd < 0) - die_errno(_("Could not open '%s' for writing"), - git_path("CHERRY_PICK_HEAD")); - if (write_in_full(fd, buf.buf, buf.len) != buf.len || close(fd)) - die_errno(_("Could not write to '%s'"), git_path("CHERRY_PICK_HEAD")); - strbuf_release(&buf); -} - -static void advise(const char *advice, ...) -{ - va_list params; - - va_start(params, advice); - vreportf("hint: ", advice, params); - va_end(params); -} - -static void print_advice(void) -{ - char *msg = getenv("GIT_CHERRY_PICK_HELP"); - - if (msg) { - fprintf(stderr, "%s\n", msg); - /* - * A conflict has occured but the porcelain - * (typically rebase --interactive) wants to take care - * of the commit itself so remove CHERRY_PICK_HEAD - */ - unlink(git_path("CHERRY_PICK_HEAD")); - return; - } - - advise("after resolving the conflicts, mark the corrected paths"); - advise("with 'git add <paths>' or 'git rm <paths>'"); - advise("and commit the result with 'git commit'"); -} - -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) { - if (action == REVERT) - die(_("Your local changes would be overwritten by revert.\n" - "Please, commit your changes or stash them to proceed.")); - else - die(_("Your local changes would be overwritten by cherry-pick.\n" - "Please, commit your changes or stash them to proceed.")); - } else { - if (action == REVERT) - die(_("Your local changes would be overwritten by revert.\n")); - else - die(_("Your local changes would be overwritten by cherry-pick.\n")); - } - } -} - -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; - const char **xopt; - static struct lock_file index_lock; - - index_fd = hold_locked_index(&index_lock, 1); - - read_cache(); - - 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(); - - for (xopt = xopts; xopt != xopts + xopts_nr; xopt++) - parse_merge_opt(&o, *xopt); - - 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))) - /* TRANSLATORS: %s will be "revert" or "cherry-pick" */ - 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, N_("end revert or cherry-pick sequence")), + OPT_BOOLEAN(0, "continue", &contin, N_("resume revert or cherry-pick sequence")), + OPT_BOOLEAN(0, "abort", &rollback, N_("cancel revert or cherry-pick sequence")), + OPT_BOOLEAN('n', "no-commit", &opts->no_commit, N_("don't automatically commit")), + OPT_BOOLEAN('e', "edit", &opts->edit, N_("edit the commit message")), + OPT_NOOP_NOARG('r', NULL), + OPT_BOOLEAN('s', "signoff", &opts->signoff, N_("add Signed-off-by:")), + OPT_INTEGER('m', "mainline", &opts->mainline, N_("parent number")), + OPT_RERERE_AUTOUPDATE(&opts->allow_rerere_auto), + OPT_STRING(0, "strategy", &opts->strategy, N_("strategy"), N_("merge strategy")), + OPT_CALLBACK('X', "strategy-option", &opts, N_("option"), + N_("option for merge strategy"), option_parse_x), + OPT_END(), + OPT_END(), + OPT_END(), + OPT_END(), + OPT_END(), + OPT_END(), + }; - if (!commit->parents) { - parent = NULL; + if (opts->action == REPLAY_PICK) { + struct option cp_extra[] = { + OPT_BOOLEAN('x', NULL, &opts->record_origin, N_("append commit name")), + OPT_BOOLEAN(0, "ff", &opts->allow_ff, N_("allow fast-forward")), + OPT_BOOLEAN(0, "allow-empty", &opts->allow_empty, N_("preserve initially empty commits")), + OPT_BOOLEAN(0, "allow-empty-message", &opts->allow_empty_message, N_("allow commits with empty messages")), + OPT_BOOLEAN(0, "keep-redundant-commits", &opts->keep_redundant_commits, N_("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) - /* TRANSLATORS: The first %s will be "revert" or - "cherry-pick", the second %s a SHA1 */ - 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 && commit->parents->next) { - strbuf_addstr(&msgbuf, ", reversing\nchanges made to "); - strbuf_addstr(&msgbuf, sha1_to_hex(parent->object.sha1)); + 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"; } - strbuf_addstr(&msgbuf, ".\n"); - } else { - base = parent; - base_label = msg.parent_label; - next = commit; - next_label = msg.label; - 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"); - } - if (!no_commit) - write_cherry_pick_head(); - } - 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, xopts_nr, xopts, 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(action == REVERT - ? _("could not revert %s... %s") - : _("could not apply %s... %s"), - 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 = REVISION_WALK_NO_WALK_UNSORTED; + 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 void read_and_refresh_cache(const char *me) -{ - static struct lock_file index_lock; - int index_fd = hold_locked_index(&index_lock, 0); - if (read_index_preload(&the_index, NULL) < 0) - die(_("git %s: failed to read the index"), me); - refresh_index(&the_index, REFRESH_QUIET|REFRESH_UNMERGED, NULL, NULL, NULL); - if (the_index.cache_changed) { - if (write_index(&the_index, index_fd) || - commit_locked_index(&index_lock)) - die(_("git %s: failed to refresh the index"), me); - } - rollback_lock_file(&index_lock); -} - -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")); - } - - read_and_refresh_cache(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 90c8a5047c..dabfcf6890 100644 --- a/builtin/rm.c +++ b/builtin/rm.c @@ -9,17 +9,67 @@ #include "cache-tree.h" #include "tree-walk.h" #include "parse-options.h" +#include "submodule.h" static const char * const builtin_rm_usage[] = { - "git rm [options] [--] <file>...", + N_("git rm [options] [--] <file>..."), NULL }; static struct { int nr, alloc; - const char **name; + struct { + const char *name; + char is_submodule; + } *entry; } list; +static int get_ours_cache_pos(const char *path, int pos) +{ + int i = -pos - 1; + + while ((i < active_nr) && !strcmp(active_cache[i]->name, path)) { + if (ce_stage(active_cache[i]) == 2) + return i; + i++; + } + return -1; +} + +static int check_submodules_use_gitfiles(void) +{ + int i; + int errs = 0; + + for (i = 0; i < list.nr; i++) { + const char *name = list.entry[i].name; + int pos; + struct cache_entry *ce; + struct stat st; + + pos = cache_name_pos(name, strlen(name)); + if (pos < 0) { + pos = get_ours_cache_pos(name, pos); + if (pos < 0) + continue; + } + ce = active_cache[pos]; + + if (!S_ISGITLINK(ce->ce_mode) || + (lstat(ce->name, &st) < 0) || + is_empty_dir(name)) + continue; + + if (!submodule_uses_gitfile(name)) + errs = error(_("submodule '%s' (or one of its nested " + "submodules) uses a .git directory\n" + "(use 'rm -rf' if you really want to remove " + "it including all of its history)"), name); + } + + return errs; +} + static int check_local_mod(unsigned char *head, int index_only) { /* @@ -37,15 +87,26 @@ static int check_local_mod(unsigned char *head, int index_only) struct stat st; int pos; struct cache_entry *ce; - const char *name = list.name[i]; + const char *name = list.entry[i].name; unsigned char sha1[20]; unsigned mode; int local_changes = 0; int staged_changes = 0; pos = cache_name_pos(name, strlen(name)); - if (pos < 0) - continue; /* removing unmerged entry */ + if (pos < 0) { + /* + * Skip unmerged entries except for populated submodules + * that could lose history when removed. + */ + pos = get_ours_cache_pos(name, pos); + if (pos < 0) + continue; + + if (!S_ISGITLINK(active_cache[pos]->ce_mode) || + is_empty_dir(name)) + continue; + } ce = active_cache[pos]; if (lstat(ce->name, &st) < 0) { @@ -58,9 +119,10 @@ static int check_local_mod(unsigned char *head, int index_only) /* if a file was removed and it is now a * directory, that is the same as ENOENT as * far as git is concerned; we do not track - * directories. + * directories unless they are submodules. */ - continue; + if (!S_ISGITLINK(ce->ce_mode)) + continue; } /* @@ -80,8 +142,11 @@ static int check_local_mod(unsigned char *head, int index_only) /* * Is the index different from the file in the work tree? + * If it's a submodule, is its work tree modified? */ - if (ce_match_stat(ce, &st, 0)) + if (ce_match_stat(ce, &st, 0) || + (S_ISGITLINK(ce->ce_mode) && + !ok_to_remove_submodule(ce->name))) local_changes = 1; /* @@ -115,10 +180,18 @@ static int check_local_mod(unsigned char *head, int index_only) errs = error(_("'%s' has changes staged in the index\n" "(use --cached to keep the file, " "or -f to force removal)"), name); - if (local_changes) - errs = error(_("'%s' has local modifications\n" - "(use --cached to keep the file, " - "or -f to force removal)"), name); + if (local_changes) { + if (S_ISGITLINK(ce->ce_mode) && + !submodule_uses_gitfile(name)) { + errs = error(_("submodule '%s' (or one of its nested " + "submodules) uses a .git directory\n" + "(use 'rm -rf' if you really want to remove " + "it including all of its history)"), name); + } else + errs = error(_("'%s' has local modifications\n" + "(use --cached to keep the file, " + "or -f to force removal)"), name); + } } } return errs; @@ -130,13 +203,13 @@ 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, "dry run"), - OPT__QUIET(&quiet, "do not list removed files"), - OPT_BOOLEAN( 0 , "cached", &index_only, "only remove from the index"), - OPT__FORCE(&force, "override the up-to-date check"), - OPT_BOOLEAN('r', NULL, &recursive, "allow recursive removal"), + OPT__DRY_RUN(&show_only, N_("dry run")), + OPT__QUIET(&quiet, N_("do not list removed files")), + OPT_BOOLEAN( 0 , "cached", &index_only, N_("only remove from the index")), + OPT__FORCE(&force, N_("override the up-to-date check")), + OPT_BOOLEAN('r', NULL, &recursive, N_("allow recursive removal")), OPT_BOOLEAN( 0 , "ignore-unmatch", &ignore_unmatch, - "exit with a zero status even if nothing matched"), + N_("exit with a zero status even if nothing matched")), OPT_END(), }; @@ -161,6 +234,21 @@ int cmd_rm(int argc, const char **argv, const char *prefix) if (read_cache() < 0) die(_("index file corrupt")); + /* + * Drop trailing directory separators from directories so we'll find + * submodules in the index. + */ + for (i = 0; i < argc; i++) { + size_t pathlen = strlen(argv[i]); + if (pathlen && is_dir_sep(argv[i][pathlen - 1]) && + is_directory(argv[i])) { + do { + pathlen--; + } while (pathlen && is_dir_sep(argv[i][pathlen - 1])); + argv[i] = xmemdupz(argv[i], pathlen); + } + } + pathspec = get_pathspec(prefix, argv); refresh_index(&the_index, REFRESH_QUIET, pathspec, NULL, NULL); @@ -173,8 +261,9 @@ 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; - ALLOC_GROW(list.name, list.nr + 1, list.alloc); - list.name[list.nr++] = ce->name; + ALLOC_GROW(list.entry, list.nr + 1, list.alloc); + list.entry[list.nr].name = ce->name; + list.entry[list.nr++].is_submodule = S_ISGITLINK(ce->ce_mode); } if (pathspec) { @@ -215,6 +304,9 @@ int cmd_rm(int argc, const char **argv, const char *prefix) hashclr(sha1); if (check_local_mod(sha1, index_only)) exit(1); + } else if (!index_only) { + if (check_submodules_use_gitfiles()) + exit(1); } /* @@ -222,7 +314,7 @@ int cmd_rm(int argc, const char **argv, const char *prefix) * the index unless all of them succeed. */ for (i = 0; i < list.nr; i++) { - const char *path = list.name[i]; + const char *path = list.entry[i].name; if (!quiet) printf("rm '%s'\n", path); @@ -244,7 +336,25 @@ int cmd_rm(int argc, const char **argv, const char *prefix) if (!index_only) { int removed = 0; for (i = 0; i < list.nr; i++) { - const char *path = list.name[i]; + const char *path = list.entry[i].name; + if (list.entry[i].is_submodule) { + if (is_empty_dir(path)) { + if (!rmdir(path)) { + removed = 1; + continue; + } + } else { + struct strbuf buf = STRBUF_INIT; + strbuf_addstr(&buf, path); + if (!remove_dir_recursively(&buf, 0)) { + removed = 1; + strbuf_release(&buf); + continue; + } + strbuf_release(&buf); + /* Fallthrough and let remove_path() fail. */ + } + } if (!remove_path(path)) { removed = 1; continue; diff --git a/builtin/send-pack.c b/builtin/send-pack.c index c1f6ddd927..d34201372d 100644 --- a/builtin/send-pack.c +++ b/builtin/send-pack.c @@ -8,6 +8,7 @@ #include "send-pack.h" #include "quote.h" #include "transport.h" +#include "version.h" static const char send_pack_usage[] = "git send-pack [--all | --mirror] [--dry-run] [--force] [--receive-pack=<git-receive-pack>] [--verbose] [--thin] [<host>:]<directory> [<ref>...]\n" @@ -15,164 +16,6 @@ static const char send_pack_usage[] = static struct send_pack_args args; -static int feed_object(const unsigned char *sha1, int fd, int negative) -{ - char buf[42]; - - if (negative && !has_sha1_file(sha1)) - return 1; - - memcpy(buf + negative, sha1_to_hex(sha1), 40); - if (negative) - buf[0] = '^'; - buf[40 + negative] = '\n'; - return write_or_whine(fd, buf, 41 + negative, "send-pack: send refs"); -} - -/* - * Make a pack stream and spit it out into file descriptor fd - */ -static int pack_objects(int fd, struct ref *refs, struct extra_have_objects *extra, struct send_pack_args *args) -{ - /* - * The child becomes pack-objects --revs; we feed - * the revision parameters to it via its stdin and - * let its stdout go back to the other end. - */ - const char *argv[] = { - "pack-objects", - "--all-progress-implied", - "--revs", - "--stdout", - NULL, - NULL, - NULL, - NULL, - NULL, - }; - struct child_process po; - int i; - - i = 4; - if (args->use_thin_pack) - argv[i++] = "--thin"; - if (args->use_ofs_delta) - argv[i++] = "--delta-base-offset"; - if (args->quiet) - argv[i++] = "-q"; - if (args->progress) - argv[i++] = "--progress"; - memset(&po, 0, sizeof(po)); - po.argv = argv; - po.in = -1; - po.out = args->stateless_rpc ? -1 : fd; - po.git_cmd = 1; - if (start_command(&po)) - die_errno("git pack-objects failed"); - - /* - * We feed the pack-objects we just spawned with revision - * parameters by writing to the pipe. - */ - for (i = 0; i < extra->nr; i++) - if (!feed_object(extra->array[i], po.in, 1)) - break; - - while (refs) { - if (!is_null_sha1(refs->old_sha1) && - !feed_object(refs->old_sha1, po.in, 1)) - break; - if (!is_null_sha1(refs->new_sha1) && - !feed_object(refs->new_sha1, po.in, 0)) - break; - refs = refs->next; - } - - close(po.in); - - if (args->stateless_rpc) { - char *buf = xmalloc(LARGE_PACKET_MAX); - while (1) { - ssize_t n = xread(po.out, buf, LARGE_PACKET_MAX); - if (n <= 0) - break; - send_sideband(fd, -1, buf, n, LARGE_PACKET_MAX); - } - free(buf); - close(po.out); - po.out = -1; - } - - if (finish_command(&po)) - return -1; - return 0; -} - -static int receive_status(int in, struct ref *refs) -{ - struct ref *hint; - char line[1000]; - int ret = 0; - int len = packet_read_line(in, line, sizeof(line)); - if (len < 10 || memcmp(line, "unpack ", 7)) - return error("did not receive remote status"); - if (memcmp(line, "unpack ok\n", 10)) { - char *p = line + strlen(line) - 1; - if (*p == '\n') - *p = '\0'; - error("unpack failed: %s", line + 7); - ret = -1; - } - hint = NULL; - while (1) { - char *refname; - char *msg; - len = packet_read_line(in, line, sizeof(line)); - if (!len) - break; - if (len < 3 || - (memcmp(line, "ok ", 3) && memcmp(line, "ng ", 3))) { - fprintf(stderr, "protocol error: %s\n", line); - ret = -1; - break; - } - - line[strlen(line)-1] = '\0'; - refname = line + 3; - msg = strchr(refname, ' '); - if (msg) - *msg++ = '\0'; - - /* first try searching at our hint, falling back to all refs */ - if (hint) - hint = find_ref_by_name(hint, refname); - if (!hint) - hint = find_ref_by_name(refs, refname); - if (!hint) { - warning("remote reported status on unknown ref: %s", - refname); - continue; - } - if (hint->status != REF_STATUS_EXPECTING_REPORT) { - warning("remote reported status on unexpected ref: %s", - refname); - continue; - } - - if (line[0] == 'o' && line[1] == 'k') - hint->status = REF_STATUS_OK; - else { - hint->status = REF_STATUS_REMOTE_REJECT; - ret = -1; - } - if (msg) - hint->remote_status = xstrdup(msg); - /* start our next search from the next ref */ - hint = hint->next; - } - return ret; -} - static void print_helper_status(struct ref *ref) { struct strbuf buf = STRBUF_INIT; @@ -226,168 +69,6 @@ static void print_helper_status(struct ref *ref) strbuf_release(&buf); } -static int sideband_demux(int in, int out, void *data) -{ - int *fd = data, ret; -#ifdef NO_PTHREADS - close(fd[1]); -#endif - ret = recv_sideband("send-pack", fd[0], out); - close(out); - return ret; -} - -int send_pack(struct send_pack_args *args, - int fd[], struct child_process *conn, - struct ref *remote_refs, - struct extra_have_objects *extra_have) -{ - int in = fd[0]; - int out = fd[1]; - struct strbuf req_buf = STRBUF_INIT; - struct ref *ref; - int new_refs; - int allow_deleting_refs = 0; - int status_report = 0; - int use_sideband = 0; - unsigned cmds_sent = 0; - int ret; - struct async demux; - - /* Does the other end support the reporting? */ - if (server_supports("report-status")) - status_report = 1; - if (server_supports("delete-refs")) - allow_deleting_refs = 1; - if (server_supports("ofs-delta")) - args->use_ofs_delta = 1; - if (server_supports("side-band-64k")) - use_sideband = 1; - - if (!remote_refs) { - fprintf(stderr, "No refs in common and none specified; doing nothing.\n" - "Perhaps you should specify a branch such as 'master'.\n"); - return 0; - } - - /* - * Finally, tell the other end! - */ - new_refs = 0; - for (ref = remote_refs; ref; ref = ref->next) { - if (!ref->peer_ref && !args->send_mirror) - continue; - - /* Check for statuses set by set_ref_status_for_push() */ - switch (ref->status) { - case REF_STATUS_REJECT_NONFASTFORWARD: - case REF_STATUS_UPTODATE: - continue; - default: - ; /* do nothing */ - } - - if (ref->deletion && !allow_deleting_refs) { - ref->status = REF_STATUS_REJECT_NODELETE; - continue; - } - - if (!ref->deletion) - new_refs++; - - if (args->dry_run) { - ref->status = REF_STATUS_OK; - } 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" : ""); - } - else - packet_buf_write(&req_buf, "%s %s %s", - old_hex, new_hex, ref->name); - ref->status = status_report ? - REF_STATUS_EXPECTING_REPORT : - REF_STATUS_OK; - cmds_sent++; - } - } - - if (args->stateless_rpc) { - if (!args->dry_run && cmds_sent) { - packet_buf_flush(&req_buf); - send_sideband(out, -1, req_buf.buf, req_buf.len, LARGE_PACKET_MAX); - } - } else { - safe_write(out, req_buf.buf, req_buf.len); - packet_flush(out); - } - strbuf_release(&req_buf); - - if (use_sideband && cmds_sent) { - memset(&demux, 0, sizeof(demux)); - demux.proc = sideband_demux; - demux.data = fd; - demux.out = -1; - if (start_async(&demux)) - die("receive-pack: unable to fork off sideband demultiplexer"); - in = demux.out; - } - - if (new_refs && cmds_sent) { - 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; - } - } - if (args->stateless_rpc && cmds_sent) - packet_flush(out); - - if (status_report && cmds_sent) - ret = receive_status(in, remote_refs); - else - ret = 0; - if (args->stateless_rpc) - packet_flush(out); - - if (use_sideband && cmds_sent) { - if (finish_async(&demux)) { - error("error in sideband demultiplexer"); - ret = -1; - } - close(demux.out); - } - - if (ret < 0) - return ret; - - if (args->porcelain) - return 0; - - for (ref = remote_refs; ref; ref = ref->next) { - switch (ref->status) { - case REF_STATUS_NONE: - case REF_STATUS_UPTODATE: - case REF_STATUS_OK: - break; - default: - return -1; - } - } - return 0; -} - int cmd_send_pack(int argc, const char **argv, const char *prefix) { int i, nr_refspecs = 0; @@ -405,6 +86,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++) { @@ -439,10 +121,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; @@ -483,6 +177,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; @@ -494,8 +192,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); @@ -509,7 +206,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 37f3193a33..83605143ac 100644 --- a/builtin/shortlog.c +++ b/builtin/shortlog.c @@ -10,9 +10,9 @@ #include "parse-options.h" static char const * const shortlog_usage[] = { - "git shortlog [-n] [-s] [-e] [-w] [rev-opts] [--] [<commit-id>... ]", + N_("git shortlog [-n] [-s] [-e] [-w] [rev-opts] [--] [<commit-id>... ]"), "", - "[rev-opts] are documented in git-rev-list(1)", + N_("[rev-opts] are documented in git-rev-list(1)"), NULL }; @@ -250,13 +250,13 @@ int cmd_shortlog(int argc, const char **argv, const char *prefix) static const struct option options[] = { OPT_BOOLEAN('n', "numbered", &log.sort_by_number, - "sort output according to the number of commits per author"), + N_("sort output according to the number of commits per author")), OPT_BOOLEAN('s', "summary", &log.summary, - "Suppress commit descriptions, only provides commit count"), + N_("Suppress commit descriptions, only provides commit count")), OPT_BOOLEAN('e', "email", &log.email, - "Show the email address of each author"), - { OPTION_CALLBACK, 'w', NULL, &log, "w[,i1[,i2]]", - "Linewrap output", PARSE_OPT_OPTARG, &parse_wrap_args }, + N_("Show the email address of each author")), + { OPTION_CALLBACK, 'w', NULL, &log, N_("w[,i1[,i2]]"), + N_("Linewrap output"), PARSE_OPT_OPTARG, &parse_wrap_args }, OPT_END(), }; @@ -306,9 +306,8 @@ parse_done: static void add_wrapped_shortlog_msg(struct strbuf *sb, const char *s, const struct shortlog *log) { - int col = strbuf_add_wrapped_text(sb, s, log->in1, log->in2, log->wrap); - if (col != log->wrap) - strbuf_addch(sb, '\n'); + strbuf_add_wrapped_text(sb, s, log->in1, log->in2, log->wrap); + strbuf_addch(sb, '\n'); } void shortlog_output(struct shortlog *log) diff --git a/builtin/show-branch.c b/builtin/show-branch.c index facc63a79e..d208fd6c68 100644 --- a/builtin/show-branch.c +++ b/builtin/show-branch.c @@ -6,8 +6,8 @@ #include "parse-options.h" static const char* show_branch_usage[] = { - "git show-branch [-a|--all] [-r|--remotes] [--topo-order | --date-order] [--current] [--color[=<when>] | --no-color] [--sparse] [--more=<n> | --list | --independent | --merge-base] [--no-name | --sha1-name] [--topics] [(<rev> | <glob>)...]", - "git show-branch (-g|--reflog)[=<n>[,<base>]] [--list] [<ref>]", + N_("git show-branch [-a|--all] [-r|--remotes] [--topo-order | --date-order] [--current] [--color[=<when>] | --no-color] [--sparse] [--more=<n> | --list | --independent | --merge-base] [--no-name | --sha1-name] [--topics] [(<rev> | <glob>)...]"), + N_("git show-branch (-g|--reflog)[=<n>[,<base>]] [--list] [<ref>]"), NULL }; @@ -26,14 +26,14 @@ static const char **default_arg; static const char *get_color_code(int idx) { - if (showbranch_use_color) + 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 ""; } @@ -573,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; } @@ -648,36 +648,36 @@ int cmd_show_branch(int ac, const char **av, const char *prefix) const char *reflog_base = NULL; struct option builtin_show_branch_options[] = { OPT_BOOLEAN('a', "all", &all_heads, - "show remote-tracking and local branches"), + N_("show remote-tracking and local branches")), OPT_BOOLEAN('r', "remotes", &all_remotes, - "show remote-tracking branches"), + N_("show remote-tracking branches")), OPT__COLOR(&showbranch_use_color, - "color '*!+-' corresponding to the branch"), - { OPTION_INTEGER, 0, "more", &extra, "n", - "show <n> more commits after the common ancestor", + N_("color '*!+-' corresponding to the branch")), + { OPTION_INTEGER, 0, "more", &extra, N_("n"), + N_("show <n> more commits after the common ancestor"), PARSE_OPT_OPTARG, NULL, (intptr_t)1 }, - OPT_SET_INT(0, "list", &extra, "synonym to more=-1", -1), - OPT_BOOLEAN(0, "no-name", &no_name, "suppress naming strings"), + OPT_SET_INT(0, "list", &extra, N_("synonym to more=-1"), -1), + OPT_BOOLEAN(0, "no-name", &no_name, N_("suppress naming strings")), OPT_BOOLEAN(0, "current", &with_current_branch, - "include the current branch"), + N_("include the current branch")), OPT_BOOLEAN(0, "sha1-name", &sha1_name, - "name commits with their object names"), + N_("name commits with their object names")), OPT_BOOLEAN(0, "merge-base", &merge_base, - "show possible merge bases"), + N_("show possible merge bases")), OPT_BOOLEAN(0, "independent", &independent, - "show refs unreachable from any other ref"), + N_("show refs unreachable from any other ref")), OPT_BOOLEAN(0, "topo-order", &lifo, - "show commits in topological order"), + N_("show commits in topological order")), OPT_BOOLEAN(0, "topics", &topics, - "show only commits not on the first branch"), + N_("show only commits not on the first branch")), OPT_SET_INT(0, "sparse", &dense, - "show merges reachable from only one tip", 0), + N_("show merges reachable from only one tip"), 0), OPT_SET_INT(0, "date-order", &lifo, - "show commits where no parent comes before its " - "children", 0), - { OPTION_CALLBACK, 'g', "reflog", &reflog_base, "<n>[,<base>]", - "show <n> most recent ref-log entries starting at " - "base", + N_("show commits where no parent comes before its " + "children"), 0), + { OPTION_CALLBACK, 'g', "reflog", &reflog_base, N_("<n>[,<base>]"), + N_("show <n> most recent ref-log entries starting at " + "base"), PARSE_OPT_OPTARG | PARSE_OPT_LITERAL_ARGHELP, parse_reflog_param }, OPT_END() @@ -685,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; @@ -729,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; @@ -794,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); diff --git a/builtin/show-ref.c b/builtin/show-ref.c index 45f0340c3e..8d9b76a02f 100644 --- a/builtin/show-ref.c +++ b/builtin/show-ref.c @@ -7,8 +7,8 @@ #include "parse-options.h" static const char * const show_ref_usage[] = { - "git show-ref [-q|--quiet] [--verify] [--head] [-d|--dereference] [-s|--hash[=<n>]] [--abbrev[=<n>]] [--tags] [--heads] [--] [pattern*] ", - "git show-ref --exclude-existing[=pattern] < ref-list", + N_("git show-ref [-q|--quiet] [--verify] [--head] [-d|--dereference] [-s|--hash[=<n>]] [--abbrev[=<n>]] [--tags] [--heads] [--] [pattern*] "), + N_("git show-ref --exclude-existing[=pattern] < ref-list"), NULL }; @@ -28,7 +28,6 @@ static void show_one(const char *refname, const unsigned char *sha1) static int show_ref(const char *refname, const unsigned char *sha1, int flag, void *cbdata) { - struct object *obj; const char *hex; unsigned char peeled[20]; @@ -79,25 +78,9 @@ match: if (!deref_tags) return 0; - if ((flag & REF_ISPACKED) && !peel_ref(refname, peeled)) { - if (!is_null_sha1(peeled)) { - hex = find_unique_abbrev(peeled, abbrev); - printf("%s %s^{}\n", hex, refname); - } - } - else { - obj = parse_object(sha1); - if (!obj) - die("git show-ref: bad ref %s (%s)", refname, - sha1_to_hex(sha1)); - if (obj->type == OBJ_TAG) { - obj = deref_tag(obj, refname, 0); - if (!obj) - die("git show-ref: bad tag at ref %s (%s)", refname, - sha1_to_hex(sha1)); - hex = find_unique_abbrev(obj->sha1, abbrev); - printf("%s %s^{}\n", hex, refname); - } + if (!peel_ref(refname, peeled)) { + hex = find_unique_abbrev(peeled, abbrev); + printf("%s %s^{}\n", hex, refname); } return 0; } @@ -145,7 +128,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; } @@ -179,26 +162,26 @@ static int help_callback(const struct option *opt, const char *arg, int unset) } static const struct option show_ref_options[] = { - OPT_BOOLEAN(0, "tags", &tags_only, "only show tags (can be combined with heads)"), - OPT_BOOLEAN(0, "heads", &heads_only, "only show heads (can be combined with tags)"), - OPT_BOOLEAN(0, "verify", &verify, "stricter reference checking, " - "requires exact ref path"), + OPT_BOOLEAN(0, "tags", &tags_only, N_("only show tags (can be combined with heads)")), + OPT_BOOLEAN(0, "heads", &heads_only, N_("only show heads (can be combined with tags)")), + OPT_BOOLEAN(0, "verify", &verify, N_("stricter reference checking, " + "requires exact ref path")), { OPTION_BOOLEAN, 'h', NULL, &show_head, NULL, - "show the HEAD reference", + N_("show the HEAD reference"), PARSE_OPT_NOARG | PARSE_OPT_HIDDEN }, - OPT_BOOLEAN(0, "head", &show_head, "show the HEAD reference"), + OPT_BOOLEAN(0, "head", &show_head, N_("show the HEAD reference")), OPT_BOOLEAN('d', "dereference", &deref_tags, - "dereference tags into object IDs"), - { OPTION_CALLBACK, 's', "hash", &abbrev, "n", - "only show SHA1 hash using <n> digits", + N_("dereference tags into object IDs")), + { OPTION_CALLBACK, 's', "hash", &abbrev, N_("n"), + N_("only show SHA1 hash using <n> digits"), PARSE_OPT_OPTARG, &hash_callback }, OPT__ABBREV(&abbrev), OPT__QUIET(&quiet, - "do not print results to stdout (useful with --verify)"), + N_("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", + N_("pattern"), N_("show refs from stdin that aren't in local repository"), PARSE_OPT_OPTARG | PARSE_OPT_NONEG, exclude_existing_callback }, - { OPTION_CALLBACK, 0, "help-all", NULL, NULL, "show usage", + { OPTION_CALLBACK, 0, "help-all", NULL, NULL, N_("show usage"), PARSE_OPT_HIDDEN | PARSE_OPT_NOARG, help_callback }, OPT_END() }; @@ -225,7 +208,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 1288ffcc52..f16986c0ae 100644 --- a/builtin/stripspace.c +++ b/builtin/stripspace.c @@ -75,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 dea849c3c5..f481959421 100644 --- a/builtin/symbolic-ref.c +++ b/builtin/symbolic-ref.c @@ -4,35 +4,43 @@ #include "parse-options.h" static const char * const git_symbolic_ref_usage[] = { - "git symbolic-ref [options] name [ref]", + N_("git symbolic-ref [options] name [ref]"), + N_("git symbolic-ref -d [-q] name"), NULL }; -static void check_symref(const char *HEAD, int quiet) +static int check_symref(const char *HEAD, int quiet, int shorten, int print) { 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) die("ref %s is not a symbolic ref", HEAD); else - exit(1); + return 1; } - puts(refs_heads_master); + if (print) { + if (shorten) + refname = shorten_unambiguous_ref(refname, 0); + puts(refname); + } + return 0; } int cmd_symbolic_ref(int argc, const char **argv, const char *prefix) { - int quiet = 0; + int quiet = 0, delete = 0, shorten = 0, ret = 0; const char *msg = NULL; struct option options[] = { OPT__QUIET(&quiet, - "suppress error message for non-symbolic (detached) refs"), - OPT_STRING('m', NULL, &msg, "reason", "reason of the update"), + N_("suppress error message for non-symbolic (detached) refs")), + OPT_BOOL('d', "delete", &delete, N_("delete symbolic ref")), + OPT_BOOL(0, "short", &shorten, N_("shorten ref output")), + OPT_STRING('m', NULL, &msg, N_("reason"), N_("reason of the update")), OPT_END(), }; @@ -41,9 +49,19 @@ int cmd_symbolic_ref(int argc, const char **argv, const char *prefix) git_symbolic_ref_usage, 0); if (msg &&!*msg) die("Refusing to perform update with empty message"); + + if (delete) { + if (argc != 1) + usage_with_options(git_symbolic_ref_usage, options); + ret = check_symref(argv[0], 1, 0, 0); + if (ret) + die("Cannot delete %s, not a symbolic ref", argv[0]); + return delete_ref(argv[0], NULL, REF_NODEREF); + } + switch (argc) { case 1: - check_symref(argv[0], quiet); + ret = check_symref(argv[0], quiet, shorten, 1); break; case 2: if (!strcmp(argv[0], "HEAD") && @@ -54,5 +72,5 @@ int cmd_symbolic_ref(int argc, const char **argv, const char *prefix) default: usage_with_options(git_symbolic_ref_usage, options); } - return 0; + return ret; } diff --git a/builtin/tag.c b/builtin/tag.c index 667515e527..9c3e0673d5 100644 --- a/builtin/tag.c +++ b/builtin/tag.c @@ -14,23 +14,28 @@ #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 -v <tagname>...", + N_("git tag [-a|-s|-u <key-id>] [-f] [-m <msg>|-F <file>] <tagname> [<head>]"), + N_("git tag -d <tagname>..."), + N_("git tag -l [-n[<num>]] [--contains <commit>] [--points-at <object>] " + "\n\t\t[<pattern>...]"), + N_("git tag -v <tagname>..."), NULL }; -static char signingkey[1000]; - struct tag_filter { const char **patterns; int lines; struct commit_list *with_commit; }; +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 */ @@ -42,6 +47,24 @@ static int match_pattern(const char **patterns, const char *ref) 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) @@ -84,18 +107,51 @@ 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 (match_pattern(filter->patterns, refname)) { - int i; - unsigned long size; - enum object_type type; - char *buf, *sp, *eol; - size_t len; - if (filter->with_commit) { struct commit *commit; @@ -106,38 +162,16 @@ static int show_reference(const char *refname, const unsigned char *sha1, 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 */ - size = parse_signature(buf, size); - for (i = 0, sp += 2; - i < filter->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; - } + show_tag_lines(sha1, filter->lines); putchar('\n'); - free(buf); } return 0; @@ -174,7 +208,7 @@ static int for_each_tag_name(const char **argv, each_tag_name_fn fn) had_error = 1; continue; } - if (!resolve_ref(ref, sha1, 1, NULL)) { + if (read_ref(ref, sha1)) { error(_("tag '%s' not found."), *p); had_error = 1; continue; @@ -208,83 +242,31 @@ static int verify_tag(const char *name, const char *ref, 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" "# Write a tag message\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); } @@ -319,8 +301,18 @@ static int build_tag_object(struct strbuf *buf, int sign, unsigned char *result) 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; @@ -340,12 +332,12 @@ 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.")); - if (!message) { + if (!opt->message_given) { int fd; /* write the template message before editing: */ @@ -356,8 +348,12 @@ static void create_tag(const unsigned char *object, const char *tag, 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)) { @@ -367,14 +363,15 @@ static void create_tag(const unsigned char *object, const char *tag, } } - stripspace(buf, 1); + if (opt->cleanup_mode != CLEANUP_NONE) + stripspace(buf, opt->cleanup_mode == CLEANUP_ALL); - if (!message && !buf->len) + 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"), path); @@ -407,12 +404,29 @@ static int parse_msg_arg(const struct option *opt, const char *arg, int unset) static int strbuf_check_tag_ref(struct strbuf *sb, const char *name) { if (name[0] == '-') - return CHECK_REF_FORMAT_ERROR; + return -1; strbuf_reset(sb); strbuf_addf(sb, "refs/tags/%s", name); - return check_ref_format(sb->buf); + 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) @@ -422,50 +436,60 @@ int cmd_tag(int argc, const char **argv, const char *prefix) unsigned char object[20], prev[20]; 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"), - { OPTION_INTEGER, 'n', NULL, &lines, "n", - "print <n> lines of each tag message", + OPT_BOOLEAN('l', "list", &list, N_("list tag names")), + { OPTION_INTEGER, 'n', NULL, &lines, N_("n"), + 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_GROUP("Tag creation options"), - OPT_BOOLEAN('a', NULL, &annotate, - "annotated tag, needs a message"), - OPT_CALLBACK('m', NULL, &msg, "message", - "tag message", parse_msg_arg), - OPT_FILENAME('F', NULL, &msgfile, "read message from file"), - OPT_BOOLEAN('s', NULL, &sign, "annotated and GPG-signed tag"), - OPT_STRING('u', NULL, &keyid, "key-id", - "use another key to sign the tag"), - OPT__FORCE(&force, "replace the tag if exists"), - - OPT_GROUP("Tag listing options"), + OPT_BOOLEAN('d', "delete", &delete, N_("delete tags")), + OPT_BOOLEAN('v', "verify", &verify, N_("verify tags")), + + OPT_GROUP(N_("Tag creation options")), + OPT_BOOLEAN('a', "annotate", &annotate, + N_("annotated tag, needs a message")), + OPT_CALLBACK('m', "message", &msg, N_("message"), + N_("tag message"), parse_msg_arg), + OPT_FILENAME('F', "file", &msgfile, N_("read message from file")), + OPT_BOOLEAN('s', "sign", &opt.sign, N_("annotated and GPG-signed tag")), + OPT_STRING(0, "cleanup", &cleanup_arg, N_("mode"), + N_("how to strip spaces and #comments from message")), + OPT_STRING('u', "local-user", &keyid, N_("key id"), + N_("use another key to sign the tag")), + OPT__FORCE(&force, N_("replace the tag if exists")), + OPT_COLUMN(0, "column", &colopts, N_("show tag list in columns")), + + OPT_GROUP(N_("Tag listing options")), { - OPTION_CALLBACK, 0, "contains", &with_commit, "commit", - "print only tags that contain the commit", + OPTION_CALLBACK, 0, "contains", &with_commit, N_("commit"), + N_("print only tags that contain the commit"), PARSE_OPT_LASTARG_DEFAULT, parse_opt_with_commit, (intptr_t)"HEAD", }, + { + OPTION_CALLBACK, 0, "points-at", NULL, N_("object"), + N_("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; @@ -476,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, 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.")); if (with_commit) 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) @@ -518,14 +560,24 @@ int cmd_tag(int argc, const char **argv, const char *prefix) if (strbuf_check_tag_ref(&ref, tag)) die(_("'%s' is not a valid tag name."), tag); - if (!resolve_ref(ref.buf, prev, 1, NULL)) + if (read_ref(ref.buf, prev)) hashclr(prev); else if (!force) 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.buf, prev, 0); if (!lock) diff --git a/builtin/unpack-objects.c b/builtin/unpack-objects.c index 14e04e6795..2217d7b3ae 100644 --- a/builtin/unpack-objects.c +++ b/builtin/unpack-objects.c @@ -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 a6a23fa1f3..5c7762eef4 100644 --- a/builtin/update-index.c +++ b/builtin/update-index.c @@ -95,7 +95,8 @@ static int add_one_path(struct cache_entry *old, const char *path, int len, stru size = cache_entry_size(len); ce = xcalloc(1, size); memcpy(ce->name, path, len); - ce->ce_flags = len; + ce->ce_flags = create_ce_flags(0); + ce->ce_namelen = len; fill_stat_cache_info(ce, st); ce->ce_mode = ce_mode_from_stat(old, st->st_mode); @@ -211,12 +212,6 @@ static int process_path(const char *path) if (S_ISDIR(st.st_mode)) return process_directory(path, len, &st); - /* - * Process a regular file - */ - if (ce && S_ISGITLINK(ce->ce_mode)) - return error("%s is already a gitlink, not replacing", path); - return add_one_path(ce, path, len, &st); } @@ -235,7 +230,8 @@ static int add_cacheinfo(unsigned int mode, const unsigned char *sha1, hashcpy(ce->sha1, sha1); memcpy(ce->name, path, len); - ce->ce_flags = create_ce_flags(len, stage); + ce->ce_flags = create_ce_flags(stage); + ce->ce_namelen = len; ce->ce_mode = create_ce_mode(mode); if (assume_unchanged) ce->ce_flags |= CE_VALID; @@ -402,7 +398,7 @@ static void read_index_info(int line_termination) } static const char * const update_index_usage[] = { - "git update-index [options] [--] [<file>...]", + N_("git update-index [options] [--] [<file>...]"), NULL }; @@ -433,7 +429,8 @@ static struct cache_entry *read_one_ent(const char *which, hashcpy(ce->sha1, sha1); memcpy(ce->name, path, namelen); - ce->ce_flags = create_ce_flags(namelen, stage); + ce->ce_flags = create_ce_flags(stage); + ce->ce_namelen = namelen; ce->ce_mode = create_ce_mode(mode); return ce; } @@ -596,6 +593,7 @@ struct refresh_params { static int refresh(struct refresh_params *o, unsigned int flag) { setup_work_tree(); + read_cache_preload(NULL); *o->has_errors |= refresh_cache(o->flags | flag); return 0; } @@ -708,6 +706,7 @@ int cmd_update_index(int argc, const char **argv, const char *prefix) 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; struct refresh_params refresh_args = {0, &has_errors}; int lock_error = 0; @@ -716,86 +715,88 @@ int cmd_update_index(int argc, const char **argv, const char *prefix) int parseopt_state = PARSE_OPT_UNKNOWN; struct option options[] = { OPT_BIT('q', NULL, &refresh_args.flags, - "continue refresh even when index needs update", + N_("continue refresh even when index needs update"), REFRESH_QUIET), OPT_BIT(0, "ignore-submodules", &refresh_args.flags, - "refresh: ignore submodules", + N_("refresh: ignore submodules"), REFRESH_IGNORE_SUBMODULES), OPT_SET_INT(0, "add", &allow_add, - "do not ignore new files", 1), + N_("do not ignore new files"), 1), OPT_SET_INT(0, "replace", &allow_replace, - "let files replace directories and vice-versa", 1), + N_("let files replace directories and vice-versa"), 1), OPT_SET_INT(0, "remove", &allow_remove, - "notice files missing from worktree", 1), + N_("notice files missing from worktree"), 1), OPT_BIT(0, "unmerged", &refresh_args.flags, - "refresh even if index contains unmerged entries", + N_("refresh even if index contains unmerged entries"), REFRESH_UNMERGED), {OPTION_CALLBACK, 0, "refresh", &refresh_args, NULL, - "refresh stat information", + N_("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", + N_("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", + N_("<mode> <object> <path>"), + N_("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", + {OPTION_CALLBACK, 0, "chmod", &set_executable_bit, N_("(+/-)x"), + N_("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\"", + N_("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", + N_("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\"", + N_("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", + N_("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), + N_("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), + N_("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'), + N_("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", + N_("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", + N_("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", + N_("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", + N_("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", + N_("ignore files missing from worktree"), REFRESH_IGNORE_MISSING), OPT_SET_INT(0, "verbose", &verbose, - "report actions to standard output", 1), + N_("report actions to standard output"), 1), {OPTION_CALLBACK, 0, "clear-resolve-undo", NULL, NULL, - "(for porcelains) forget saved unresolved conflicts", + N_("(for porcelains) forget saved unresolved conflicts"), PARSE_OPT_NOARG | PARSE_OPT_NONEG, resolve_undo_clear_callback}, + OPT_INTEGER(0, "index-version", &preferred_index_format, + N_("write index in this format")), OPT_END() }; if (argc == 2 && !strcmp(argv[1], "-h")) - usage(update_index_usage[0]); + usage_with_options(update_index_usage, options); git_config(git_default_config, NULL); @@ -851,6 +852,17 @@ int cmd_update_index(int argc, const char **argv, const char *prefix) } } 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; diff --git a/builtin/update-ref.c b/builtin/update-ref.c index 835c62ab15..51d2684859 100644 --- a/builtin/update-ref.c +++ b/builtin/update-ref.c @@ -4,8 +4,8 @@ #include "parse-options.h" static const char * const git_update_ref_usage[] = { - "git update-ref [options] -d <refname> [<oldval>]", - "git update-ref [options] <refname> <newval> [<oldval>]", + N_("git update-ref [options] -d <refname> [<oldval>]"), + N_("git update-ref [options] <refname> <newval> [<oldval>]"), NULL }; @@ -15,10 +15,10 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix) unsigned char sha1[20], oldsha1[20]; int delete = 0, no_deref = 0, flags = 0; struct option options[] = { - OPT_STRING( 'm', NULL, &msg, "reason", "reason of the update"), - OPT_BOOLEAN('d', NULL, &delete, "deletes the reference"), + OPT_STRING( 'm', NULL, &msg, N_("reason"), N_("reason of the update")), + OPT_BOOLEAN('d', NULL, &delete, N_("delete the reference")), OPT_BOOLEAN( 0 , "no-deref", &no_deref, - "update <refname> not the one it points to"), + N_("update <refname> not the one it points to")), OPT_END(), }; diff --git a/builtin/update-server-info.c b/builtin/update-server-info.c index b90dce6358..6c8cc3edc1 100644 --- a/builtin/update-server-info.c +++ b/builtin/update-server-info.c @@ -3,7 +3,7 @@ #include "parse-options.h" static const char * const update_server_info_usage[] = { - "git update-server-info [--force]", + N_("git update-server-info [--force]"), NULL }; @@ -11,10 +11,11 @@ int cmd_update_server_info(int argc, const char **argv, const char *prefix) { int force = 0; struct option options[] = { - OPT__FORCE(&force, "update the info files from scratch"), + OPT__FORCE(&force, N_("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 99d068a532..aedbb53a2d 100644 --- a/builtin/var.c +++ b/builtin/var.c @@ -11,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; @@ -55,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..66cd2df0f8 100644 --- a/builtin/verify-pack.c +++ b/builtin/verify-pack.c @@ -1,140 +1,59 @@ #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; } static const char * const verify_pack_usage[] = { - "git verify-pack [-v|--verbose] [-s|--stat-only] <pack>...", + N_("git verify-pack [-v|--verbose] [-s|--stat-only] <pack>..."), NULL }; @@ -144,9 +63,9 @@ int cmd_verify_pack(int argc, const char **argv, const char *prefix) unsigned int flags = 0; int i; const struct option verify_pack_options[] = { - OPT_BIT('v', "verbose", &flags, "verbose", + OPT_BIT('v', "verbose", &flags, N_("verbose"), VERIFY_PACK_VERBOSE), - OPT_BIT('s', "stat-only", &flags, "show statistics only", + OPT_BIT('s', "stat-only", &flags, N_("show statistics only"), VERIFY_PACK_STAT_ONLY), OPT_END() }; @@ -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 3134766049..a8eee886a5 100644 --- a/builtin/verify-tag.c +++ b/builtin/verify-tag.c @@ -11,50 +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>...", + N_("git verify-tag [-v|--verbose] <tag>..."), NULL }; 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]; - 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 */ + 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); + if (size == len) + return error("no signature found"); - unlink_or_warn(path); - - return ret; + return verify_signed_buffer(buf, len, buf + len, size - len, NULL); } static int verify_tag(const char *name, int verbose) @@ -83,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, "print tag contents"), + OPT__VERBOSE(&verbose, N_("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); diff --git a/builtin/write-tree.c b/builtin/write-tree.c index b223af416f..084c0df783 100644 --- a/builtin/write-tree.c +++ b/builtin/write-tree.c @@ -10,7 +10,7 @@ #include "parse-options.h" static const char * const write_tree_usage[] = { - "git write-tree [--missing-ok] [--prefix=<prefix>/]", + N_("git write-tree [--missing-ok] [--prefix=<prefix>/]"), NULL }; @@ -21,13 +21,13 @@ int cmd_write_tree(int argc, const char **argv, const char *unused_prefix) unsigned char sha1[20]; const char *me = "git-write-tree"; struct option write_tree_options[] = { - OPT_BIT(0, "missing-ok", &flags, "allow missing objects", + OPT_BIT(0, "missing-ok", &flags, N_("allow missing objects"), WRITE_TREE_MISSING_OK), - { OPTION_STRING, 0, "prefix", &prefix, "<prefix>/", - "write tree object for a subdirectory <prefix>" , + { OPTION_STRING, 0, "prefix", &prefix, N_("<prefix>/"), + N_("write tree object for a subdirectory <prefix>") , PARSE_OPT_LITERAL_ARGHELP }, { OPTION_BIT, 0, "ignore-cache-tree", &flags, NULL, - "only useful for debugging", + N_("only useful for debugging"), PARSE_OPT_HIDDEN | PARSE_OPT_NOARG, NULL, WRITE_TREE_IGNORE_CACHE_TREE }, OPT_END() |