diff options
Diffstat (limited to 'builtin')
80 files changed, 5023 insertions, 4600 deletions
diff --git a/builtin/add.c b/builtin/add.c index 3716617e22..8266a9cb70 100644 --- a/builtin/add.c +++ b/builtin/add.c @@ -17,7 +17,7 @@ #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; @@ -26,8 +26,55 @@ static int take_worktree_changes; struct update_callback_data { int flags; int add_errors; + const char *implicit_dot; + size_t implicit_dot_len; + + /* only needed for 2.0 transition preparation */ + int warn_add_would_remove; }; +static const char *option_with_implicit_dot; +static const char *short_option_with_implicit_dot; + +static void warn_pathless_add(void) +{ + static int shown; + assert(option_with_implicit_dot && short_option_with_implicit_dot); + + if (shown) + return; + shown = 1; + + /* + * To be consistent with "git add -p" and most Git + * commands, we should default to being tree-wide, but + * this is not the original behavior and can't be + * changed until users trained themselves not to type + * "git add -u" or "git add -A". For now, we warn and + * keep the old behavior. Later, the behavior can be changed + * to tree-wide, keeping the warning for a while, and + * eventually we can drop the warning. + */ + warning(_("The behavior of 'git add %s (or %s)' with no path argument from a\n" + "subdirectory of the tree will change in Git 2.0 and should not be used anymore.\n" + "To add content for the whole tree, run:\n" + "\n" + " git add %s :/\n" + " (or git add %s :/)\n" + "\n" + "To restrict the command to the current directory, run:\n" + "\n" + " git add %s .\n" + " (or git add %s .)\n" + "\n" + "With the current Git version, the command is restricted to " + "the current directory.\n" + ""), + option_with_implicit_dot, short_option_with_implicit_dot, + option_with_implicit_dot, short_option_with_implicit_dot, + option_with_implicit_dot, short_option_with_implicit_dot); +} + static int fix_unmerged_status(struct diff_filepair *p, struct update_callback_data *data) { @@ -49,15 +96,49 @@ static int fix_unmerged_status(struct diff_filepair *p, return DIFF_STATUS_MODIFIED; } +static const char *add_would_remove_warning = N_( + "You ran 'git add' with neither '-A (--all)' or '--ignore-removal',\n" +"whose behaviour will change in Git 2.0 with respect to paths you removed.\n" +"Paths like '%s' that are\n" +"removed from your working tree are ignored with this version of Git.\n" +"\n" +"* 'git add --ignore-removal <pathspec>', which is the current default,\n" +" ignores paths you removed from your working tree.\n" +"\n" +"* 'git add --all <pathspec>' will let you also record the removals.\n" +"\n" +"Run 'git status' to check the paths you removed from your working tree.\n"); + +static void warn_add_would_remove(const char *path) +{ + warning(_(add_would_remove_warning), path); +} + static void update_callback(struct diff_queue_struct *q, struct diff_options *opt, void *cbdata) { int i; struct update_callback_data *data = cbdata; + const char *implicit_dot = data->implicit_dot; + size_t implicit_dot_len = data->implicit_dot_len; for (i = 0; i < q->nr; i++) { struct diff_filepair *p = q->queue[i]; const char *path = p->one->path; + /* + * Check if "git add -A" or "git add -u" was run from a + * subdirectory with a modified file outside that directory, + * and warn if so. + * + * "git add -u" will behave like "git add -u :/" instead of + * "git add -u ." in the future. This warning prepares for + * that change. + */ + if (implicit_dot && + strncmp_icase(path, implicit_dot, implicit_dot_len)) { + warn_pathless_add(); + continue; + } switch (fix_unmerged_status(p, data)) { default: die(_("unexpected diff status %c"), p->status); @@ -70,6 +151,10 @@ static void update_callback(struct diff_queue_struct *q, } break; case DIFF_STATUS_DELETED: + if (data->warn_add_would_remove) { + warn_add_would_remove(path); + data->warn_add_would_remove = 0; + } if (data->flags & ADD_CACHE_IGNORE_REMOVAL) break; if (!(data->flags & ADD_CACHE_PRETEND)) @@ -81,24 +166,34 @@ static void update_callback(struct diff_queue_struct *q, } } -int add_files_to_cache(const char *prefix, const char **pathspec, int flags) +static void update_files_in_cache(const char *prefix, const char **pathspec, + struct update_callback_data *data) { - struct update_callback_data data; struct rev_info rev; + init_revisions(&rev, prefix); setup_revisions(0, NULL, &rev, NULL); init_pathspec(&rev.prune_data, pathspec); rev.diffopt.output_format = DIFF_FORMAT_CALLBACK; rev.diffopt.format_callback = update_callback; - data.flags = flags; - data.add_errors = 0; - rev.diffopt.format_callback_data = &data; + rev.diffopt.format_callback_data = data; rev.max_count = 0; /* do not compare unmerged paths with stage #2 */ run_diff_files(&rev, DIFF_RACY_IS_MODIFIED); +} + +int add_files_to_cache(const char *prefix, const char **pathspec, int flags) +{ + struct update_callback_data data; + + memset(&data, 0, sizeof(data)); + data.flags = flags; + update_files_in_cache(prefix, pathspec, &data); return !!data.add_errors; } -static char *prune_directory(struct dir_struct *dir, const char **pathspec, int prefix) +#define WARN_IMPLICIT_DOT (1u << 0) +static char *prune_directory(struct dir_struct *dir, const char **pathspec, + int prefix, unsigned flag) { char *seen; int i, specs; @@ -115,6 +210,16 @@ static char *prune_directory(struct dir_struct *dir, const char **pathspec, int if (match_pathspec(pathspec, entry->name, entry->len, prefix, seen)) *dst++ = entry; + else if (flag & WARN_IMPLICIT_DOT) + /* + * "git add -A" was run from a subdirectory with a + * new file outside that directory. + * + * "git add -A" will behave like "git add -A :/" + * instead of "git add -A ." in the future. + * Warn about the coming behavior change. + */ + warn_pathless_add(); } dir->nr = dst - dir->entries; add_pathspec_matches_against_index(pathspec, seen, specs); @@ -218,7 +323,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; @@ -238,6 +343,7 @@ 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; + rev.diffopt.use_color = 0; DIFF_OPT_SET(&rev.diffopt, IGNORE_DIRTY_SUBMODULES); out = open(file, O_CREAT | O_WRONLY, 0666); if (out < 0) @@ -261,6 +367,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; } @@ -269,23 +376,38 @@ static struct lock_file lock_file; static const char ignore_error[] = N_("The following paths are ignored by one of your .gitignore files:\n"); -static int verbose = 0, show_only = 0, ignored_too = 0, refresh_only = 0; -static int ignore_add_errors, addremove, intent_to_add, ignore_missing = 0; +static int verbose, show_only, ignored_too, refresh_only; +static int ignore_add_errors, intent_to_add, ignore_missing; + +#define ADDREMOVE_DEFAULT 0 /* Change to 1 in Git 2.0 */ +static int addremove = ADDREMOVE_DEFAULT; +static int addremove_explicit = -1; /* unspecified */ + +static int ignore_removal_cb(const struct option *opt, const char *arg, int unset) +{ + /* if we are told to ignore, we are not adding removals */ + *(int *)opt->value = !unset ? 0 : 1; + return 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_BOOL('i', "interactive", &add_interactive, N_("interactive picking")), + OPT_BOOL('p', "patch", &patch_interactive, N_("select hunks interactively")), + OPT_BOOL('e', "edit", &edit_interactive, N_("edit current diff and apply")), + OPT__FORCE(&ignored_too, N_("allow adding otherwise ignored files")), + OPT_BOOL('u', "update", &take_worktree_changes, N_("update tracked files")), + OPT_BOOL('N', "intent-to-add", &intent_to_add, N_("record only the fact that the path will be added later")), + OPT_BOOL('A', "all", &addremove_explicit, N_("add changes from all tracked and untracked files")), + { OPTION_CALLBACK, 0, "ignore-removal", &addremove_explicit, + NULL /* takes no arguments */, + N_("ignore paths removed in the working tree (same as --no-all)"), + PARSE_OPT_NOARG, ignore_removal_cb }, + OPT_BOOL( 0 , "refresh", &refresh_only, N_("don't add, only refresh the index")), + OPT_BOOL( 0 , "ignore-errors", &ignore_add_errors, N_("just skip files which cannot be added because of errors")), + OPT_BOOL( 0 , "ignore-missing", &ignore_missing, N_("check if - even missing - files are ignored in dry run")), OPT_END(), }; @@ -330,6 +452,8 @@ int cmd_add(int argc, const char **argv, const char *prefix) int add_new_files; int require_pathspec; char *seen = NULL; + int implicit_dot = 0; + struct update_callback_data update_data; git_config(add_config, NULL); @@ -345,14 +469,44 @@ int cmd_add(int argc, const char **argv, const char *prefix) argc--; argv++; + if (0 <= addremove_explicit) + addremove = addremove_explicit; + else if (take_worktree_changes && ADDREMOVE_DEFAULT) + addremove = 0; /* "-u" was given but not "-A" */ + if (addremove && take_worktree_changes) die(_("-A and -u are mutually incompatible")); + + /* + * Warn when "git add pathspec..." was given without "-u" or "-A" + * and pathspec... covers a removed path. + */ + memset(&update_data, 0, sizeof(update_data)); + if (!take_worktree_changes && addremove_explicit < 0) + update_data.warn_add_would_remove = 1; + + if (!take_worktree_changes && addremove_explicit < 0 && argc) + /* + * Turn "git add pathspec..." to "git add -A pathspec..." + * in Git 2.0 but not yet + */ + ; /* addremove = 1; */ + if (!show_only && ignore_missing) die(_("Option --ignore-missing can only be used together with --dry-run")); - if ((addremove || take_worktree_changes) && !argc) { + if (addremove) { + option_with_implicit_dot = "--all"; + short_option_with_implicit_dot = "-A"; + } + if (take_worktree_changes) { + option_with_implicit_dot = "--update"; + short_option_with_implicit_dot = "-u"; + } + if (option_with_implicit_dot && !argc) { static const char *here[2] = { ".", NULL }; argc = 1; argv = here; + implicit_dot = 1; } add_new_files = !take_worktree_changes && !refresh_only; @@ -365,7 +519,8 @@ int cmd_add(int argc, const char **argv, const char *prefix) (intent_to_add ? ADD_CACHE_INTENT : 0) | (ignore_add_errors ? ADD_CACHE_IGNORE_ERRORS : 0) | (!(addremove || take_worktree_changes) - ? ADD_CACHE_IGNORE_REMOVAL : 0)); + ? ADD_CACHE_IGNORE_REMOVAL : 0)) | + (implicit_dot ? ADD_CACHE_IMPLICIT_DOT : 0); if (require_pathspec && argc == 0) { fprintf(stderr, _("Nothing specified, nothing added.\n")); @@ -389,21 +544,22 @@ int cmd_add(int argc, const char **argv, const char *prefix) } /* This picks up the paths that are not tracked */ - baselen = fill_directory(&dir, pathspec); + baselen = fill_directory(&dir, implicit_dot ? NULL : pathspec); if (pathspec) - seen = prune_directory(&dir, pathspec, baselen); + seen = prune_directory(&dir, pathspec, baselen, + implicit_dot ? WARN_IMPLICIT_DOT : 0); } if (refresh_only) { refresh(verbose, pathspec); goto finish; } + if (implicit_dot && prefix) + refresh_cache(REFRESH_QUIET); if (pathspec) { int i; - struct path_exclude_check check; - path_exclude_check_init(&check, &dir); if (!seen) seen = find_pathspecs_matching_against_index(pathspec); for (i = 0; pathspec[i]; i++) { @@ -411,7 +567,7 @@ int cmd_add(int argc, const char **argv, const char *prefix) && !file_exists(pathspec[i])) { if (ignore_missing) { int dtype = DT_UNKNOWN; - if (is_path_excluded(&check, pathspec[i], -1, &dtype)) + if (is_excluded(&dir, pathspec[i], &dtype)) dir_add_ignored(&dir, pathspec[i], strlen(pathspec[i])); } else die(_("pathspec '%s' did not match any files"), @@ -419,13 +575,24 @@ 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 ((flags & ADD_CACHE_IMPLICIT_DOT) && prefix) { + /* + * Check for modified files throughout the worktree so + * update_callback has a chance to warn about changes + * outside the cwd. + */ + update_data.implicit_dot = prefix; + update_data.implicit_dot_len = strlen(prefix); + pathspec = NULL; + } + update_data.flags = flags & ~ADD_CACHE_IMPLICIT_DOT; + update_files_in_cache(prefix, pathspec, &update_data); + exit_status |= !!update_data.add_errors; if (add_new_files) exit_status |= add_files(&dir, flags); diff --git a/builtin/apply.c b/builtin/apply.c index d2180b0374..64310cd678 100644 --- a/builtin/apply.c +++ b/builtin/apply.c @@ -16,6 +16,9 @@ #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 @@ -46,6 +49,7 @@ 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; @@ -192,12 +196,17 @@ 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) @@ -370,8 +379,8 @@ 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)); } /* fmt must contain _one_ %s and no other substitution */ @@ -713,7 +722,7 @@ static char *find_name(const char *line, char *def, int p_value, int terminate) static char *find_name_traditional(const char *line, char *def, int p_value) { - size_t len = strlen(line); + size_t len; size_t date_len; if (*line == '"') { @@ -897,7 +906,7 @@ static void parse_traditional_patch(const char *first, const char *second, struc patch->old_name = name; } else { patch->old_name = name; - patch->new_name = xstrdup(name); + patch->new_name = null_strdup(name); } } if (!name) @@ -1032,15 +1041,17 @@ static int gitdiff_renamedst(const char *line, struct patch *patch) 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; } @@ -1910,7 +1921,7 @@ static int parse_binary(char *buffer, unsigned long size, struct patch *patch) } /* - * Read the patch text in "buffer" taht extends for "size" bytes; stop + * Read the patch text in "buffer" that 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 @@ -2086,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; @@ -2096,18 +2107,20 @@ 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; /* * Adjust the common context lines in postimage. This can be - * done in-place when we are just doing whitespace fixing, - * which does not make the string grow, but needs a new buffer - * when ignoring whitespace causes the update, since in this case - * we could have e.g. tabs converted to multiple spaces. + * done in-place when we are shrinking it with whitespace + * fixing, but needs a new buffer when ignoring whitespace or + * expanding leading tabs to spaces. + * * We trust the caller to tell us if the update can be done * in place (postlen==0) or not. */ @@ -2117,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 */ @@ -2136,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; @@ -2150,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, @@ -2163,7 +2185,7 @@ static int match_fragment(struct image *img, int i; char *fixed_buf, *buf, *orig, *target; struct strbuf fixed; - size_t fixed_len; + size_t fixed_len, postlen; int preimage_limit; if (preimage->nr + try_lno <= img->nr) { @@ -2313,6 +2335,7 @@ static int match_fragment(struct image *img, strbuf_init(&fixed, preimage->len + 1); orig = preimage->buf; target = img->buf + try; + postlen = 0; for (i = 0; i < preimage_limit; i++) { size_t oldlen = preimage->line[i].len; size_t tgtlen = img->line[try_lno + i].len; @@ -2340,6 +2363,7 @@ static int match_fragment(struct image *img, match = (tgtfix.len == fixed.len - fixstart && !memcmp(tgtfix.buf, fixed.buf + fixstart, fixed.len - fixstart)); + postlen += tgtfix.len; strbuf_release(&tgtfix); if (!match) @@ -2377,8 +2401,10 @@ static int match_fragment(struct image *img, * hunk match. Update the context lines in the postimage. */ fixed_buf = strbuf_detach(&fixed, &fixed_len); + if (postlen < postimage->len) + postlen = 0; update_pre_post_images(preimage, postimage, - fixed_buf, fixed_len, 0); + fixed_buf, fixed_len, postlen); return 1; unmatch_exit: @@ -2954,20 +2980,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 */ @@ -2976,6 +2999,13 @@ static int read_file_or_gitlink(struct cache_entry *ce, struct strbuf *buf) return 0; } +static int read_file_or_gitlink(const 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; @@ -2994,9 +3024,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 presence 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) @@ -3048,127 +3084,325 @@ 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(const 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, + const 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, + const 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); - } + 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, tpatch->result, tpatch->resultsize); - } else if (cached) { - if (read_file_or_gitlink(ce, &buf)) + 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); - } 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. - * NEEDSWORK: shouldn't this be flagged - * as an error??? - */ - free_fragment_list(patch->fragments); - patch->fragments = NULL; - } - } else { - if (read_old_data(st, patch->old_name, &buf)) - return error(_("read of %s failed"), patch->old_name); } } 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, const 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; - return error(_("%s: already exists in working directory"), new_name); + /* 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"); + + 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, const 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)); } - 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) @@ -3177,13 +3411,7 @@ static int check_preimage(struct patch *patch, struct cache_entry **ce, struct s } *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)) @@ -3196,7 +3424,7 @@ static int check_preimage(struct patch *patch, struct cache_entry **ce, struct s 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) @@ -3220,6 +3448,41 @@ static int check_preimage(struct patch *patch, struct cache_entry **ce, struct s 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. @@ -3242,31 +3505,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 presence 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) + ((0 < patch->is_new) || patch->is_rename || patch->is_copy)) { + 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); - if (!cached) { - int err = check_to_create_blob(new_name, ok_if_exists); - if (err) - return err; + 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; @@ -3328,6 +3605,40 @@ static int get_current_sha1(const char *path, unsigned char *sha1) return 0; } +static int preimage_sha1_in_gitlink_patch(struct patch *p, unsigned char sha1[20]) +{ + /* + * A usable gitlink patch has only one fragment (hunk) that looks like: + * @@ -1 +1 @@ + * -Subproject commit <old sha1> + * +Subproject commit <new sha1> + * or + * @@ -1 +0,0 @@ + * -Subproject commit <old sha1> + * for a removal patch. + */ + struct fragment *hunk = p->fragments; + static const char heading[] = "-Subproject commit "; + char *preimage; + + if (/* does the patch have only one hunk? */ + hunk && !hunk->next && + /* is its preimage one line? */ + hunk->oldpos == 1 && hunk->oldlines == 1 && + /* does preimage begin with the heading? */ + (preimage = memchr(hunk->patch, '\n', hunk->size)) != NULL && + !prefixcmp(++preimage, heading) && + /* does it record full SHA-1? */ + !get_sha1_hex(preimage + sizeof(heading) - 1, sha1) && + preimage[sizeof(heading) + 40 - 1] == '\n' && + /* does the abbreviated name on the index line agree with it? */ + !prefixcmp(preimage + sizeof(heading) - 1, p->old_sha1_prefix)) + return 0; /* it all looks fine */ + + /* we may have full object name on the index line */ + return get_sha1_hex(p->old_sha1_prefix, sha1); +} + /* Build an index that contains the just the files needed for a 3way merge */ static void build_fake_ancestor(struct patch *list, const char *filename) { @@ -3339,7 +3650,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; @@ -3347,20 +3657,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 (!preimage_sha1_in_gitlink_patch(patch, sha1)) + ; /* ok, the textual part looks sane */ + else + die("sha1 information is lacking or useless for submoule %s", + 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); if (add_index_entry(&result, ce, ADD_CACHE_OK_TO_ADD)) @@ -3527,12 +3842,13 @@ 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 submodule %s"), path); } else { if (!cached) { if (lstat(path, &st) < 0) @@ -3629,6 +3945,33 @@ static void create_one_file(char *path, unsigned mode, const char *buf, unsigned 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) { char *path = patch->new_name; @@ -3639,7 +3982,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 */ @@ -3741,6 +4088,7 @@ static int write_out_results(struct patch *list) int phase; int errs = 0; struct patch *l; + struct string_list cpath = STRING_LIST_INIT_DUP; for (phase = 0; phase < 2; phase++) { l = list; @@ -3749,12 +4097,30 @@ static int write_out_results(struct patch *list) 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; } @@ -3877,8 +4243,12 @@ static int apply_patch(int fd, const char *filename, int options) !apply_with_reject) exit(1); - if (apply && write_out_results(list)) - 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); @@ -4000,7 +4370,7 @@ int cmd_apply(int argc, const char **argv, const char *prefix_) OPT_NOOP_NOARG(0, "allow-binary-replacement"), OPT_NOOP_NOARG(0, "binary"), OPT_BOOLEAN(0, "numstat", &numstat, - N_("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, N_("instead of applying the patch, output a summary for the input")), OPT_BOOLEAN(0, "check", &check, @@ -4011,6 +4381,8 @@ int cmd_apply(int argc, const char **argv, const char *prefix_) N_("apply a patch without touching the working tree")), OPT_BOOLEAN(0, "apply", &force_apply, 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, N_("build a temporary index based on embedded index information")), { OPTION_CALLBACK, 'z', NULL, NULL, NULL, @@ -4059,6 +4431,15 @@ 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)) diff --git a/builtin/archive.c b/builtin/archive.c index 931956def9..49178f159e 100644 --- a/builtin/archive.c +++ b/builtin/archive.c @@ -27,8 +27,8 @@ static int run_remote_archiver(int argc, const char **argv, const char *remote, const char *exec, const char *name_hint) { - char buf[LARGE_PACKET_MAX]; - int fd[2], i, len, rv; + char *buf; + int fd[2], i, rv; struct transport *transport; struct remote *_remote; @@ -53,21 +53,18 @@ static int run_remote_archiver(int argc, const char **argv, packet_write(fd[1], "argument %s\n", argv[i]); packet_flush(fd[1]); - len = packet_read_line(fd[0], buf, sizeof(buf)); - if (!len) + buf = packet_read_line(fd[0], NULL); + if (!buf) die(_("git archive: expected ACK/NAK, got EOF")); - if (buf[len-1] == '\n') - buf[--len] = 0; if (strcmp(buf, "ACK")) { - if (len > 5 && !prefixcmp(buf, "NACK ")) + if (!prefixcmp(buf, "NACK ")) die(_("git archive: NACK %s"), buf + 5); - if (len > 4 && !prefixcmp(buf, "ERR ")) + if (!prefixcmp(buf, "ERR ")) die(_("remote error: %s"), buf + 4); die(_("git archive: protocol error")); } - len = packet_read_line(fd[0], buf, sizeof(buf)); - if (len) + if (packet_read_line(fd[0], NULL)) die(_("git archive: expected a flush")); /* Now, start reading from fd[0] and spit it out to stdout */ @@ -88,12 +85,12 @@ int cmd_archive(int argc, const char **argv, const char *prefix) const char *output = NULL; const char *remote = 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() }; diff --git a/builtin/bisect--helper.c b/builtin/bisect--helper.c index 8d325a5179..e3884e3bb6 100644 --- a/builtin/bisect--helper.c +++ b/builtin/bisect--helper.c @@ -4,7 +4,7 @@ #include "bisect.h" static const char * const git_bisect_helper_usage[] = { - "git bisect--helper --next-all [--no-checkout]", + N_("git bisect--helper --next-all [--no-checkout]"), NULL }; @@ -14,9 +14,9 @@ int cmd_bisect__helper(int argc, const char **argv, const char *prefix) 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, - "update BISECT_HEAD instead of checking out the current commit"), + N_("update BISECT_HEAD instead of checking out the current commit")), OPT_END() }; diff --git a/builtin/blame.c b/builtin/blame.c index cad4111a36..079dcd3407 100644 --- a/builtin/blame.c +++ b/builtin/blame.c @@ -21,13 +21,14 @@ #include "parse-options.h" #include "utf8.h" #include "userdiff.h" +#include "line-range.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 }; @@ -42,6 +43,7 @@ static int blank_boundary; static int incremental; static int xdl_opts; static int abbrev = -1; +static int no_whole_file_rename; static enum date_mode blame_date_mode = DATE_ISO8601; static size_t blame_date_width; @@ -565,11 +567,16 @@ static void dup_entry(struct blame_entry *dst, struct blame_entry *src) dst->score = 0; } -static const char *nth_line(struct scoreboard *sb, int lno) +static const char *nth_line(struct scoreboard *sb, long lno) { return sb->final_buf + sb->lineno[lno]; } +static const char *nth_line_cb(void *data, long lno) +{ + return nth_line((struct scoreboard *)data, lno); +} + /* * It is known that lines between tlno to same came from parent, and e * has an overlap with that range. it also is known that parent's @@ -1226,7 +1233,7 @@ static void pass_blame(struct scoreboard *sb, struct origin *origin, int opt) * The first pass looks for unrenamed path to optimize for * common cases, then we look for renames in the second pass. */ - for (pass = 0; pass < 2; pass++) { + for (pass = 0; pass < 2 - no_whole_file_rename; pass++) { struct origin *(*find)(struct scoreboard *, struct commit *, struct origin *); find = pass ? find_rename : find_origin; @@ -1321,30 +1328,31 @@ static void pass_blame(struct scoreboard *sb, struct origin *origin, int opt) * Information on commits, used for output. */ struct commit_info { - const char *author; - const char *author_mail; + struct strbuf author; + struct strbuf author_mail; unsigned long author_time; - const char *author_tz; + struct strbuf author_tz; /* filled only when asked for details */ - const char *committer; - const char *committer_mail; + struct strbuf committer; + struct strbuf committer_mail; unsigned long committer_time; - const char *committer_tz; + struct strbuf committer_tz; - const char *summary; + struct strbuf summary; }; /* * Parse author/committer line in the commit object buffer */ static void get_ac_line(const char *inbuf, const char *what, - int person_len, char *person, - int mail_len, char *mail, - unsigned long *time, const char **tz) + struct strbuf *name, struct strbuf *mail, + unsigned long *time, struct strbuf *tz) { - int len, tzlen, maillen; - char *tmp, *endp, *timepos, *mailpos; + struct ident_split ident; + size_t len, maillen, namelen; + char *tmp, *endp; + const char *namebuf, *mailbuf; tmp = strstr(inbuf, what); if (!tmp) @@ -1355,69 +1363,66 @@ static void get_ac_line(const char *inbuf, const char *what, len = strlen(tmp); else len = endp - tmp; - if (person_len <= len) { + + if (split_ident_line(&ident, tmp, len)) { error_out: /* Ugh */ - *tz = "(unknown)"; - strcpy(person, *tz); - strcpy(mail, *tz); + tmp = "(unknown)"; + strbuf_addstr(name, tmp); + strbuf_addstr(mail, tmp); + strbuf_addstr(tz, tmp); *time = 0; return; } - memcpy(person, tmp, len); - tmp = person; - tmp += len; - *tmp = 0; - while (person < tmp && *tmp != ' ') - tmp--; - if (tmp <= person) - goto error_out; - *tz = tmp+1; - tzlen = (person+len)-(tmp+1); + namelen = ident.name_end - ident.name_begin; + namebuf = ident.name_begin; - *tmp = 0; - while (person < tmp && *tmp != ' ') - tmp--; - if (tmp <= person) - goto error_out; - *time = strtoul(tmp, NULL, 10); - timepos = tmp; + maillen = ident.mail_end - ident.mail_begin; + mailbuf = ident.mail_begin; - *tmp = 0; - while (person < tmp && !(*tmp == ' ' && tmp[1] == '<')) - tmp--; - if (tmp <= person) - return; - mailpos = tmp + 1; - *tmp = 0; - maillen = timepos - tmp; - memcpy(mail, mailpos, maillen); - - if (!mailmap.nr) - return; + if (ident.date_begin && ident.date_end) + *time = strtoul(ident.date_begin, NULL, 10); + else + *time = 0; - /* - * mailmap expansion may make the name longer. - * make room by pushing stuff down. - */ - tmp = person + person_len - (tzlen + 1); - memmove(tmp, *tz, tzlen); - tmp[tzlen] = 0; - *tz = tmp; + if (ident.tz_begin && ident.tz_end) + strbuf_add(tz, ident.tz_begin, ident.tz_end - ident.tz_begin); + else + strbuf_addstr(tz, "(unknown)"); /* * Now, convert both name and e-mail using mailmap */ - if (map_user(&mailmap, mail+1, mail_len-1, person, tmp-person-1)) { - /* Add a trailing '>' to email, since map_user returns plain emails - Note: It already has '<', since we replace from mail+1 */ - mailpos = memchr(mail, '\0', mail_len); - if (mailpos && mailpos-mail < mail_len - 1) { - *mailpos = '>'; - *(mailpos+1) = '\0'; - } - } + map_user(&mailmap, &mailbuf, &maillen, + &namebuf, &namelen); + + strbuf_addf(mail, "<%.*s>", (int)maillen, mailbuf); + strbuf_add(name, namebuf, namelen); +} + +static void commit_info_init(struct commit_info *ci) +{ + + strbuf_init(&ci->author, 0); + strbuf_init(&ci->author_mail, 0); + strbuf_init(&ci->author_tz, 0); + strbuf_init(&ci->committer, 0); + strbuf_init(&ci->committer_mail, 0); + strbuf_init(&ci->committer_tz, 0); + strbuf_init(&ci->summary, 0); +} + +static void commit_info_destroy(struct commit_info *ci) +{ + + strbuf_release(&ci->author); + strbuf_release(&ci->author_mail); + strbuf_release(&ci->author_tz); + strbuf_release(&ci->committer); + strbuf_release(&ci->committer_mail); + strbuf_release(&ci->committer_tz); + strbuf_release(&ci->summary); } static void get_commit_info(struct commit *commit, @@ -1425,57 +1430,33 @@ static void get_commit_info(struct commit *commit, int detailed) { int len; - const char *subject; - char *reencoded, *message; - static char author_name[1024]; - static char author_mail[1024]; - static char committer_name[1024]; - static char committer_mail[1024]; - static char summary_buf[1024]; + const char *subject, *encoding; + char *message; - /* - * We've operated without save_commit_buffer, so - * we now need to populate them for output. - */ - if (!commit->buffer) { - enum object_type type; - unsigned long size; - commit->buffer = - read_sha1_file(commit->object.sha1, &type, &size); - if (!commit->buffer) - die("Cannot read commit %s", - sha1_to_hex(commit->object.sha1)); - } - reencoded = reencode_commit_message(commit, NULL); - message = reencoded ? reencoded : commit->buffer; - ret->author = author_name; - ret->author_mail = author_mail; + commit_info_init(ret); + + encoding = get_log_output_encoding(); + message = logmsg_reencode(commit, NULL, encoding); get_ac_line(message, "\nauthor ", - sizeof(author_name), author_name, - sizeof(author_mail), author_mail, + &ret->author, &ret->author_mail, &ret->author_time, &ret->author_tz); if (!detailed) { - free(reencoded); + logmsg_free(message, commit); return; } - ret->committer = committer_name; - ret->committer_mail = committer_mail; get_ac_line(message, "\ncommitter ", - sizeof(committer_name), committer_name, - sizeof(committer_mail), committer_mail, + &ret->committer, &ret->committer_mail, &ret->committer_time, &ret->committer_tz); - ret->summary = summary_buf; len = find_commit_subject(message, &subject); - if (len && len < sizeof(summary_buf)) { - memcpy(summary_buf, subject, len); - summary_buf[len] = 0; - } else { - sprintf(summary_buf, "(%s)", sha1_to_hex(commit->object.sha1)); - } - free(reencoded); + if (len) + strbuf_add(&ret->summary, subject, len); + else + strbuf_addf(&ret->summary, "(%s)", sha1_to_hex(commit->object.sha1)); + + logmsg_free(message, commit); } /* @@ -1503,15 +1484,15 @@ static int emit_one_suspect_detail(struct origin *suspect, int repeat) suspect->commit->object.flags |= METAINFO_SHOWN; get_commit_info(suspect->commit, &ci, 1); - printf("author %s\n", ci.author); - printf("author-mail %s\n", ci.author_mail); + printf("author %s\n", ci.author.buf); + printf("author-mail %s\n", ci.author_mail.buf); printf("author-time %lu\n", ci.author_time); - printf("author-tz %s\n", ci.author_tz); - printf("committer %s\n", ci.committer); - printf("committer-mail %s\n", ci.committer_mail); + printf("author-tz %s\n", ci.author_tz.buf); + printf("committer %s\n", ci.committer.buf); + printf("committer-mail %s\n", ci.committer_mail.buf); printf("committer-time %lu\n", ci.committer_time); - printf("committer-tz %s\n", ci.committer_tz); - printf("summary %s\n", ci.summary); + printf("committer-tz %s\n", ci.committer_tz.buf); + printf("summary %s\n", ci.summary.buf); if (suspect->commit->object.flags & UNINTERESTING) printf("boundary\n"); if (suspect->previous) { @@ -1519,6 +1500,9 @@ static int emit_one_suspect_detail(struct origin *suspect, int repeat) printf("previous %s ", sha1_to_hex(prev->commit->object.sha1)); write_name_quoted(prev->path, stdout, '\n'); } + + commit_info_destroy(&ci); + return 1; } @@ -1705,11 +1689,11 @@ static void emit_other(struct scoreboard *sb, struct blame_entry *ent, int opt) if (opt & OUTPUT_ANNOTATE_COMPAT) { const char *name; if (opt & OUTPUT_SHOW_EMAIL) - name = ci.author_mail; + name = ci.author_mail.buf; else - name = ci.author; + name = ci.author.buf; printf("\t(%10s\t%10s\t%d)", name, - format_time(ci.author_time, ci.author_tz, + format_time(ci.author_time, ci.author_tz.buf, show_raw_time), ent->lno + 1 + cnt); } else { @@ -1728,14 +1712,14 @@ static void emit_other(struct scoreboard *sb, struct blame_entry *ent, int opt) const char *name; int pad; if (opt & OUTPUT_SHOW_EMAIL) - name = ci.author_mail; + name = ci.author_mail.buf; else - name = ci.author; + name = ci.author.buf; pad = longest_author - utf8_strwidth(name); printf(" (%s%*s %10s", name, pad, "", format_time(ci.author_time, - ci.author_tz, + ci.author_tz.buf, show_raw_time)); } printf(" %*d) ", @@ -1750,6 +1734,8 @@ static void emit_other(struct scoreboard *sb, struct blame_entry *ent, int opt) if (sb->final_buf_size && cp[-1] != '\n') putchar('\n'); + + commit_info_destroy(&ci); } static void output(struct scoreboard *sb, int option) @@ -1874,9 +1860,9 @@ static void find_alignment(struct scoreboard *sb, int *option) suspect->commit->object.flags |= METAINFO_SHOWN; get_commit_info(suspect->commit, &ci, 1); if (*option & OUTPUT_SHOW_EMAIL) - num = utf8_strwidth(ci.author_mail); + num = utf8_strwidth(ci.author_mail.buf); else - num = utf8_strwidth(ci.author); + num = utf8_strwidth(ci.author.buf); if (longest_author < num) longest_author = num; } @@ -1888,6 +1874,8 @@ static void find_alignment(struct scoreboard *sb, int *option) longest_dst_lines = num; if (largest_score < ent_score(sb, e)) largest_score = ent_score(sb, e); + + commit_info_destroy(&ci); } max_orig_digits = decimal_width(longest_src_lines); max_digits = decimal_width(longest_dst_lines); @@ -1950,83 +1938,6 @@ static const char *add_prefix(const char *prefix, const char *path) } /* - * Parsing of (comma separated) one item in the -L option - */ -static const char *parse_loc(const char *spec, - struct scoreboard *sb, long lno, - long begin, long *ret) -{ - char *term; - const char *line; - long num; - int reg_error; - regex_t regexp; - regmatch_t match[1]; - - /* Allow "-L <something>,+20" to mean starting at <something> - * for 20 lines, or "-L <something>,-5" for 5 lines ending at - * <something>. - */ - if (1 < begin && (spec[0] == '+' || spec[0] == '-')) { - num = strtol(spec + 1, &term, 10); - if (term != spec + 1) { - if (spec[0] == '-') - num = 0 - num; - if (0 < num) - *ret = begin + num - 2; - else if (!num) - *ret = begin; - else - *ret = begin + num; - return term; - } - return spec; - } - num = strtol(spec, &term, 10); - if (term != spec) { - *ret = num; - return term; - } - if (spec[0] != '/') - return spec; - - /* it could be a regexp of form /.../ */ - for (term = (char *) spec + 1; *term && *term != '/'; term++) { - if (*term == '\\') - term++; - } - if (*term != '/') - return spec; - - /* try [spec+1 .. term-1] as regexp */ - *term = 0; - begin--; /* input is in human terms */ - line = nth_line(sb, begin); - - if (!(reg_error = regcomp(®exp, spec + 1, REG_NEWLINE)) && - !(reg_error = regexec(®exp, line, 1, match, 0))) { - const char *cp = line + match[0].rm_so; - const char *nline; - - while (begin++ < lno) { - nline = nth_line(sb, begin); - if (line <= cp && cp < nline) - break; - line = nline; - } - *ret = begin; - regfree(®exp); - *term++ = '/'; - return term; - } - else { - char errbuf[1024]; - regerror(reg_error, ®exp, errbuf, 1024); - die("-L parameter '%s': %s", spec + 1, errbuf); - } -} - -/* * Parsing of -L option */ static void prepare_blame_range(struct scoreboard *sb, @@ -2034,15 +1945,7 @@ static void prepare_blame_range(struct scoreboard *sb, long lno, long *bottom, long *top) { - const char *term; - - term = parse_loc(bottomtop, sb, lno, 1, bottom); - if (*term == ',') { - term = parse_loc(term + 1, sb, lno, *bottom + 1, top); - if (*term) - usage(blame_usage); - } - if (*term) + if (parse_range_arg(bottomtop, nth_line_cb, sb, lno, bottom, top, sb->path)) usage(blame_usage); } @@ -2069,6 +1972,55 @@ static int git_blame_config(const char *var, const char *value, void *cb) 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. @@ -2079,6 +2031,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; @@ -2086,20 +2039,38 @@ 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; @@ -2136,7 +2107,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"); @@ -2169,7 +2139,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); @@ -2180,16 +2151,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; } @@ -2312,27 +2273,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_BIT(0, "minimal", &xdl_opts, "Spend extra cycles to find better match", XDF_NEED_MINIMAL), - OPT_STRING('S', NULL, &revs_file, "file", "Use revisions from <file> instead of calling git-rev-list"), - OPT_STRING(0, "contents", &contents_from, "file", "Use <file>'s contents as the final image"), - { OPTION_CALLBACK, 'C', NULL, &opt, "score", "Find line copies within and across files", PARSE_OPT_OPTARG, blame_copy_callback }, - { OPTION_CALLBACK, 'M', NULL, &opt, "score", "Find line movements within and across files", PARSE_OPT_OPTARG, blame_move_callback }, - OPT_CALLBACK('L', NULL, &bottomtop, "n,m", "Process only line range n,m, counting from 1", blame_bottomtop_callback), + OPT_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() }; @@ -2344,6 +2305,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix) init_revisions(&revs, NULL); revs.date_mode = blame_date_mode; DIFF_OPT_SET(&revs.diffopt, ALLOW_TEXTCONV); + DIFF_OPT_SET(&revs.diffopt, FOLLOW_RENAMES); save_commit_buffer = 0; dashdash_pos = 0; @@ -2367,6 +2329,8 @@ int cmd_blame(int argc, const char **argv, const char *prefix) parse_revision_opt(&revs, &ctx, options, blame_opt_usage); } parse_done: + no_whole_file_rename = !DIFF_OPT_TST(&revs.diffopt, FOLLOW_RENAMES); + DIFF_OPT_CLR(&revs.diffopt, FOLLOW_RENAMES); argc = parse_options_end(&ctx); if (0 < abbrev) @@ -2531,10 +2495,6 @@ parse_done: bottom = top = 0; if (bottomtop) prepare_blame_range(&sb, bottomtop, lno, &bottom, &top); - if (bottom && top && top < bottom) { - long tmp; - tmp = top; top = bottom; bottom = tmp; - } if (bottom < 1) bottom = 1; if (top < 1) diff --git a/builtin/branch.c b/builtin/branch.c index 0e060f2e4a..083689063f 100644 --- a/builtin/branch.c +++ b/builtin/branch.c @@ -17,12 +17,14 @@ #include "revision.h" #include "string-list.h" #include "column.h" +#include "utf8.h" +#include "wt-status.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 }; @@ -39,13 +41,15 @@ static char branch_colors[][COLOR_MAXLEN] = { GIT_COLOR_RED, /* REMOTE */ GIT_COLOR_NORMAL, /* LOCAL */ GIT_COLOR_GREEN, /* CURRENT */ + GIT_COLOR_BLUE, /* UPSTREAM */ }; enum color_branch { BRANCH_COLOR_RESET = 0, BRANCH_COLOR_PLAIN = 1, BRANCH_COLOR_REMOTE = 2, BRANCH_COLOR_LOCAL = 3, - BRANCH_COLOR_CURRENT = 4 + BRANCH_COLOR_CURRENT = 4, + BRANCH_COLOR_UPSTREAM = 5 }; static enum merge_filter { @@ -70,6 +74,8 @@ static int parse_branch_color_slot(const char *var, int ofs) return BRANCH_COLOR_LOCAL; if (!strcasecmp(var+ofs, "current")) return BRANCH_COLOR_CURRENT; + if (!strcasecmp(var+ofs, "upstream")) + return BRANCH_COLOR_UPSTREAM; return -1; } @@ -129,7 +135,7 @@ static int branch_merged(int kind, const char *name, 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 @@ -139,7 +145,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."), @@ -153,10 +159,37 @@ static int branch_merged(int kind, const char *name, return merged; } +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; @@ -186,6 +219,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' " @@ -196,8 +232,10 @@ static int delete_branches(int argc, const char **argv, int force, int kinds, free(name); - name = xstrdup(mkpath(fmt, bname.buf)); - if (read_ref(name, sha1)) { + 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); @@ -205,40 +243,31 @@ static int delete_branches(int argc, const char **argv, int force, int kinds, 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); - ret = 1; - continue; - } - - if (delete_ref(name, sha1, 0)) { + if (delete_ref(name, sha1, REF_NODEREF)) { error(remote_branch ? _("Error deleting remote branch '%s'") : _("Error deleting branch '%s'"), bname.buf); ret = 1; - } else { - struct strbuf buf = STRBUF_INIT; - 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); + continue; + } + if (!quiet) { + printf(remote_branch + ? _("Deleted remote branch %s (was %s).\n") + : _("Deleted branch %s (was %s).\n"), + bname.buf, + (flags & REF_ISSYMREF) + ? target + : find_unique_abbrev(sha1, DEFAULT_ABBREV)); } + delete_branch_config(bname.buf); } free(name); @@ -249,7 +278,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; }; @@ -354,14 +383,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; } @@ -393,36 +422,52 @@ static void fill_tracking_info(struct strbuf *stat, const char *branch_name, int ours, theirs; char *ref = NULL; struct branch *branch = branch_get(branch_name); + struct strbuf fancy = STRBUF_INIT; if (!stat_tracking_info(branch, &ours, &theirs)) { if (branch && branch->merge && branch->merge[0]->dst && - show_upstream_ref) - strbuf_addf(stat, "[%s] ", - shorten_unambiguous_ref(branch->merge[0]->dst, 0)); + show_upstream_ref) { + ref = shorten_unambiguous_ref(branch->merge[0]->dst, 0); + if (want_color(branch_use_color)) + strbuf_addf(stat, "[%s%s%s] ", + branch_get_color(BRANCH_COLOR_UPSTREAM), + ref, branch_get_color(BRANCH_COLOR_RESET)); + else + strbuf_addf(stat, "[%s] ", ref); + } return; } - if (show_upstream_ref) + if (show_upstream_ref) { ref = shorten_unambiguous_ref(branch->merge[0]->dst, 0); + if (want_color(branch_use_color)) + strbuf_addf(&fancy, "%s%s%s", + branch_get_color(BRANCH_COLOR_UPSTREAM), + ref, branch_get_color(BRANCH_COLOR_RESET)); + else + strbuf_addstr(&fancy, ref); + } + if (!ours) { if (ref) - strbuf_addf(stat, _("[%s: behind %d]"), ref, theirs); + strbuf_addf(stat, _("[%s: behind %d]"), fancy.buf, theirs); else strbuf_addf(stat, _("[behind %d]"), theirs); } else if (!theirs) { if (ref) - strbuf_addf(stat, _("[%s: ahead %d]"), ref, ours); + strbuf_addf(stat, _("[%s: ahead %d]"), fancy.buf, ours); else strbuf_addf(stat, _("[ahead %d]"), ours); } else { if (ref) strbuf_addf(stat, _("[%s: ahead %d, behind %d]"), - ref, ours, theirs); + fancy.buf, ours, theirs); else strbuf_addf(stat, _("[ahead %d, behind %d]"), ours, theirs); } + strbuf_release(&fancy); strbuf_addch(stat, ' '); free(ref); } @@ -442,7 +487,7 @@ static void add_verbose_info(struct strbuf *out, struct ref_item *item, int verbose, int abbrev) { struct strbuf subject = STRBUF_INIT, stat = STRBUF_INIT; - const char *sub = " **** invalid ref ****"; + const char *sub = _(" **** invalid ref ****"); struct commit *commit = item->commit; if (commit && !parse_commit(commit)) { @@ -490,11 +535,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)); @@ -519,12 +565,35 @@ 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; } +static char *get_head_description(void) +{ + struct strbuf desc = STRBUF_INIT; + struct wt_status_state state; + memset(&state, 0, sizeof(state)); + wt_status_get_state(&state, 1); + if (state.rebase_in_progress || + state.rebase_interactive_in_progress) + strbuf_addf(&desc, _("(no branch, rebasing %s)"), + state.branch); + else if (state.bisect_in_progress) + strbuf_addf(&desc, _("(no branch, bisect started on %s)"), + state.branch); + else if (state.detached_from) + strbuf_addf(&desc, _("(detached from %s)"), + state.detached_from); + else + strbuf_addstr(&desc, _("(no branch)")); + free(state.branch); + free(state.onto); + free(state.detached_from); + return strbuf_detach(&desc, NULL); +} static void show_detached(struct ref_list *ref_list) { @@ -532,13 +601,13 @@ 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.name = get_head_description(); + 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); } @@ -565,7 +634,7 @@ static int print_ref_list(int kinds, int detached, int verbose, int abbrev, stru struct commit *filter; filter = lookup_commit_reference_gently(merge_filter_ref, 0); if (!filter) - die("object '%s' does not point to a commit", + die(_("object '%s' does not point to a commit"), sha1_to_hex(merge_filter_ref)); filter->object.flags |= UNINTERESTING; @@ -681,11 +750,11 @@ static int edit_branch_description(const char *branch_name) 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); + strbuf_commented_addf(&buf, + "Please edit the description for the branch\n" + " %s\n" + "Lines starting with '%c' will be stripped.\n", + branch_name, comment_line_char); fp = fopen(git_path(edit_description), "w"); if ((fwrite(buf.buf, 1, buf.len, fp) < buf.len) || fclose(fp)) { strbuf_release(&buf); @@ -700,7 +769,7 @@ static int edit_branch_description(const char *branch_name) stripspace(&buf, 1); strbuf_addf(&name, "branch.%s.description", branch_name); - status = git_config_set(name.buf, buf.buf); + status = git_config_set(name.buf, buf.len ? buf.buf : NULL); strbuf_release(&name); strbuf_release(&buf); @@ -712,62 +781,65 @@ int cmd_branch(int argc, const char **argv, const char *prefix) 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; + 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__QUIET(&quiet, "suppress informational messages"), - 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', "remotes", &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', "all", &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', "delete", &delete, "delete fully merged branch", 1), - OPT_BIT('D', NULL, &delete, "delete branch (even if not merged)", 2), - OPT_BIT('m', "move", &rename, "move/rename a branch and its reflog", 1), - OPT_BIT('M', NULL, &rename, "move/rename a branch, even if target exists", 2), - OPT_BOOLEAN(0, "list", &list, "list branch names"), - OPT_BOOLEAN('l', "create-reflog", &reflog, "create the branch's reflog"), + 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, - "edit the description for the branch"), - OPT__FORCE(&force_create, "force creation (when already exists)"), + 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, "list branches in columns"), + OPT_COLUMN(0, "column", &colopts, N_("list branches in columns")), OPT_END(), }; @@ -794,10 +866,13 @@ int cmd_branch(int argc, const char **argv, const char *prefix) argc = parse_options(argc, argv, prefix, options, builtin_branch_usage, 0); - if (!delete && !rename && !edit_description && argc == 0) + if (!delete && !rename && !edit_description && !new_upstream && !unset_upstream && argc == 0) list = 1; - if (!!delete + !!rename + !!force_create + !!list > 1) + if (with_commit || merge_filter != NO_FILTER) + list = 1; + + if (!!delete + !!rename + !!force_create + !!list + !!new_upstream + !!unset_upstream > 1) usage_with_options(builtin_branch_usage, options); if (abbrev == -1) @@ -809,9 +884,11 @@ int cmd_branch(int argc, const char **argv, const char *prefix) colopts = 0; } - if (delete) + if (delete) { + if (!argc) + die(_("branch name required")); return delete_branches(argc, argv, delete > 1, kinds, quiet); - else if (list) { + } else if (list) { int ret = print_ref_list(kinds, detached, verbose, abbrev, with_commit, argv); print_columns(&output, colopts, NULL); @@ -822,41 +899,122 @@ int cmd_branch(int argc, const char **argv, const char *prefix) const char *branch_name; struct strbuf branch_ref = STRBUF_INIT; - if (detached) - die("Cannot give description to detached HEAD"); - if (!argc) + if (!argc) { + if (detached) + die(_("Cannot give description to detached HEAD")); branch_name = head; - else if (argc == 1) + } else if (argc == 1) branch_name = argv[0]; else - usage_with_options(builtin_branch_usage, options); + die(_("cannot edit description of more than one branch")); 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.", + return error(_("No commit on branch '%s' yet."), branch_name); else - return error("No such branch '%s'.", branch_name); + return error(_("No branch named '%s'."), + branch_name); } strbuf_release(&branch_ref); if (edit_branch_description(branch_name)) return 1; } else if (rename) { - if (argc == 1) + if (!argc) + die(_("branch name required")); + else 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); + die(_("too many branches for a rename operation")); + } else if (new_upstream) { + struct branch *branch = branch_get(argv[0]); + + if (argc > 1) + die(_("too many branches to set new upstream")); + + if (!branch) { + if (!argc || !strcmp(argv[0], "HEAD")) + die(_("could not set upstream of HEAD to %s when " + "it does not point to any branch."), + new_upstream); + die(_("no such branch '%s'"), 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 (argc > 1) + die(_("too many branches to unset upstream")); + + if (!branch) { + if (!argc || !strcmp(argv[0], "HEAD")) + die(_("could not unset upstream of HEAD when " + "it does not point to any branch.")); + die(_("no such branch '%s'"), argv[0]); + } + + 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 (!strcmp(argv[0], "HEAD")) + die(_("it does not make sense to create 'HEAD' manually")); + + if (!branch) + die(_("no such branch '%s'"), argv[0]); + 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, 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/cat-file.c b/builtin/cat-file.c index 60568f9c39..0e64b4159c 100644 --- a/builtin/cat-file.c +++ b/builtin/cat-file.c @@ -13,76 +13,6 @@ #include "userdiff.h" #include "streaming.h" -#define BATCH 1 -#define BATCH_CHECK 2 - -static void pprint_tag(const unsigned char *sha1, const char *buf, unsigned long size) -{ - /* the parser in tag.c is useless here. */ - const char *endp = buf + size; - const char *cp = buf; - - while (cp < endp) { - char c = *cp++; - if (c != '\n') - continue; - if (7 <= endp - cp && !memcmp("tagger ", cp, 7)) { - const char *tagger = cp; - - /* Found the tagger line. Copy out the contents - * of the buffer so far. - */ - write_or_die(1, buf, cp - buf); - - /* - * Do something intelligent, like pretty-printing - * the date. - */ - while (cp < endp) { - if (*cp++ == '\n') { - /* tagger to cp is a line - * that has ident and time. - */ - const char *sp = tagger; - char *ep; - unsigned long date; - long tz; - while (sp < cp && *sp != '>') - sp++; - if (sp == cp) { - /* give up */ - write_or_die(1, tagger, - cp - tagger); - break; - } - while (sp < cp && - !('0' <= *sp && *sp <= '9')) - sp++; - write_or_die(1, tagger, sp - tagger); - date = strtoul(sp, &ep, 10); - tz = strtol(ep, NULL, 10); - sp = show_date(date, tz, 0); - write_or_die(1, sp, strlen(sp)); - xwrite(1, "\n", 1); - break; - } - } - break; - } - if (cp < endp && *cp == '\n') - /* end of header */ - break; - } - /* At this point, we have copied out the header up to the end of - * the tagger line and cp points at one past \n. It could be the - * next header line after the tagger line, or it could be another - * \n that marks the end of the headers. We need to copy out the - * remainder as is. - */ - if (cp < endp) - write_or_die(1, cp, endp - cp); -} - static int cat_one_file(int opt, const char *exp_type, const char *obj_name) { unsigned char sha1[20]; @@ -91,7 +21,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; @@ -133,10 +63,6 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name) buf = read_sha1_file(sha1, &type, &size); if (!buf) die("Cannot read object %s", obj_name); - if (type == OBJ_TAG) { - pprint_tag(sha1, buf, size); - return 0; - } /* otherwise just spit out the data */ break; @@ -188,54 +114,174 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name) return 0; } -static int batch_one_object(const char *obj_name, int print_contents) -{ +struct expand_data { unsigned char sha1[20]; - enum object_type type = 0; + enum object_type type; unsigned long size; - void *contents = contents; + unsigned long disk_size; + const char *rest; + + /* + * If mark_query is true, we do not expand anything, but rather + * just mark the object_info with items we wish to query. + */ + int mark_query; + + /* + * After a mark_query run, this object_info is set up to be + * passed to sha1_object_info_extended. It will point to the data + * elements above, so you can retrieve the response from there. + */ + struct object_info info; +}; + +static int is_atom(const char *atom, const char *s, int slen) +{ + int alen = strlen(atom); + return alen == slen && !memcmp(atom, s, alen); +} + +static void expand_atom(struct strbuf *sb, const char *atom, int len, + void *vdata) +{ + struct expand_data *data = vdata; + + if (is_atom("objectname", atom, len)) { + if (!data->mark_query) + strbuf_addstr(sb, sha1_to_hex(data->sha1)); + } else if (is_atom("objecttype", atom, len)) { + if (!data->mark_query) + strbuf_addstr(sb, typename(data->type)); + } else if (is_atom("objectsize", atom, len)) { + if (data->mark_query) + data->info.sizep = &data->size; + else + strbuf_addf(sb, "%lu", data->size); + } else if (is_atom("objectsize:disk", atom, len)) { + if (data->mark_query) + data->info.disk_sizep = &data->disk_size; + else + strbuf_addf(sb, "%lu", data->disk_size); + } else if (is_atom("rest", atom, len)) { + if (!data->mark_query && data->rest) + strbuf_addstr(sb, data->rest); + } else + die("unknown format element: %.*s", len, atom); +} + +static size_t expand_format(struct strbuf *sb, const char *start, void *data) +{ + const char *end; + + if (*start != '(') + return 0; + end = strchr(start + 1, ')'); + if (!end) + die("format element '%s' does not end in ')'", start); + + expand_atom(sb, start + 1, end - start - 1, data); + + return end - start + 1; +} + +static void print_object_or_die(int fd, const unsigned char *sha1, + enum object_type type, unsigned long size) +{ + if (type == OBJ_BLOB) { + if (stream_blob_to_fd(fd, sha1, NULL, 0) < 0) + die("unable to stream %s to stdout", sha1_to_hex(sha1)); + } + else { + enum object_type rtype; + unsigned long rsize; + void *contents; + + contents = read_sha1_file(sha1, &rtype, &rsize); + if (!contents) + die("object %s disappeared", sha1_to_hex(sha1)); + if (rtype != type) + die("object %s changed type!?", sha1_to_hex(sha1)); + if (rsize != size) + die("object %s change size!?", sha1_to_hex(sha1)); + + write_or_die(fd, contents, size); + free(contents); + } +} + +struct batch_options { + int enabled; + int print_contents; + const char *format; +}; + +static int batch_one_object(const char *obj_name, struct batch_options *opt, + struct expand_data *data) +{ + struct strbuf buf = STRBUF_INIT; if (!obj_name) return 1; - if (get_sha1(obj_name, sha1)) { + if (get_sha1(obj_name, data->sha1)) { printf("%s missing\n", obj_name); fflush(stdout); return 0; } - if (print_contents == BATCH) - contents = read_sha1_file(sha1, &type, &size); - else - type = sha1_object_info(sha1, &size); - - if (type <= 0) { + data->type = sha1_object_info_extended(data->sha1, &data->info); + if (data->type <= 0) { printf("%s missing\n", obj_name); fflush(stdout); - if (print_contents == BATCH) - free(contents); return 0; } - printf("%s %s %lu\n", sha1_to_hex(sha1), typename(type), size); - fflush(stdout); + strbuf_expand(&buf, opt->format, expand_format, data); + strbuf_addch(&buf, '\n'); + write_or_die(1, buf.buf, buf.len); + strbuf_release(&buf); - if (print_contents == BATCH) { - write_or_die(1, contents, size); - printf("\n"); - fflush(stdout); - free(contents); + if (opt->print_contents) { + print_object_or_die(1, data->sha1, data->type, data->size); + write_or_die(1, "\n", 1); } - return 0; } -static int batch_objects(int print_contents) +static int batch_objects(struct batch_options *opt) { struct strbuf buf = STRBUF_INIT; + struct expand_data data; + + if (!opt->format) + opt->format = "%(objectname) %(objecttype) %(objectsize)"; + + /* + * Expand once with our special mark_query flag, which will prime the + * object_info to be handed to sha1_object_info_extended for each + * object. + */ + memset(&data, 0, sizeof(data)); + data.mark_query = 1; + strbuf_expand(&buf, opt->format, expand_format, &data); + data.mark_query = 0; while (strbuf_getline(&buf, stdin, '\n') != EOF) { - int error = batch_one_object(buf.buf, print_contents); + char *p; + int error; + + /* + * Split at first whitespace, tying off the beginning of the + * string and saving the remainder (or NULL) in data.rest. + */ + p = strpbrk(buf.buf, " \t"); + if (p) { + while (*p && strchr(" \t", *p)) + *p++ = '\0'; + } + data.rest = p; + + error = batch_one_object(buf.buf, opt, &data); if (error) return error; } @@ -244,8 +290,8 @@ 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 }; @@ -257,26 +303,45 @@ static int git_cat_file_config(const char *var, const char *value, void *cb) return git_default_config(var, value, cb); } +static int batch_option_callback(const struct option *opt, + const char *arg, + int unset) +{ + struct batch_options *bo = opt->value; + + if (unset) { + memset(bo, 0, sizeof(*bo)); + return 0; + } + + bo->enabled = 1; + bo->print_contents = !strcmp(opt->long_name, "batch"); + bo->format = arg; + + return 0; +} + int cmd_cat_file(int argc, const char **argv, const char *prefix) { - int opt = 0, batch = 0; + int opt = 0; const char *exp_type = NULL, *obj_name = NULL; + struct batch_options batch = {0}; 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'), - OPT_SET_INT(0, "batch", &batch, - "show info and content of objects fed from the standard input", - BATCH), - OPT_SET_INT(0, "batch-check", &batch, - "show info about objects fed from the standard input", - BATCH_CHECK), + N_("for blob objects, run textconv on object's content"), 'c'), + { OPTION_CALLBACK, 0, "batch", &batch, "format", + N_("show info and content of objects fed from the standard input"), + PARSE_OPT_OPTARG, batch_option_callback }, + { OPTION_CALLBACK, 0, "batch-check", &batch, "format", + N_("show info about objects fed from the standard input"), + PARSE_OPT_OPTARG, batch_option_callback }, OPT_END() }; @@ -293,19 +358,19 @@ int cmd_cat_file(int argc, const char **argv, const char *prefix) else usage_with_options(cat_file_usage, options); } - if (!opt && !batch) { + if (!opt && !batch.enabled) { if (argc == 2) { exp_type = argv[0]; obj_name = argv[1]; } else usage_with_options(cat_file_usage, options); } - if (batch && (opt || argc)) { + if (batch.enabled && (opt || argc)) { usage_with_options(cat_file_usage, options); } - if (batch) - return batch_objects(batch); + if (batch.enabled) + return batch_objects(&batch); return cat_one_file(opt, exp_type, obj_name); } diff --git a/builtin/check-attr.c b/builtin/check-attr.c index 44c421eb0f..075d01d30c 100644 --- a/builtin/check-attr.c +++ b/builtin/check-attr.c @@ -8,19 +8,19 @@ static int all_attrs; static int cached_attrs; static int stdin_paths; static const char * const check_attr_usage[] = { -"git check-attr [-a | --all | attr...] [--] pathname...", -"git check-attr --stdin [-a | --all | 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('a', "all", &all_attrs, "report all attributes set on file"), - OPT_BOOLEAN(0, "cached", &cached_attrs, "use .gitattributes only from the index"), - OPT_BOOLEAN(0 , "stdin", &stdin_paths, "read file names from stdin"), + OPT_BOOLEAN('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() }; diff --git a/builtin/check-ignore.c b/builtin/check-ignore.c index c00a7d6bb9..4a8fc707c7 100644 --- a/builtin/check-ignore.c +++ b/builtin/check-ignore.c @@ -63,7 +63,7 @@ static void output_exclude(const char *path, struct exclude *exclude) } } -static int check_ignore(struct path_exclude_check *check, +static int check_ignore(struct dir_struct *dir, const char *prefix, const char **pathspec) { const char *path, *full_path; @@ -91,8 +91,7 @@ static int check_ignore(struct path_exclude_check *check, die_if_path_beyond_symlink(full_path, prefix); exclude = NULL; if (!seen[i]) { - exclude = last_exclude_matching_path(check, full_path, - -1, &dtype); + exclude = last_exclude_matching(dir, full_path, &dtype); } if (!quiet && (exclude || show_non_matching)) output_exclude(path, exclude); @@ -104,7 +103,7 @@ static int check_ignore(struct path_exclude_check *check, return num_ignored; } -static int check_ignore_stdin_paths(struct path_exclude_check *check, const char *prefix) +static int check_ignore_stdin_paths(struct dir_struct *dir, const char *prefix) { struct strbuf buf, nbuf; char *pathspec[2] = { NULL, NULL }; @@ -121,7 +120,7 @@ static int check_ignore_stdin_paths(struct path_exclude_check *check, const char strbuf_swap(&buf, &nbuf); } pathspec[0] = buf.buf; - num_ignored += check_ignore(check, prefix, (const char **)pathspec); + num_ignored += check_ignore(dir, prefix, (const char **)pathspec); maybe_flush_or_die(stdout, "check-ignore to stdout"); } strbuf_release(&buf); @@ -133,7 +132,6 @@ int cmd_check_ignore(int argc, const char **argv, const char *prefix) { int num_ignored; struct dir_struct dir; - struct path_exclude_check check; git_config(git_default_config, NULL); @@ -163,19 +161,16 @@ int cmd_check_ignore(int argc, const char **argv, const char *prefix) die(_("index file corrupt")); memset(&dir, 0, sizeof(dir)); - dir.flags |= DIR_COLLECT_IGNORED; setup_standard_excludes(&dir); - path_exclude_check_init(&check, &dir); if (stdin_paths) { - num_ignored = check_ignore_stdin_paths(&check, prefix); + num_ignored = check_ignore_stdin_paths(&dir, prefix); } else { - num_ignored = check_ignore(&check, prefix, argv); + num_ignored = check_ignore(&dir, prefix, argv); maybe_flush_or_die(stdout, "ignore to stdout"); } clear_directory(&dir); - path_exclude_check_clear(&check); return !num_ignored; } 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 45aa0a8701..7025938ae3 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -22,22 +22,20 @@ #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; int overwrite_ignore; - - /* not set by parse_options */ - int branch_exists; + int ignore_skipworktree; const char *new_branch; const char *new_branch_force; @@ -45,6 +43,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, @@ -73,7 +76,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_UPDATE; + 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; @@ -93,7 +97,7 @@ static int read_tree_some(struct tree *tree, const char **pathspec) return 0; } -static int skip_same_name(struct cache_entry *ce, int pos) +static int skip_same_name(const struct cache_entry *ce, int pos) { while (++pos < active_nr && !strcmp(active_cache[pos]->name, ce->name)) @@ -101,7 +105,7 @@ static int skip_same_name(struct cache_entry *ce, int pos) return pos; } -static int check_stage(int stage, struct cache_entry *ce, int pos) +static int check_stage(int stage, const struct cache_entry *ce, int pos) { while (pos < active_nr && !strcmp(active_cache[pos]->name, ce->name)) { @@ -115,7 +119,7 @@ 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_stages(unsigned stages, struct cache_entry *ce, int pos) +static int check_stages(unsigned stages, const struct cache_entry *ce, int pos) { unsigned seen = 0; const char *name = ce->name; @@ -214,8 +218,8 @@ static int checkout_merged(int pos, struct checkout *state) 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; @@ -227,37 +231,98 @@ 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); + /* + * Make sure all pathspecs participated in locating the paths + * to be checked out. + */ for (pos = 0; pos < active_nr; pos++) { struct cache_entry *ce = active_cache[pos]; - if (source_tree && !(ce->ce_flags & CE_UPDATE)) + ce->ce_flags &= ~CE_MATCHED; + if (!opts->ignore_skipworktree && ce_skip_worktree(ce)) continue; - match_pathspec(pathspec, ce->name, ce_namelen(ce), 0, ps_matched); + if (opts->source_tree && !(ce->ce_flags & CE_UPDATE)) + /* + * "git checkout tree-ish -- path", but this entry + * is in the original index; it will not be checked + * out to the working tree and it does not matter + * if pathspec matched this entry. We will not do + * anything to this entry at all. + */ + continue; + /* + * Either this entry came from the tree-ish we are + * checking the paths out of, or we are checking out + * of the index. + * + * If it comes from the tree-ish, we already know it + * matches the pathspec and could just stamp + * CE_MATCHED to it from update_some(). But we still + * need ps_matched and read_tree_recursive (and + * eventually tree_entry_interesting) cannot fill + * ps_matched yet. Once it can, we can avoid calling + * match_pathspec() for _all_ entries when + * opts->source_tree != NULL. + */ + if (match_pathspec(opts->pathspec, ce->name, ce_namelen(ce), + 0, ps_matched)) + ce->ce_flags |= CE_MATCHED; } - if (report_path_error(ps_matched, pathspec, prefix)) + if (report_path_error(ps_matched, opts->pathspec, opts->prefix)) { + free(ps_matched); return 1; + } + free(ps_matched); /* "checkout -m path" to recreate conflicted state */ if (opts->merge) - unmerge_cache(pathspec); + unmerge_marked_index(&the_index); /* 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)) { + const struct cache_entry *ce = active_cache[pos]; + if (ce->ce_flags & CE_MATCHED) { if (!ce_stage(ce)) continue; if (opts->force) { @@ -282,9 +347,7 @@ static int checkout_paths(struct tree *source_tree, const char **pathspec, state.refresh_cache = 1; for (pos = 0; pos < active_nr; pos++) { struct cache_entry *ce = active_cache[pos]; - if (source_tree && !(ce->ce_flags & CE_UPDATE)) - continue; - if (match_pathspec(pathspec, ce->name, ce_namelen(ce), 0, NULL)) { + if (ce->ce_flags & CE_MATCHED) { if (!ce_stage(ce)) { errs |= checkout_entry(ce, &state, NULL); continue; @@ -308,7 +371,8 @@ static int checkout_paths(struct tree *source_tree, const char **pathspec, 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. */ @@ -330,7 +394,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; @@ -349,7 +414,7 @@ static int reset_tree(struct tree *tree, struct checkout_opts *o, int worktree) 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 @@ -380,8 +445,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)); @@ -392,7 +459,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 { @@ -478,7 +545,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; @@ -486,7 +554,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; } @@ -513,12 +582,12 @@ static void report_tracking(struct branch_info *new) strbuf_release(&sb); } -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) { struct strbuf msg = STRBUF_INIT; - const char *old_desc; + const char *old_desc, *reflog_msg; if (opts->new_branch) { if (opts->new_orphan_branch) { if (opts->new_branch_log && !log_all_ref_updates) { @@ -551,8 +620,13 @@ static void update_refs_for_switch(struct checkout_opts *opts, old_desc = old->name; if (!old_desc && old->commit) old_desc = sha1_to_hex(old->commit->object.sha1); - strbuf_addf(&msg, "checkout: moving from %s to %s", - old_desc ? old_desc : "(invalid)", new->name); + + reflog_msg = getenv("GIT_REFLOG_ACTION"); + if (!reflog_msg) + strbuf_addf(&msg, "checkout: moving from %s to %s", + old_desc ? old_desc : "(invalid)", new->name); + else + strbuf_insert(&msg, 0, reflog_msg, strlen(reflog_msg)); if (!strcmp(new->name, "HEAD") && !new->path && !opts->force_detach) { /* Nothing to do. */ @@ -663,7 +737,7 @@ static void suggest_reattach(struct commit *commit, struct rev_info *revs) "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)); + find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV)); } /* @@ -700,13 +774,14 @@ static void orphaned_commit_warning(struct commit *old, struct commit *new) 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 = path_to_free = resolve_refdup("HEAD", rev, 0, &flag); old.commit = lookup_commit_reference_gently(rev, 1); @@ -724,7 +799,7 @@ static int switch_branches(struct checkout_opts *opts, struct branch_info *new) parse_commit(new->commit); } - ret = merge_working_tree(opts, &old, new); + ret = merge_working_tree(opts, &old, new, &writeout_error); if (ret) { free(path_to_free); return ret; @@ -737,7 +812,7 @@ static int switch_branches(struct checkout_opts *opts, struct branch_info *new) ret = post_checkout_hook(old.commit, new->commit, 1); free(path_to_free); - return ret || opts->writeout_error; + return ret || writeout_error; } static int git_checkout_config(const char *var, const char *value, void *cb) @@ -754,45 +829,44 @@ 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; + /* const */ char *src_ref; + char *dst_ref; + unsigned char *dst_sha1; int unique; }; -static int check_tracking_name(const char *refname, const unsigned char *sha1, - int flags, void *cb_data) +static int check_tracking_name(struct remote *remote, void *cb_data) { struct tracking_name_data *cb = cb_data; - const char *slash; - - if (prefixcmp(refname, "refs/remotes/")) - return 0; - slash = strchr(refname + 13, '/'); - if (!slash || strcmp(slash + 1, cb->name)) + struct refspec query; + memset(&query, 0, sizeof(struct refspec)); + query.src = cb->src_ref; + if (remote_find_tracking(remote, &query) || + get_sha1(query.dst, cb->dst_sha1)) { + free(query.dst); return 0; - if (cb->remote) { + } + if (cb->dst_ref) { + free(query.dst); cb->unique = 0; return 0; } - cb->remote = xstrdup(refname); + cb->dst_ref = query.dst; return 0; } -static const char *unique_tracking_name(const char *name) +static const char *unique_tracking_name(const char *name, unsigned char *sha1) { - struct tracking_name_data cb_data = { NULL, NULL, 1 }; - cb_data.name = name; - for_each_ref(check_tracking_name, &cb_data); + struct tracking_name_data cb_data = { NULL, NULL, NULL, 1 }; + char src_ref[PATH_MAX]; + snprintf(src_ref, PATH_MAX, "refs/heads/%s", name); + cb_data.src_ref = src_ref; + cb_data.dst_sha1 = sha1; + for_each_remote(check_tracking_name, &cb_data); if (cb_data.unique) - return cb_data.remote; - free(cb_data.remote); + return cb_data.dst_ref; + free(cb_data.dst_ref); return NULL; } @@ -855,8 +929,8 @@ static int parse_branchname_arg(int argc, const char **argv, if (dwim_new_local_branch_ok && !check_filename(NULL, arg) && argc == 1) { - const char *remote = unique_tracking_name(arg); - if (!remote || get_sha1(remote, rev)) + const char *remote = unique_tracking_name(arg, rev); + if (!remote) return argcount; *new_branch = arg; arg = remote; @@ -909,7 +983,7 @@ static int parse_branchname_arg(int argc, const char **argv, return argcount; } -static int switch_unborn_to_new_branch(struct checkout_opts *opts) +static int switch_unborn_to_new_branch(const struct checkout_opts *opts) { int status; struct strbuf branch_ref = STRBUF_INIT; @@ -919,41 +993,88 @@ static int switch_unborn_to_new_branch(struct checkout_opts *opts) 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_BOOLEAN(0, "overwrite-ignore", &opts.overwrite_ignore, "update ignored files (default)"), - OPT_STRING(0, "conflict", &conflict_style, "style", - "conflict style (merge or diff3)"), - OPT_BOOLEAN('p', "patch", &patch_mode, "select hunks interactively"), + 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")), + OPT_BOOL(0, "ignore-skip-worktree-bits", &opts.ignore_skipworktree, + N_("do not limit pathspecs to sparse entries only")), { 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(), }; @@ -961,6 +1082,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) 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); @@ -970,26 +1092,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")); @@ -1003,22 +1126,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. @@ -1033,73 +1140,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; - opts.branch_exists = validate_new_branchname(opts.new_branch, &buf, - !!opts.new_branch_force, - !!opts.new_branch_force); + opts.branch_exists = + validate_new_branchname(opts.new_branch, &buf, + !!opts.new_branch_force, + !!opts.new_branch_force); strbuf_release(&buf); } - if (new.name && !new.commit) { - die(_("Cannot switch branch to a non-commit.")); - } - if (opts.writeout_stage) - die(_("--ours/--theirs is incompatible with switching branches.")); - - if (!new.commit && opts.new_branch) { - unsigned char rev[20]; - int flag; - - if (!read_ref_full("HEAD", rev, 0, &flag) && - (flag & REF_ISSYMREF) && is_null_sha1(rev)) - return switch_unborn_to_new_branch(&opts); - } - return switch_branches(&opts, &new); + 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 b9cb7ad4e0..e344137ef2 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; @@ -49,16 +157,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", - "add <pattern> to ignore rules", 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() }; @@ -78,7 +186,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")); @@ -113,7 +221,7 @@ int cmd_clean(int argc, const char **argv, const char *prefix) struct dir_entry *ent = dir.entries[i]; int len, pos; int matches = 0; - struct cache_entry *ce; + const struct cache_entry *ce; struct stat st; /* @@ -150,38 +258,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 920ef7f8c9..430307b298 100644 --- a/builtin/clone.c +++ b/builtin/clone.c @@ -18,11 +18,11 @@ #include "transport.h" #include "strbuf.h" #include "dir.h" -#include "pack-refs.h" #include "sigchain.h" #include "branch.h" #include "remote.h" #include "run-command.h" +#include "connected.h" /* * Overall FIXMEs: @@ -33,12 +33,12 @@ * */ 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, option_single_branch = -1; -static int option_local, option_no_hardlinks, option_shared, option_recursive; +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; @@ -61,43 +61,43 @@ 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_BOOL(0, "progress", &option_progress, - "force progress reporting"), + 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, "name", - "use <name> instead of 'origin' to track upstream"), - OPT_STRING('b', "branch", &option_branch, "branch", - "checkout <branch> instead of the remote's HEAD"), - OPT_STRING('u', "upload-pack", &option_upload_pack, "path", - "path to git-upload-pack on the remote"), - OPT_STRING(0, "depth", &option_depth, "depth", - "create a shallow clone of that depth"), + 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, - "clone only one branch, HEAD or --branch"), - OPT_STRING(0, "separate-git-dir", &real_git_dir, "gitdir", - "separate git dir from working tree"), - OPT_STRING_LIST('c', "config", &option_config, "key=value", - "set config inside the new repository"), + 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() }; @@ -231,16 +231,26 @@ static void strip_trailing_slashes(char *dir) static int add_one_reference(struct string_list_item *item, void *cb_data) { char *ref_git; + const char *repo; struct strbuf alternate = STRBUF_INIT; - /* Beware: real_path() and mkpath() return static buffer */ + /* Beware: read_gitfile(), 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)); + + repo = read_gitfile(ref_git); + if (!repo) + repo = read_gitfile(mkpath("%s/.git", ref_git)); + if (repo) { + free(ref_git); + ref_git = xstrdup(repo); + } + + if (!repo && is_directory(mkpath("%s/.git/objects", 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))) - die(_("reference repository '%s' is not a local directory."), + die(_("reference repository '%s' is not a local repository."), item->string); strbuf_addf(&alternate, "%s/objects", ref_git); @@ -342,7 +352,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; } @@ -376,10 +386,32 @@ static void clone_local(const char *src_repo, const char *dest_repo) static const char *junk_work_tree; static const char *junk_git_dir; static pid_t junk_pid; +static enum { + JUNK_LEAVE_NONE, + JUNK_LEAVE_REPO, + JUNK_LEAVE_ALL +} junk_mode = JUNK_LEAVE_NONE; + +static const char junk_leave_repo_msg[] = +N_("Clone succeeded, but checkout failed.\n" + "You can inspect what was checked out with 'git status'\n" + "and retry the checkout with 'git checkout -f HEAD'\n"); static void remove_junk(void) { struct strbuf sb = STRBUF_INIT; + + switch (junk_mode) { + case JUNK_LEAVE_REPO: + warning("%s", _(junk_leave_repo_msg)); + /* fall-through */ + case JUNK_LEAVE_ALL: + return; + default: + /* proceed to removal */ + break; + } + if (getpid() != junk_pid) return; if (junk_git_dir) { @@ -461,13 +493,16 @@ static void write_remote_refs(const struct ref *local_refs) { const struct ref *r; + lock_packed_refs(LOCK_DIE_ON_ERROR); + 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); + if (commit_packed_refs()) + die_errno("unable to overwrite old ref-pack file"); } static void write_followtags(const struct ref *refs, const char *msg) @@ -485,12 +520,46 @@ static void write_followtags(const struct ref *refs, const char *msg) } } +static int iterate_ref_map(void *cb_data, unsigned char sha1[20]) +{ + struct ref **rm = cb_data; + struct ref *ref = *rm; + + /* + * Skip anything missing a peer_ref, which we are not + * actually going to write a ref for. + */ + while (ref && !ref->peer_ref) + ref = ref->next; + /* Returning -1 notes "end of list" to the caller. */ + if (!ref) + return -1; + + hashcpy(sha1, ref->old_sha1); + *rm = ref->next; + return 0; +} + 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) + const char *msg, + struct transport *transport, + int check_connectivity) { + const struct ref *rm = mapped_refs; + + if (check_connectivity) { + if (0 <= option_verbosity) + printf(_("Checking connectivity... ")); + if (check_everything_connected_with_transport(iterate_ref_map, + 0, &rm, transport)) + die(_("remote did not send all necessary objects")); + if (0 <= option_verbosity) + printf(_("done\n")); + } + if (refs) { write_remote_refs(mapped_refs); if (option_single_branch) @@ -579,7 +648,8 @@ static int checkout(void) tree = parse_tree_indirect(sha1); parse_tree(tree); init_tree_desc(&t, tree->buffer, tree->size); - unpack_trees(1, &t, &opts); + if (unpack_trees(1, &t, &opts) < 0) + die(_("unable to checkout working tree")); if (write_cache(fd, active_cache, active_nr) || commit_locked_index(lock_file)) @@ -610,6 +680,54 @@ static void write_config(struct string_list *config) } } +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) { int is_bundle = 0, is_local; @@ -656,6 +774,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; } @@ -671,9 +791,11 @@ 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.")); + if (option_local > 0 && !is_local) + warning(_("--local is ignored")); if (argc == 2) dir = xstrdup(argv[1]); @@ -700,7 +822,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) { @@ -717,14 +839,14 @@ int cmd_clone(int argc, const char **argv, const char *prefix) atexit(remove_junk); sigchain_push_common(remove_junk_on_signal); - setenv(CONFIG_ENVIRONMENT, mkpath("%s/config", git_dir), 1); - if (safe_create_leading_directories_const(git_dir) < 0) die(_("could not create leading directories of '%s'"), git_dir); set_git_dir_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) @@ -735,13 +857,6 @@ int cmd_clone(int argc, const char **argv, const char *prefix) init_db(option_template, INIT_DB_QUIET); write_config(&option_config); - /* - * At this point, the config exists, so we do not need the - * environment variable. We actually need to unset it, too, to - * re-enable parsing of the global configs. - */ - unsetenv(CONFIG_ENVIRONMENT); - git_config(git_default_config, NULL); if (option_bare) { @@ -755,20 +870,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); @@ -801,6 +902,9 @@ int cmd_clone(int argc, const char **argv, const char *prefix) if (option_upload_pack) transport_set_option(transport, TRANS_OPT_UPLOADPACK, option_upload_pack); + + if (transport->smart_options && !option_depth) + transport->smart_options->check_self_contained_and_connected = 1; } refs = transport_get_remote_refs(transport); @@ -853,25 +957,29 @@ int cmd_clone(int argc, const char **argv, const char *prefix) "refs/heads/master"); } + write_refspec_config(src_ref_prefix, our_head_points_at, + remote_head_points_at, &branch_top); + if (is_local) clone_local(path, git_dir); else if (refs && complete_refs_before_fetch) transport_fetch_refs(transport, mapped_refs); update_remote_refs(refs, mapped_refs, remote_head_points_at, - branch_top.buf, reflog_msg.buf); + branch_top.buf, reflog_msg.buf, transport, !is_local); update_head(our_head_points_at, remote_head, reflog_msg.buf); transport_unlock_pack(transport); transport_disconnect(transport); + junk_mode = JUNK_LEAVE_REPO; err = checkout(); strbuf_release(&reflog_msg); strbuf_release(&branch_top); strbuf_release(&key); strbuf_release(&value); - junk_pid = 0; + junk_mode = JUNK_LEAVE_ALL; return err; } diff --git a/builtin/column.c b/builtin/column.c index 5ea798a7ca..e125a55fc9 100644 --- a/builtin/column.c +++ b/builtin/column.c @@ -6,7 +6,7 @@ #include "column.h" static const char * const builtin_column_usage[] = { - "git column [options]", + N_("git column [options]"), NULL }; static unsigned int colopts; @@ -23,13 +23,13 @@ int cmd_column(int argc, const char **argv, const char *prefix) struct column_options copts; const char *command = NULL, *real_command = NULL; struct option options[] = { - OPT_STRING(0, "command", &real_command, "name", "lookup config vars"), - OPT_COLUMN(0, "mode", &colopts, "layout to use"), - OPT_INTEGER(0, "raw-mode", &colopts, "layout to use"), - OPT_INTEGER(0, "width", &copts.width, "Maximum width"), - OPT_STRING(0, "indent", &copts.indent, "string", "Padding space on left border"), - OPT_INTEGER(0, "nl", &copts.nl, "Padding space on right border"), - OPT_INTEGER(0, "padding", &copts.padding, "Padding space between columns"), + OPT_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() }; diff --git a/builtin/commit-tree.c b/builtin/commit-tree.c index a0df12ce16..f641ff2a89 100644 --- a/builtin/commit-tree.c +++ b/builtin/commit-tree.c @@ -10,7 +10,7 @@ #include "utf8.h" #include "gpg-interface.h" -static const char commit_tree_usage[] = "git commit-tree [(-p <sha1>)...] [-S<signer>] [-m <message>] [-F <file>] <sha1> <changelog"; +static const char commit_tree_usage[] = "git commit-tree [(-p <sha1>)...] [-S[<keyid>]] [-m <message>] [-F <file>] <sha1> <changelog"; static void new_parent(struct commit *parent, struct commit_list **parents_p) { @@ -54,7 +54,7 @@ int cmd_commit_tree(int argc, const char **argv, const char *prefix) unsigned char sha1[20]; if (argc <= ++i) usage(commit_tree_usage); - if (get_sha1(argv[i], sha1)) + 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); @@ -101,7 +101,7 @@ int cmd_commit_tree(int argc, const char **argv, const char *prefix) continue; } - if (get_sha1(arg, tree_sha1)) + 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"); diff --git a/builtin/commit.c b/builtin/commit.c index 62028e7b44..65cf2a79b7 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -28,14 +28,16 @@ #include "submodule.h" #include "gpg-interface.h" #include "column.h" +#include "sequencer.h" +#include "notes-utils.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 }; @@ -102,28 +104,35 @@ static enum { CLEANUP_NONE, CLEANUP_ALL } cleanup_mode; -static char *cleanup_arg; +static const char *cleanup_arg; static enum commit_whence whence; static int use_editor = 1, include_status = 1; -static int show_ignored_in_status; +static int show_ignored_in_status, have_option_m; static const char *only_include_assumed; static struct strbuf message = STRBUF_INIT; -static enum { +static enum status_format { + STATUS_FORMAT_NONE = 0, STATUS_FORMAT_LONG, STATUS_FORMAT_SHORT, - STATUS_FORMAT_PORCELAIN -} status_format = STATUS_FORMAT_LONG; + STATUS_FORMAT_PORCELAIN, + + STATUS_FORMAT_UNSPECIFIED +} status_format = STATUS_FORMAT_UNSPECIFIED; static int opt_parse_m(const struct option *opt, const char *arg, int unset) { struct strbuf *buf = opt->value; - if (unset) + if (unset) { + have_option_m = 0; strbuf_setlen(buf, 0); - else { + } else { + have_option_m = 1; + if (buf->len) + strbuf_addch(buf, '\n'); strbuf_addstr(buf, arg); - strbuf_addstr(buf, "\n\n"); + strbuf_complete_line(buf); } return 0; } @@ -198,7 +207,7 @@ static int list_paths(struct string_list *list, const char *with_tree, } for (i = 0; i < active_nr; i++) { - struct cache_entry *ce = active_cache[i]; + const struct cache_entry *ce = active_cache[i]; struct string_list_item *item; if (ce->ce_flags & CE_UPDATE) @@ -453,6 +462,10 @@ static int run_status(FILE *fp, const char *index_file, const char *prefix, int case STATUS_FORMAT_PORCELAIN: wt_porcelain_print(s); break; + case STATUS_FORMAT_UNSPECIFIED: + die("BUG: finalize_deferred_config() should have been called"); + break; + case STATUS_FORMAT_NONE: case STATUS_FORMAT_LONG: wt_status_print(s); break; @@ -466,8 +479,6 @@ static int is_a_merge(const struct commit *current_head) return !!(current_head->parents && current_head->parents->next); } -static const char sign_off_header[] = "Signed-off-by: "; - static void export_one(const char *var, const char *s, const char *e, int hack) { struct strbuf buf = STRBUF_INIT; @@ -552,47 +563,6 @@ static void determine_author_info(struct strbuf *author_ident) } } -static int ends_rfc2822_footer(struct strbuf *sb) -{ - int ch; - int hit = 0; - int i, j, k; - int len = sb->len; - int first = 1; - const char *buf = sb->buf; - - for (i = len - 1; i > 0; i--) { - if (hit && buf[i] == '\n') - break; - hit = (buf[i] == '\n'); - } - - while (i < len - 1 && buf[i] == '\n') - i++; - - for (; i < len; i = k) { - for (k = i; k < len && buf[k] != '\n'; k++) - ; /* do nothing */ - k++; - - if ((buf[k] == ' ' || buf[k] == '\t') && !first) - continue; - - first = 0; - - for (j = 0; i + j < len; j++) { - ch = buf[i + j]; - if (ch == ':') - break; - if (isalnum(ch) || - (ch == '-')) - continue; - return 0; - } - } - return 1; -} - static char *cut_ident_timestamp_part(char *string) { char *ket = strrchr(string, '>'); @@ -717,21 +687,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, 0); } if (fwrite(sb.buf, 1, sb.len, s->fp) < sb.len) @@ -764,15 +743,15 @@ static int prepare_to_commit(const char *index_file, const char *prefix, if (cleanup_mode == CLEANUP_ALL) 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")); + " Lines starting\nwith '%c' will be ignored, and an empty" + " message aborts the commit.\n"), comment_line_char); else /* CLEANUP_SPACE, that is. */ 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")); + " Lines starting\n" + "with '%c' will be kept; you may remove them" + " yourself if you want to.\n" + "An empty message aborts the commit.\n"), comment_line_char); if (only_include_assumed) status_printf_ln(s, GIT_COLOR_NORMAL, "%s", only_include_assumed); @@ -786,7 +765,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"), @@ -977,24 +956,50 @@ static void handle_untracked_files_arg(struct wt_status *s) static const char *read_commit_message(const char *name) { - const char *out_enc, *out; + const char *out_enc; struct commit *commit; commit = lookup_commit_reference_by_name(name); if (!commit) die(_("could not lookup commit %s"), name); out_enc = get_commit_output_encoding(); - out = logmsg_reencode(commit, out_enc); + return logmsg_reencode(commit, NULL, out_enc); +} - /* - * If we failed to reencode the buffer, just copy it - * byte for byte so the user can try to fix it up. - * This also handles the case where input and output - * encodings are identical. - */ - if (out == NULL) - out = xstrdup(commit->buffer); - return out; +/* + * Enumerate what needs to be propagated when --porcelain + * is not in effect here. + */ +static struct status_deferred_config { + enum status_format status_format; + int show_branch; +} status_deferred_config = { + STATUS_FORMAT_UNSPECIFIED, + -1 /* unspecified */ +}; + +static void finalize_deferred_config(struct wt_status *s) +{ + int use_deferred_config = (status_format != STATUS_FORMAT_PORCELAIN && + !s->null_termination); + + if (s->null_termination) { + if (status_format == STATUS_FORMAT_NONE || + status_format == STATUS_FORMAT_UNSPECIFIED) + status_format = STATUS_FORMAT_PORCELAIN; + else if (status_format == STATUS_FORMAT_LONG) + die(_("--long and -z are incompatible")); + } + + if (use_deferred_config && status_format == STATUS_FORMAT_UNSPECIFIED) + status_format = status_deferred_config.status_format; + if (status_format == STATUS_FORMAT_UNSPECIFIED) + status_format = STATUS_FORMAT_NONE; + + if (use_deferred_config && s->show_branch < 0) + s->show_branch = status_deferred_config.show_branch; + if (s->show_branch < 0) + s->show_branch = 0; } static int parse_and_validate_options(int argc, const char *argv[], @@ -1007,6 +1012,7 @@ static int parse_and_validate_options(int argc, const char *argv[], int f = 0; argc = parse_options(argc, argv, prefix, options, usage, 0); + finalize_deferred_config(s); if (force_author && !strchr(force_author, '>')) force_author = find_author_by_nickname(force_author); @@ -1014,7 +1020,7 @@ static int parse_and_validate_options(int argc, const char *argv[], if (force_author && renew_authorship) die(_("Using both --reset-author and --author does not make sense")); - if (logfile || message.len || use_message || fixup_message) + if (logfile || have_option_m || use_message || fixup_message) use_editor = 0; if (0 <= edit_flag) use_editor = edit_flag; @@ -1091,9 +1097,7 @@ static int parse_and_validate_options(int argc, const char *argv[], if (all && argc > 0) die(_("Paths with -a does not make sense.")); - if (s->null_termination && status_format == STATUS_FORMAT_LONG) - status_format = STATUS_FORMAT_PORCELAIN; - if (status_format != STATUS_FORMAT_LONG) + if (status_format != STATUS_FORMAT_NONE) dry_run = 1; return argc; @@ -1145,6 +1149,17 @@ static int git_status_config(const char *k, const char *v, void *cb) s->submodule_summary = -1; return 0; } + if (!strcmp(k, "status.short")) { + if (git_config_bool(k, v)) + status_deferred_config.status_format = STATUS_FORMAT_SHORT; + else + status_deferred_config.status_format = STATUS_FORMAT_NONE; + return 0; + } + if (!strcmp(k, "status.branch")) { + status_deferred_config.show_branch = git_config_bool(k, v); + return 0; + } if (!strcmp(k, "status.color") || !strcmp(k, "color.status")) { s->use_color = git_config_colorbool(k, v); return 0; @@ -1184,26 +1199,29 @@ int cmd_status(int argc, const char **argv, const char *prefix) 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", &s.show_branch, - "show branch information"), + N_("show status concisely"), STATUS_FORMAT_SHORT), + OPT_BOOL('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_SET_INT(0, "long", &status_format, + N_("show status in long format (default)"), + STATUS_FORMAT_LONG), OPT_BOOLEAN('z', "null", &s.null_termination, - "terminate entries with NUL"), + 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, "list untracked files in columns"), + OPT_COLUMN(0, "column", &s.colopts, N_("list untracked files in columns")), OPT_END(), }; @@ -1218,9 +1236,7 @@ int cmd_status(int argc, const char **argv, const char *prefix) builtin_status_options, builtin_status_usage, 0); finalize_colopts(&s.colopts, -1); - - if (s.null_termination && status_format == STATUS_FORMAT_LONG) - status_format = STATUS_FORMAT_PORCELAIN; + finalize_deferred_config(&s); handle_untracked_files_arg(&s); if (show_ignored_in_status) @@ -1249,6 +1265,10 @@ int cmd_status(int argc, const char **argv, const char *prefix) case STATUS_FORMAT_PORCELAIN: wt_porcelain_print(&s); break; + case STATUS_FORMAT_UNSPECIFIED: + die("BUG: finalize_deferred_config() should have been called"); + break; + case STATUS_FORMAT_NONE: case STATUS_FORMAT_LONG: s.verbose = verbose; s.ignore_submodule_arg = ignore_submodule_arg; @@ -1284,7 +1304,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) { @@ -1339,6 +1359,8 @@ static int git_commit_config(const char *k, const char *v, void *cb) include_status = git_config_bool(k, v); return 0; } + if (!strcmp(k, "commit.cleanup")) + return git_config_string(&cleanup_arg, k, v); status = git_gpg_config(k, v, NULL); if (status) @@ -1346,8 +1368,6 @@ static int git_commit_config(const char *k, const char *v, void *cb) return git_status_config(k, v, s); } -static const char post_rewrite_hook[] = "hooks/post-rewrite"; - static int run_rewrite_hook(const unsigned char *oldsha1, const unsigned char *newsha1) { @@ -1358,10 +1378,10 @@ static int run_rewrite_hook(const unsigned char *oldsha1, int code; size_t n; - if (access(git_path(post_rewrite_hook), X_OK) < 0) + argv[0] = find_hook("post-rewrite"); + if (!argv[0]) return 0; - argv[0] = git_path(post_rewrite_hook); argv[1] = "amend"; argv[2] = NULL; @@ -1384,53 +1404,56 @@ int cmd_commit(int argc, const char **argv, const char *prefix) { static struct wt_status s; static struct option builtin_commit_options[] = { - OPT__QUIET(&quiet, "suppress summary after successful commit"), - OPT__VERBOSE(&verbose, "show diff in commit message template"), - - OPT_GROUP("Commit message options"), - OPT_FILENAME('F', "file", &logfile, "read message from file"), - OPT_STRING(0, "author", &force_author, "author", "override author for commit"), - OPT_STRING(0, "date", &force_date, "date", "override date for commit"), - OPT_CALLBACK('m', "message", &message, "message", "commit message", opt_parse_m), - OPT_STRING('c', "reedit-message", &edit_message, "commit", "reuse and edit message from specified commit"), - OPT_STRING('C', "reuse-message", &use_message, "commit", "reuse message from specified commit"), - OPT_STRING(0, "fixup", &fixup_message, "commit", "use autosquash formatted message to fixup specified commit"), - OPT_STRING(0, "squash", &squash_message, "commit", "use autosquash formatted message to squash specified commit"), - OPT_BOOLEAN(0, "reset-author", &renew_authorship, "the commit is authored by me now (used with -C/-c/--amend)"), - OPT_BOOLEAN('s', "signoff", &signoff, "add Signed-off-by:"), - OPT_FILENAME('t', "template", &template_file, "use specified template file"), - OPT_BOOL('e', "edit", &edit_flag, "force edit of commit"), - OPT_STRING(0, "cleanup", &cleanup_arg, "default", "how to strip spaces and #comments from message"), - OPT_BOOLEAN(0, "status", &include_status, "include status in commit message template"), - { OPTION_STRING, 'S', "gpg-sign", &sign_commit, "key id", - "GPG sign commit", PARSE_OPT_OPTARG, NULL, (intptr_t) "" }, + 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("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", + 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, "show branch information"), + OPT_BOOL(0, "branch", &s.show_branch, N_("show branch information")), OPT_SET_INT(0, "porcelain", &status_format, - "machine-readable output", STATUS_FORMAT_PORCELAIN), + 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, - "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" }, + 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, - "ok to record an empty change", + N_("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", + N_("ok to record a change with an empty message"), PARSE_OPT_NOARG | PARSE_OPT_HIDDEN }, OPT_END() @@ -1452,7 +1475,9 @@ int cmd_commit(int argc, const char **argv, const char *prefix) usage_with_options(builtin_commit_usage, builtin_commit_options); wt_status_prepare(&s); + gitmodules_config(); git_config(git_commit_config, &s); + status_format = STATUS_FORMAT_NONE; /* Ignore status.short */ determine_whence(&s); s.colopts = 0; @@ -1614,7 +1639,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix) if (cfg) { /* 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); + finish_copy_notes_for_rewrite(cfg, "Notes added by 'git commit --amend'"); } run_rewrite_hook(current_head->object.sha1, sha1); } diff --git a/builtin/config.c b/builtin/config.c index b44277c23e..7759671eb8 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'; @@ -49,33 +48,33 @@ static int respect_includes = -1; #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_BOOL(0, "includes", &respect_includes, "respect include directives on lookup"), + 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(), }; @@ -95,12 +94,19 @@ 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)) @@ -111,12 +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) { - 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) @@ -129,7 +137,8 @@ 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_; @@ -138,16 +147,12 @@ static int show_config(const char *key_, const char *value_, void *cb) vptr = ""; must_print_delim = 0; } - seen++; - if (dup_error) { - error("More than one value for the key %s: %s", - key_, vptr); - } - else { - if (must_print_delim) - printf("%c", key_delim); - 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 @@ -161,21 +166,8 @@ static int show_config(const char *key_, const char *value_, void *cb) static int get_value(const char *key_, const char *regex_) { int ret = CONFIG_GENERIC_ERROR; - char *global = NULL, *repo_config = NULL; - const char *system_wide = NULL, *local; - struct config_include_data inc = CONFIG_INCLUDE_INIT; - config_fn_t fn; - void *data; - - local = given_config_file; - 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(); - } + struct strbuf_list values = {NULL}; + int i; if (use_key_regexp) { char *tl; @@ -197,7 +189,8 @@ 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; } @@ -217,48 +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; } } - fn = show_config; - data = NULL; - if (respect_includes) { - inc.fn = fn; - inc.data = data; - fn = git_config_include; - data = &inc; - } - - if (do_all && system_wide) - git_config_from_file(fn, system_wide, data); - if (do_all && global) - git_config_from_file(fn, global, data); - if (do_all) - git_config_from_file(fn, local, data); - git_config_from_parameters(fn, data); - if (!do_all && !seen) - git_config_from_file(fn, local, data); - if (!do_all && !seen && global) - git_config_from_file(fn, global, data); - if (!do_all && !seen && system_wide) - git_config_from_file(fn, system_wide, data); + 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; } @@ -347,6 +329,7 @@ static int get_colorbool(int print) { get_colorbool_found = -1; get_diff_color_found = -1; + get_color_ui_found = -1; git_config_with_options(git_get_colorbool_config, NULL, given_config_file, respect_includes); @@ -357,6 +340,10 @@ static int get_colorbool(int print) get_colorbool_found = get_color_ui_found; } + if (get_colorbool_found < 0) + /* default value if none found in config */ + get_colorbool_found = GIT_COLOR_AUTO; + get_colorbool_found = want_color(get_colorbool_found); if (print) { @@ -383,13 +370,25 @@ 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)); - given_config_file = 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, 0) && + xdg_config && !access_or_warn(xdg_config, R_OK, 0)) + given_config_file = xdg_config; + else + given_config_file = user_config; } else if (use_system_config) given_config_file = git_etc_gitconfig(); diff --git a/builtin/count-objects.c b/builtin/count-objects.c index c37cb98c31..a7f70cb858 100644 --- a/builtin/count-objects.c +++ b/builtin/count-objects.c @@ -9,11 +9,22 @@ #include "builtin.h" #include "parse-options.h" +static unsigned long garbage; +static off_t size_garbage; + +static void real_report_garbage(const char *desc, const char *path) +{ + struct stat st; + if (!stat(path, &st)) + size_garbage += st.st_size; + warning("%s: %s", desc, path); + garbage++; +} + static void count_objects(DIR *d, char *path, int len, int verbose, unsigned long *loose, off_t *loose_size, - unsigned long *packed_loose, - unsigned long *garbage) + unsigned long *packed_loose) { struct dirent *ent; while ((ent = readdir(d)) != NULL) { @@ -46,9 +57,11 @@ static void count_objects(DIR *d, char *path, int len, int verbose, } if (bad) { if (verbose) { - error("garbage found: %.*s/%s", - len + 2, path, ent->d_name); - (*garbage)++; + struct strbuf sb = STRBUF_INIT; + strbuf_addf(&sb, "%.*s/%s", + len + 2, path, ent->d_name); + report_garbage("garbage found", sb.buf); + strbuf_release(&sb); } continue; } @@ -66,20 +79,22 @@ 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] [-H | --human-readable]"), NULL }; int cmd_count_objects(int argc, const char **argv, const char *prefix) { - int i, verbose = 0; + int i, verbose = 0, human_readable = 0; const char *objdir = get_object_directory(); int len = strlen(objdir); char *path = xmalloc(len + 50); - unsigned long loose = 0, packed = 0, packed_loose = 0, garbage = 0; + unsigned long loose = 0, packed = 0, packed_loose = 0; off_t loose_size = 0; struct option opts[] = { - OPT__VERBOSE(&verbose, "be verbose"), + OPT__VERBOSE(&verbose, N_("be verbose")), + OPT_BOOL('H', "human-readable", &human_readable, + N_("print sizes in human readable format")), OPT_END(), }; @@ -87,6 +102,8 @@ int cmd_count_objects(int argc, const char **argv, const char *prefix) /* we do not take arguments other than flags for now */ if (argc) usage_with_options(count_objects_usage, opts); + if (verbose) + report_garbage = real_report_garbage; memcpy(path, objdir, len); if (len && objdir[len-1] != '/') path[len++] = '/'; @@ -97,13 +114,16 @@ int cmd_count_objects(int argc, const char **argv, const char *prefix) if (!d) continue; count_objects(d, path, len, verbose, - &loose, &loose_size, &packed_loose, &garbage); + &loose, &loose_size, &packed_loose); closedir(d); } if (verbose) { struct packed_git *p; unsigned long num_pack = 0; off_t size_pack = 0; + struct strbuf loose_buf = STRBUF_INIT; + struct strbuf pack_buf = STRBUF_INIT; + struct strbuf garbage_buf = STRBUF_INIT; if (!packed_git) prepare_packed_git(); for (p = packed_git; p; p = p->next) { @@ -115,16 +135,40 @@ int cmd_count_objects(int argc, const char **argv, const char *prefix) size_pack += p->pack_size + p->index_size; num_pack++; } + + if (human_readable) { + strbuf_humanise_bytes(&loose_buf, loose_size); + strbuf_humanise_bytes(&pack_buf, size_pack); + strbuf_humanise_bytes(&garbage_buf, size_garbage); + } else { + strbuf_addf(&loose_buf, "%lu", + (unsigned long)(loose_size / 1024)); + strbuf_addf(&pack_buf, "%lu", + (unsigned long)(size_pack / 1024)); + strbuf_addf(&garbage_buf, "%lu", + (unsigned long)(size_garbage / 1024)); + } + printf("count: %lu\n", loose); - printf("size: %lu\n", (unsigned long) (loose_size / 1024)); + printf("size: %s\n", loose_buf.buf); printf("in-pack: %lu\n", packed); printf("packs: %lu\n", num_pack); - printf("size-pack: %lu\n", (unsigned long) (size_pack / 1024)); + printf("size-pack: %s\n", pack_buf.buf); printf("prune-packable: %lu\n", packed_loose); printf("garbage: %lu\n", garbage); + printf("size-garbage: %s\n", garbage_buf.buf); + strbuf_release(&loose_buf); + strbuf_release(&pack_buf); + strbuf_release(&garbage_buf); + } else { + struct strbuf buf = STRBUF_INIT; + if (human_readable) + strbuf_humanise_bytes(&buf, loose_size); + else + strbuf_addf(&buf, "%lu kilobytes", + (unsigned long)(loose_size / 1024)); + printf("%lu objects, %s\n", loose, buf.buf); + strbuf_release(&buf); } - else - printf("%lu objects, %lu kilobytes\n", - loose, (unsigned long) (loose_size / 1024)); return 0; } 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..4e675c3d0d 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 }; @@ -21,6 +21,7 @@ static int debug; /* Display lots of verbose info */ static int all; /* Any valid ref can be used */ static int tags; /* Allow lightweight tags */ static int longformat; +static int first_parent; static int abbrev = -1; /* unspecified */ static int max_candidates = 10; static struct hash_table names; @@ -42,7 +43,7 @@ struct commit_name { unsigned prio:2; /* annotated tag = 2, tag = 1, head = 0 */ unsigned name_checked:1; unsigned char sha1[20]; - const char *path; + char *path; }; static const char *prio_names[] = { "head", "lightweight", "annotated", @@ -126,51 +127,52 @@ static void add_to_known_names(const char *path, } else { e->next = NULL; } + e->path = NULL; } e->tag = tag; e->prio = prio; e->name_checked = 0; hashcpy(e->sha1, sha1); - e->path = path; + free(e->path); + e->path = xstrdup(path); } } static int get_name(const char *path, const unsigned char *sha1, int flag, void *cb_data) { - int might_be_tag = !prefixcmp(path, "refs/tags/"); + int is_tag = !prefixcmp(path, "refs/tags/"); unsigned char peeled[20]; - int is_tag, prio; + int is_annotated, prio; - if (!all && !might_be_tag) + /* Reject anything outside refs/tags/ unless --all */ + if (!all && !is_tag) return 0; - if (!peel_ref(path, peeled) && !is_null_sha1(peeled)) { - is_tag = !!hashcmp(sha1, peeled); + /* Accept only tags that match the pattern, if given */ + if (pattern && (!is_tag || fnmatch(pattern, path + 10, 0))) + return 0; + + /* Is it annotated? */ + if (!peel_ref(path, peeled)) { + is_annotated = !!hashcmp(sha1, peeled); } else { hashcpy(peeled, sha1); - is_tag = 0; + is_annotated = 0; } - /* If --all, then any refs are used. - * If --tags, then any tags are used. - * Otherwise only annotated tags are used. + /* + * By default, we only use annotated tags, but with --tags + * we fall back to lightweight ones (even without --tags, + * we still remember lightweight ones, only to give hints + * in an error message). --all allows any refs to be used. */ - if (might_be_tag) { - if (is_tag) - prio = 2; - else - prio = 1; - - if (pattern && fnmatch(pattern, path + 10, 0)) - prio = 0; - } + if (is_annotated) + prio = 2; + else if (is_tag) + prio = 1; else prio = 0; - if (!all) { - if (!prio) - return 0; - } add_to_known_names(all ? path + 5 : path + 10, peeled, prio, sha1); return 0; } @@ -337,6 +339,9 @@ static void describe(const char *arg, int last_one) commit_list_insert_by_date(p, &list); p->object.flags |= c->object.flags; parents = parents->next; + + if (first_parent) + break; } } @@ -400,22 +405,23 @@ 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")), + OPT_BOOLEAN(0, "tags", &tags, N_("use any tag, even unannotated")), + OPT_BOOLEAN(0, "long", &longformat, N_("always use long format")), + OPT_BOOLEAN(0, "first-parent", &first_parent, N_("only follow first parent")), 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 9650be2c50..9fc273d8cd 100644 --- a/builtin/diff.c +++ b/builtin/diff.c @@ -130,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. @@ -140,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); @@ -149,7 +153,8 @@ static int builtin_diff_index(struct rev_info *revs, static int builtin_diff_tree(struct rev_info *revs, int argc, const char **argv, - struct object_array_entry *ent) + struct object_array_entry *ent0, + struct object_array_entry *ent1) { const unsigned char *(sha1[2]); int swap = 0; @@ -157,13 +162,14 @@ static int builtin_diff_tree(struct rev_info *revs, if (argc > 1) usage(builtin_diff_usage); - /* We saw two trees, ent[0] and ent[1]. - * if ent[1] is uninteresting, they are swapped + /* + * We saw two trees, ent0 and ent1. If ent1 is uninteresting, + * swap them. */ - if (ent[1].item->flags & UNINTERESTING) + if (ent1->item->flags & UNINTERESTING) swap = 1; - sha1[swap] = ent[0].item->sha1; - sha1[1-swap] = ent[1].item->sha1; + sha1[swap] = ent0->item->sha1; + sha1[1-swap] = ent1->item->sha1; diff_tree_sha1(sha1[0], sha1[1], "", &revs->diffopt); log_tree_diff_flush(revs); return 0; @@ -247,8 +253,8 @@ int cmd_diff(int argc, const char **argv, const char *prefix) { int i; struct rev_info rev; - struct object_array_entry ent[100]; - int ents = 0, blobs = 0, paths = 0; + struct object_array ent = OBJECT_ARRAY_INIT; + int blobs = 0, paths = 0; const char *path = NULL; struct blobinfo blob[2]; int nongit; @@ -333,9 +339,9 @@ int cmd_diff(int argc, const char **argv, const char *prefix) } for (i = 0; i < rev.pending.nr; i++) { - struct object_array_entry *list = rev.pending.objects+i; - struct object *obj = list->item; - const char *name = list->name; + struct object_array_entry *entry = &rev.pending.objects[i]; + struct object *obj = entry->item; + const char *name = entry->name; int flags = (obj->flags & UNINTERESTING); if (!obj->parsed) obj = parse_object(obj->sha1); @@ -344,27 +350,21 @@ int cmd_diff(int argc, const char **argv, const char *prefix) die(_("invalid object '%s' given."), name); if (obj->type == OBJ_COMMIT) obj = &((struct commit *)obj)->tree->object; + if (obj->type == OBJ_TREE) { - if (ARRAY_SIZE(ent) <= ents) - die(_("more than %d trees given: '%s'"), - (int) ARRAY_SIZE(ent), name); obj->flags |= flags; - ent[ents].item = obj; - ent[ents].name = name; - ents++; - continue; - } - if (obj->type == OBJ_BLOB) { + add_object_array(obj, name, &ent); + } else if (obj->type == OBJ_BLOB) { if (2 <= blobs) die(_("more than two blobs given: '%s'"), name); hashcpy(blob[blobs].sha1, obj->sha1); blob[blobs].name = name; - blob[blobs].mode = list->mode; + blob[blobs].mode = entry->mode; blobs++; - continue; + } else { + die(_("unhandled object '%s' given."), name); } - die(_("unhandled object '%s' given."), name); } if (rev.prune_data.nr) { if (!path) @@ -375,7 +375,7 @@ int cmd_diff(int argc, const char **argv, const char *prefix) /* * Now, do the arguments look reasonable? */ - if (!ents) { + if (!ent.nr) { switch (blobs) { case 0: result = builtin_diff_files(&rev, argc, argv); @@ -396,41 +396,28 @@ int cmd_diff(int argc, const char **argv, const char *prefix) } else if (blobs) usage(builtin_diff_usage); - else if (ents == 1) + else if (ent.nr == 1) result = builtin_diff_index(&rev, argc, argv); - else if (ents == 2) - result = builtin_diff_tree(&rev, argc, argv, ent); - else if (ent[0].item->flags & UNINTERESTING) { + else if (ent.nr == 2) + result = builtin_diff_tree(&rev, argc, argv, + &ent.objects[0], &ent.objects[1]); + else if (ent.objects[0].item->flags & UNINTERESTING) { /* * diff A...B where there is at least one merge base - * between A and B. We have ent[0] == merge-base, - * ent[ents-2] == A, and ent[ents-1] == B. Show diff - * between the base and B. Note that we pick one - * merge base at random if there are more than one. + * between A and B. We have ent.objects[0] == + * merge-base, ent.objects[ents-2] == A, and + * ent.objects[ents-1] == B. Show diff between the + * base and B. Note that we pick one merge base at + * random if there are more than one. */ - ent[1] = ent[ents-1]; - result = builtin_diff_tree(&rev, argc, argv, ent); + result = builtin_diff_tree(&rev, argc, argv, + &ent.objects[0], + &ent.objects[ent.nr-1]); } else result = builtin_diff_combined(&rev, argc, argv, - ent, ents); + ent.objects, ent.nr); result = diff_result_code(&rev.diffopt, result); if (1 < rev.diffopt.skip_stat_unmatch) refresh_index_quietly(); return result; } - -void setup_diff_pager(struct diff_options *opt) -{ - /* - * If the user asked for our exit code, then either they want --quiet - * or --exit-code. We should definitely not bother with a pager in the - * former case, as we will generate no output. Since we still properly - * report our exit code even when a pager is run, we _could_ run a - * pager with --exit-code. But since we have not done so historically, - * and because it is easy to find people oneline advising "git diff - * --exit-code" in hooks and other scripts, we do not do so. - */ - if (!DIFF_OPT_TST(opt, EXIT_WITH_STATUS) && - check_pager_config("diff") != 0) - setup_pager(); -} diff --git a/builtin/fast-export.c b/builtin/fast-export.c index 9ab6db3fb0..d1d68e9fc6 100644 --- a/builtin/fast-export.c +++ b/builtin/fast-export.c @@ -19,12 +19,12 @@ #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 { ABORT, VERBATIM, WARN, WARN_STRIP, STRIP } signed_tag_mode = ABORT; static enum { ERROR, DROP, REWRITE } tag_of_filtered_mode = ERROR; static int fake_missing_tagger; static int use_done_feature; @@ -40,10 +40,12 @@ static int parse_opt_signed_tag_mode(const struct option *opt, signed_tag_mode = VERBATIM; else if (!strcmp(arg, "warn")) signed_tag_mode = WARN; + else if (!strcmp(arg, "warn-strip")) + signed_tag_mode = WARN_STRIP; else if (!strcmp(arg, "strip")) signed_tag_mode = STRIP; else - return error("Unknown signed-tag mode: %s", arg); + return error("Unknown signed-tags mode: %s", arg); return 0; } @@ -113,12 +115,13 @@ static void show_progress(void) printf("progress %d objects\n", counter); } -static void handle_object(const unsigned char *sha1) +static void export_blob(const unsigned char *sha1) { unsigned long size; enum object_type type; char *buf; struct object *object; + int eaten; if (no_data) return; @@ -126,16 +129,18 @@ static void handle_object(const unsigned char *sha1) if (is_null_sha1(sha1)) return; - object = parse_object(sha1); - if (!object) - die ("Could not read blob %s", sha1_to_hex(sha1)); - - if (object->flags & SHOWN) + object = lookup_object(sha1); + if (object && object->flags & SHOWN) return; buf = read_sha1_file(sha1, &type, &size); if (!buf) die ("Could not read blob %s", sha1_to_hex(sha1)); + if (check_sha1_signature(sha1, buf, size, typename(type)) < 0) + die("sha1 mismatch in blob %s", sha1_to_hex(sha1)); + object = parse_object_buffer(sha1, type, size, buf, &eaten); + if (!object) + die("Could not read blob %s", sha1_to_hex(sha1)); mark_next_object(object); @@ -147,7 +152,8 @@ static void handle_object(const unsigned char *sha1) show_progress(); object->flags |= SHOWN; - free(buf); + if (!eaten) + free(buf); } static int depth_first(const void *a_, const void *b_) @@ -312,7 +318,7 @@ static void handle_commit(struct commit *commit, struct rev_info *rev) /* Export the referenced blobs, and remember the marks. */ for (i = 0; i < diff_queued_diff.nr; i++) if (!S_ISGITLINK(diff_queued_diff.queue[i]->two->mode)) - handle_object(diff_queued_diff.queue[i]->two->sha1); + export_blob(diff_queued_diff.queue[i]->two->sha1); mark_next_object(&commit->object); if (!is_encoding_utf8(encoding)) @@ -416,7 +422,7 @@ static void handle_tag(const char *name, struct tag *tag) switch(signed_tag_mode) { case ABORT: die ("Encountered signed tag %s; use " - "--signed-tag=<mode> to handle it.", + "--signed-tags=<mode> to handle it.", sha1_to_hex(tag->object.sha1)); case WARN: warning ("Exporting signed tag %s", @@ -424,6 +430,10 @@ static void handle_tag(const char *name, struct tag *tag) /* fallthru */ case VERBATIM: break; + case WARN_STRIP: + warning ("Stripping signature from tag %s", + sha1_to_hex(tag->object.sha1)); + /* fallthru */ case STRIP: message_size = signature + 1 - message; break; @@ -474,18 +484,21 @@ static void handle_tag(const char *name, struct tag *tag) (int)message_size, (int)message_size, message ? message : ""); } -static void get_tags_and_duplicates(struct object_array *pending, +static void get_tags_and_duplicates(struct rev_cmdline_info *info, struct string_list *extra_refs) { struct tag *tag; int i; - for (i = 0; i < pending->nr; i++) { - struct object_array_entry *e = pending->objects + i; + for (i = 0; i < info->nr; i++) { + struct rev_cmdline_entry *e = info->rev + i; unsigned char sha1[20]; - struct commit *commit = commit; + struct commit *commit; char *full_name; + if (e->flags & UNINTERESTING) + continue; + if (dwim_ref(e->name, strlen(e->name), sha1, &full_name) != 1) continue; @@ -509,7 +522,7 @@ static void get_tags_and_duplicates(struct object_array *pending, commit = (struct commit *)tag; break; case OBJ_BLOB: - handle_object(tag->object.sha1); + export_blob(tag->object.sha1); continue; default: /* OBJ_TAG (nested tags) is already handled */ warning("Tag points to object of unexpected type %s, skipping.", @@ -523,10 +536,14 @@ static void get_tags_and_duplicates(struct object_array *pending, typename(e->item->type)); continue; } - if (commit->util) - /* more than one name for the same object */ + + /* + * This ref will not be updated through a commit, lets make + * sure it gets properly updated eventually. + */ + if (commit->util || commit->object.flags & SHOWN) string_list_append(extra_refs, full_name)->util = commit; - else + if (!commit->util) commit->util = full_name; } } @@ -596,6 +613,8 @@ static void import_marks(char *input_file) char *line_end, *mark_end; unsigned char sha1[20]; struct object *object; + struct commit *commit; + enum object_type type; line_end = strchr(line, '\n'); if (line[0] != ':' || !line_end) @@ -604,19 +623,30 @@ static void import_marks(char *input_file) mark = strtoumax(line + 1, &mark_end, 10); if (!mark || mark_end == line + 1 - || *mark_end != ' ' || get_sha1(mark_end + 1, sha1)) + || *mark_end != ' ' || get_sha1_hex(mark_end + 1, sha1)) die("corrupt mark line: %s", line); - object = parse_object(sha1); - if (!object) - die ("Could not read blob %s", sha1_to_hex(sha1)); + if (last_idnum < mark) + last_idnum = mark; + + type = sha1_object_info(sha1, NULL); + if (type < 0) + die("object not found: %s", sha1_to_hex(sha1)); + + if (type != OBJ_COMMIT) + /* only commits */ + continue; + + commit = lookup_commit(sha1); + if (!commit) + die("not a commit? can't happen: %s", sha1_to_hex(sha1)); + + object = &commit->object; if (object->flags & SHOWN) error("Object %s already has a mark", sha1_to_hex(sha1)); mark_object(object, mark); - if (last_idnum < mark) - last_idnum = mark; object->flags |= SHOWN; } @@ -630,26 +660,27 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix) struct string_list extra_refs = STRING_LIST_INIT_NODUP; struct commit *commit; char *export_filename = NULL, *import_filename = NULL; + uint32_t lastimportid; 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"), + N_("Output full tree for each commit")), OPT_BOOLEAN(0, "use-done-feature", &use_done_feature, - "Use the done feature to terminate the stream"), - OPT_BOOL(0, "no-data", &no_data, "Skip output of blob data"), + N_("Use the done feature to terminate the stream")), + OPT_BOOL(0, "no-data", &no_data, N_("Skip output of blob data")), OPT_END() }; @@ -673,11 +704,12 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix) if (import_filename) import_marks(import_filename); + lastimportid = last_idnum; if (import_filename && revs.prune_data.nr) full_tree = 1; - get_tags_and_duplicates(&revs.pending, &extra_refs); + get_tags_and_duplicates(&revs.cmdline, &extra_refs); if (prepare_revision_walk(&revs)) die("revision walk setup failed"); @@ -695,7 +727,7 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix) handle_tags_and_duplicates(&extra_refs); - if (export_filename) + if (export_filename && lastimportid != last_idnum) export_marks(export_filename); if (use_done_feature) diff --git a/builtin/fetch-pack.c b/builtin/fetch-pack.c index 149db88726..aba4465552 100644 --- a/builtin/fetch-pack.c +++ b/builtin/fetch-pack.c @@ -1,900 +1,28 @@ #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; -static int fetch_fsck_objects = -1; -static int transfer_fsck_objects = -1; -static struct fetch_pack_args args = { - /* .uploadpack = */ "git-upload-pack", -}; static const char fetch_pack_usage[] = "git fetch-pack [--all] [--stdin] [--quiet|-q] [--keep|-k] [--thin] " "[--include-tag] [--upload-pack=<git-upload-pack>] [--depth=<n>] " "[--no-progress] [-v] [<host>:]<directory> [<refs>...]"; -#define COMPLETE (1U << 0) -#define COMMON (1U << 1) -#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 *refname, const unsigned char *sha1, int flag, void *cb_data) -{ - struct object *o = deref_tag(parse_object(sha1), refname, 0); - - if (o && o->type == OBJ_COMMIT) - rev_list_push((struct commit *)o, SEEN); - - return 0; -} - -static int clear_marks(const char *refname, const unsigned char *sha1, int flag, void *cb_data) -{ - struct object *o = deref_tag(parse_object(sha1), refname, 0); - - if (o && o->type == OBJ_COMMIT) - clear_commit_marks((struct commit *)o, - COMMON | COMMON_REF | SEEN | POPPED); - return 0; -} - -/* - This function marks a rev and its ancestors as common. - In some cases, it is desirable to mark only the ancestors (for example - when only the server does not yet know that they are common). -*/ - -static void mark_common(struct commit *commit, - int ancestors_only, int dont_parse) -{ - if (commit != NULL && !(commit->object.flags & COMMON)) { - struct object *o = (struct object *)commit; - - if (!ancestors_only) - o->flags |= COMMON; - - if (!(o->flags & SEEN)) - rev_list_push(commit, SEEN); - else { - struct commit_list *parents; - - if (!ancestors_only && !(o->flags & POPPED)) - non_common_revs--; - if (!o->parsed && !dont_parse) - if (parse_commit(commit)) - return; - - for (parents = commit->parents; - parents; - parents = parents->next) - mark_common(parents->item, 0, dont_parse); - } - } -} - -/* - Get the next rev to send, ignoring the common. -*/ - -static const unsigned char *get_rev(void) -{ - struct commit *commit = NULL; - - while (commit == NULL) { - unsigned int mark; - struct commit_list *parents; - - if (rev_list == NULL || non_common_revs == 0) - return NULL; - - commit = rev_list->item; - if (!commit->object.parsed) - parse_commit(commit); - parents = commit->parents; - - commit->object.flags |= POPPED; - if (!(commit->object.flags & COMMON)) - non_common_revs--; - - if (commit->object.flags & COMMON) { - /* do not send "have", and ignore ancestors */ - commit = NULL; - mark = COMMON | SEEN; - } else if (commit->object.flags & COMMON_REF) - /* send "have", and ignore ancestors */ - mark = COMMON | SEEN; - else - /* send "have", also for its ancestors */ - mark = SEEN; - - while (parents) { - if (!(parents->item->object.flags & SEEN)) - rev_list_push(parents->item, mark); - if (mark & COMMON) - mark_common(parents->item, 1, 0); - parents = parents->next; - } - - rev_list = rev_list->next; - } - - return commit->object.sha1; -} - -enum ack_type { - NAK = 0, - ACK, - ACK_continue, - ACK_common, - ACK_ready -}; - -static void consume_shallow_list(int fd) -{ - if (args.stateless_rpc && args.depth > 0) { - /* If we sent a depth we will get back "duplicate" - * shallow and unshallow commands every time there - * is a block of have lines exchanged. - */ - char line[1000]; - while (packet_read_line(fd, line, sizeof(line))) { - if (!prefixcmp(line, "shallow ")) - continue; - if (!prefixcmp(line, "unshallow ")) - continue; - die("git fetch-pack: expected shallow list"); - } - } -} - -struct write_shallow_data { - struct strbuf *out; - int use_pack_protocol; - int count; -}; - -static int write_one_shallow(const struct commit_graft *graft, void *cb_data) -{ - struct write_shallow_data *data = cb_data; - const char *hex = sha1_to_hex(graft->sha1); - data->count++; - if (data->use_pack_protocol) - packet_buf_write(data->out, "shallow %s", hex); - else { - strbuf_addstr(data->out, hex); - strbuf_addch(data->out, '\n'); - } - return 0; -} - -static int write_shallow_commits(struct strbuf *out, int use_pack_protocol) -{ - struct write_shallow_data data; - data.out = out; - data.use_pack_protocol = use_pack_protocol; - data.count = 0; - for_each_commit_graft(write_one_shallow, &data); - return data.count; -} - -static enum ack_type get_ack(int fd, unsigned char *result_sha1) -{ - static char line[1000]; - 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); -} - -#define INITIAL_FLUSH 16 -#define PIPESAFE_FLUSH 32 -#define LARGE_FLUSH 1024 - -static int next_flush(int count) -{ - int flush_limit = args.stateless_rpc ? LARGE_FLUSH : PIPESAFE_FLUSH; - - if (count < flush_limit) - count <<= 1; - else - count += flush_limit; - return count; -} - -static int find_common(int fd[2], unsigned char *result_sha1, - struct ref *refs) -{ - int fetching; - int count = 0, flushes = 0, 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; - - if (args.stateless_rpc && multi_ack == 1) - die("--stateless-rpc requires multi_ack_detailed"); - if (marked) - for_each_ref(clear_marks, NULL); - marked = 1; - - for_each_ref(rev_list_insert_ref, NULL); - for_each_alternate_ref(insert_one_alternate_ref, NULL); - - fetching = 0; - for ( ; refs ; refs = refs->next) { - unsigned char *remote = refs->old_sha1; - const char *remote_hex; - struct object *o; - - /* - * If that object is complete (i.e. it is an ancestor of a - * local ref), we tell them we have it but do not have to - * tell them about its ancestors, which they already know - * about. - * - * We use lookup_object here because we are only - * interested in the case we *know* the object is - * reachable and we have already scanned it. - */ - if (((o = lookup_object(remote)) != NULL) && - (o->flags & COMPLETE)) { - continue; - } - - remote_hex = sha1_to_hex(remote); - if (!fetching) { - struct strbuf c = STRBUF_INIT; - if (multi_ack == 2) strbuf_addstr(&c, " multi_ack_detailed"); - if (multi_ack == 1) strbuf_addstr(&c, " multi_ack"); - if (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); - } - } 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 */ - } - } - } -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; - 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 *refname, 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); - } - } - 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]; - int match_pos; - - if (nr_match && !args.fetch_all) { - if (ARRAY_SIZE(fastarray) < nr_match) - return_refs = xcalloc(nr_match, sizeof(struct ref *)); - else { - return_refs = fastarray; - memset(return_refs, 0, sizeof(struct ref *) * nr_match); - } - } - else - return_refs = NULL; - - match_pos = 0; - for (ref = *refs; ref; ref = next) { - next = ref->next; - if (!memcmp(ref->name, "refs/", 5) && - check_refname_format(ref->name + 5, 0)) - ; /* trash */ - else if (args.fetch_all && - (!args.depth || prefixcmp(ref->name, "refs/tags/") )) { - *newtail = ref; - ref->next = NULL; - newtail = &ref->next; - continue; - } - else { - int cmp = -1; - while (match_pos < nr_match) { - cmp = strcmp(ref->name, match[match_pos]); - if (cmp < 0) /* definitely do not have it */ - break; - else if (cmp == 0) { /* definitely have it */ - match[match_pos][0] = '\0'; - return_refs[match_pos] = ref; - break; - } - else /* might have it; keep looking */ - match_pos++; - } - if (!cmp) - continue; /* we will link it later */ - } - free(ref); - } - - if (!args.fetch_all) { - int i; - for (i = 0; i < nr_match; i++) { - ref = return_refs[i]; - if (ref) { - *newtail = ref; - ref->next = NULL; - newtail = &ref->next; - } - } - if (return_refs != fastarray) - free(return_refs); - } - *refs = newlist; -} - -static void mark_alternate_complete(const struct ref *ref, void *unused) -{ - mark_complete(NULL, ref->old_sha1, 0, NULL); -} - -static int everything_local(struct ref **refs, int nr_match, char **match) +static void add_sought_entry_mem(struct ref ***sought, int *nr, int *alloc, + const char *name, int namelen) { - struct ref *ref; - int retval; - unsigned long cutoff = 0; - - save_commit_buffer = 0; - - for (ref = *refs; ref; ref = ref->next) { - struct object *o; + struct ref *ref = xcalloc(1, sizeof(*ref) + namelen + 1); - o = parse_object(ref->old_sha1); - if (!o) - continue; - - /* We already have it -- which may mean that we were - * in sync with the other side at some time after - * that (it is OK if we guess wrong here). - */ - if (o->type == OBJ_COMMIT) { - struct commit *commit = (struct commit *)o; - if (!cutoff || cutoff < commit->date) - cutoff = commit->date; - } - } - - if (!args.depth) { - for_each_ref(mark_complete, NULL); - for_each_alternate_ref(mark_alternate_complete, NULL); - if (cutoff) - mark_recent_complete_commits(cutoff); - } - - /* - * Mark all complete remote refs as common refs. - * Don't mark them common yet; the server has to be told so first. - */ - for (ref = *refs; ref; ref = ref->next) { - struct object *o = deref_tag(lookup_object(ref->old_sha1), - NULL, 0); - - if (!o || o->type != OBJ_COMMIT || !(o->flags & COMPLETE)) - continue; - - if (!(o->flags & SEEN)) { - rev_list_push((struct commit *)o, COMMON_REF | SEEN); - - mark_common((struct commit *)o, 1, 1); - } - } - - filter_refs(refs, nr_match, match); - - for (retval = 1, ref = *refs; ref ; ref = ref->next) { - const unsigned char *remote = ref->old_sha1; - unsigned char local[20]; - struct object *o; - - o = lookup_object(remote); - if (!o || !(o->flags & COMPLETE)) { - retval = 0; - if (!args.verbose) - continue; - fprintf(stderr, - "want %s (%s)\n", sha1_to_hex(remote), - ref->name); - continue; - } - - hashcpy(ref->new_sha1, local); - if (!args.verbose) - continue; - fprintf(stderr, - "already have %s (%s)\n", sha1_to_hex(remote), - ref->name); - } - return retval; + memcpy(ref->name, name, namelen); + ref->name[namelen] = '\0'; + (*nr)++; + ALLOC_GROW(*sought, *nr, *alloc); + (*sought)[*nr - 1] = ref; } -static int sideband_demux(int in, int out, void *data) +static void add_sought_entry(struct ref ***sought, int *nr, int *alloc, + const char *string) { - int *xd = data; - - int ret = recv_sideband("fetch-pack", xd[0], out); - close(out); - return ret; -} - -static int get_pack(int xd[2], char **pack_lockfile) -{ - struct async demux; - const char *argv[20]; - char keep_arg[256]; - char hdr_arg[256]; - const char **av; - int do_keep = args.keep_pack; - struct child_process cmd; - - memset(&demux, 0, sizeof(demux)); - if (use_sideband) { - /* xd[] is talking with upload-pack; subprocess reads from - * xd[0], spits out band#2 to stderr, and feeds us band#1 - * through demux->out. - */ - demux.proc = sideband_demux; - demux.data = xd; - demux.out = -1; - if (start_async(&demux)) - die("fetch-pack: unable to fork off sideband" - " demultiplexer"); - } - else - demux.out = xd[0]; - - memset(&cmd, 0, sizeof(cmd)); - cmd.argv = argv; - av = argv; - *hdr_arg = 0; - if (!args.keep_pack && unpack_limit) { - struct pack_header header; - - if (read_pack_header(demux.out, &header)) - die("protocol error: bad pack header"); - snprintf(hdr_arg, sizeof(hdr_arg), - "--pack_header=%"PRIu32",%"PRIu32, - ntohl(header.hdr_version), ntohl(header.hdr_entries)); - if (ntohl(header.hdr_entries) < unpack_limit) - do_keep = 0; - else - do_keep = 1; - } - - if (do_keep) { - if (pack_lockfile) - cmd.out = -1; - *av++ = "index-pack"; - *av++ = "--stdin"; - if (!args.quiet && !args.no_progress) - *av++ = "-v"; - if (args.use_thin_pack) - *av++ = "--fix-thin"; - if (args.lock_pack || unpack_limit) { - int s = sprintf(keep_arg, - "--keep=fetch-pack %"PRIuMAX " on ", (uintmax_t) getpid()); - if (gethostname(keep_arg + s, sizeof(keep_arg) - s)) - strcpy(keep_arg + s, "localhost"); - *av++ = keep_arg; - } - } - else { - *av++ = "unpack-objects"; - if (args.quiet || args.no_progress) - *av++ = "-q"; - } - if (*hdr_arg) - *av++ = hdr_arg; - if (fetch_fsck_objects >= 0 - ? fetch_fsck_objects - : transfer_fsck_objects >= 0 - ? transfer_fsck_objects - : 0) - *av++ = "--strict"; - *av++ = NULL; - - cmd.in = demux.out; - 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]; - - sort_ref_list(&ref, ref_compare_name); - - if (is_repository_shallow() && !server_supports("shallow")) - die("Server does not support shallow clients"); - if (server_supports("multi_ack_detailed")) { - if (args.verbose) - fprintf(stderr, "Server supports multi_ack_detailed\n"); - multi_ack = 2; - if (server_supports("no-done")) { - if (args.verbose) - fprintf(stderr, "Server supports no-done\n"); - if (args.stateless_rpc) - no_done = 1; - } - } - else if (server_supports("multi_ack")) { - if (args.verbose) - fprintf(stderr, "Server supports multi_ack\n"); - multi_ack = 1; - } - if (server_supports("side-band-64k")) { - if (args.verbose) - fprintf(stderr, "Server supports side-band-64k\n"); - use_sideband = 2; - } - else if (server_supports("side-band")) { - if (args.verbose) - fprintf(stderr, "Server supports side-band\n"); - use_sideband = 1; - } - if (server_supports("ofs-delta")) { - if (args.verbose) - fprintf(stderr, "Server supports ofs-delta\n"); - } else - prefer_ofs_delta = 0; - if (everything_local(&ref, nr_match, match)) { - packet_flush(fd[1]); - goto all_done; - } - if (find_common(fd, sha1, ref) < 0) - if (!args.keep_pack) - /* When cloning, it is not unusual to have - * no common commit. - */ - warning("no common commits"); - - if (args.stateless_rpc) - packet_flush(fd[1]); - if (get_pack(fd, pack_lockfile)) - die("git fetch-pack: fetch failed."); - - all_done: - return ref; -} - -static int remove_duplicates(int nr_heads, char **heads) -{ - int src, dst; - - if (!nr_heads) - return 0; - - for (src = dst = 1; src < nr_heads; src++) - if (strcmp(heads[src], heads[dst-1])) - heads[dst++] = heads[src]; - return dst; -} - -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; - } - - if (!strcmp(var, "fetch.fsckobjects")) { - fetch_fsck_objects = git_config_bool(var, value); - return 0; - } - - if (!strcmp(var, "transfer.fsckobjects")) { - transfer_fsck_objects = git_config_bool(var, value); - return 0; - } - - return git_default_config(var, value, cb); -} - -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; + add_sought_entry_mem(sought, nr, alloc, string, strlen(string)); } int cmd_fetch_pack(int argc, const char **argv, const char *prefix) @@ -902,15 +30,19 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix) int i, ret; struct ref *ref = NULL; const char *dest = NULL; - int alloc_heads = 0, nr_heads = 0; - char **heads = NULL; + struct ref **sought = NULL; + int nr_sought = 0, alloc_sought = 0; int fd[2]; char *pack_lockfile = NULL; char **pack_lockfile_ptr = NULL; struct child_process *conn; + struct fetch_pack_args args; packet_trace_identity("fetch-pack"); + memset(&args, 0, sizeof(args)); + args.uploadpack = "git-upload-pack"; + for (i = 1; i < argc && *argv[i] == '-'; i++) { const char *arg = argv[i]; @@ -980,32 +112,25 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix) * Copy refs from cmdline to growable list, then append any * refs from the standard input: */ - ALLOC_GROW(heads, argc - i, alloc_heads); for (; i < argc; i++) - heads[nr_heads++] = xstrdup(argv[i]); + add_sought_entry(&sought, &nr_sought, &alloc_sought, argv[i]); if (args.stdin_refs) { if (args.stateless_rpc) { /* in stateless RPC mode we use pkt-line to read * from stdin, until we get a flush packet */ - static char line[1000]; for (;;) { - int n = packet_read_line(0, line, sizeof(line)); - if (!n) + char *line = packet_read_line(0, NULL); + if (!line) break; - if (line[n-1] == '\n') - n--; - ALLOC_GROW(heads, nr_heads + 1, alloc_heads); - heads[nr_heads++] = xmemdupz(line, n); + add_sought_entry(&sought, &nr_sought, &alloc_sought, line); } } else { /* read from stdin one ref per line, until EOF */ struct strbuf line = STRBUF_INIT; - while (strbuf_getline(&line, stdin, '\n') != EOF) { - ALLOC_GROW(heads, nr_heads + 1, alloc_heads); - heads[nr_heads++] = strbuf_detach(&line, NULL); - } + while (strbuf_getline(&line, stdin, '\n') != EOF) + add_sought_entry(&sought, &nr_sought, &alloc_sought, line.buf); strbuf_release(&line); } } @@ -1019,10 +144,10 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix) args.verbose ? CONNECT_VERBOSE : 0); } - get_remote_heads(fd[0], &ref, 0, NULL); + get_remote_heads(fd[0], NULL, 0, &ref, 0, NULL); ref = fetch_pack(&args, fd, conn, ref, dest, - nr_heads, heads, pack_lockfile_ptr); + sought, nr_sought, pack_lockfile_ptr); if (pack_lockfile) { printf("lock %s\n", pack_lockfile); fflush(stdout); @@ -1030,21 +155,23 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix) close(fd[0]); close(fd[1]); if (finish_connect(conn)) - ref = NULL; + return 1; + ret = !ref; - if (!ret && nr_heads) { - /* If the heads to pull were given, we should have - * consumed all of them by matching the remote. - * Otherwise, 'git fetch remote no-such-ref' would - * silently succeed without issuing an error. - */ - for (i = 0; i < nr_heads; i++) - if (heads[i] && heads[i][0]) { - error("no such remote ref %s", heads[i]); - ret = 1; - } + /* + * 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_sought; i++) { + if (!sought[i] || sought[i]->matched) + continue; + error("no such remote ref %s", sought[i]->name); + ret = 1; } + while (ref) { printf("%s %s\n", sha1_to_hex(ref->old_sha1), ref->name); @@ -1053,72 +180,3 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix) return ret; } - -static int compare_heads(const void *a, const void *b) -{ - return strcmp(*(const char **)a, *(const char **)b); -} - -struct ref *fetch_pack(struct fetch_pack_args *my_args, - int fd[], struct child_process *conn, - const struct ref *ref, - 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) { - qsort(heads, nr_heads, sizeof(*heads), compare_heads); - nr_heads = remove_duplicates(nr_heads, heads); - } - - if (!ref) { - packet_flush(fd[1]); - die("no matching remote head"); - } - 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 bb9a0743ff..d784b2e694 100644 --- a/builtin/fetch.c +++ b/builtin/fetch.c @@ -14,12 +14,13 @@ #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 }; @@ -31,7 +32,7 @@ enum { static int all, append, dry_run, force, keep, multiple, prune, update_head_ok, verbosity; static int progress = -1, recurse_submodules = RECURSE_SUBMODULES_DEFAULT; -static int tags = TAGS_DEFAULT; +static int tags = TAGS_DEFAULT, unshallow; static const char *depth; static const char *upload_pack; static struct strbuf default_rla = STRBUF_INIT; @@ -56,36 +57,39 @@ 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_BOOL(0, "progress", &progress, "force progress reporting"), - OPT_STRING(0, "depth", &depth, "depth", - "deepen history of shallow clone"), - { OPTION_STRING, 0, "submodule-prefix", &submodule_prefix, "dir", - "prepend this to submodule path output", PARSE_OPT_HIDDEN }, + 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_SET_INT, 0, "unshallow", &unshallow, NULL, + N_("convert to a complete repository"), + PARSE_OPT_NONEG | PARSE_OPT_NOARG, NULL, 1 }, + { 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() }; @@ -115,7 +119,7 @@ static void add_merge_config(struct ref **head, for (rm = *head; rm; rm = rm->next) { if (branch_merge_matches(branch, i, rm->name)) { - rm->merge = 1; + rm->fetch_head_status = FETCH_HEAD_MERGE; break; } } @@ -136,7 +140,7 @@ static void add_merge_config(struct ref **head, refspec.src = branch->merge[i]->src; get_fetch_map(remote_refs, &refspec, tail, 1); for (rm = *old_tail; rm; rm = rm->next) - rm->merge = 1; + rm->fetch_head_status = FETCH_HEAD_MERGE; } } @@ -156,6 +160,8 @@ static struct ref *get_ref_map(struct transport *transport, const struct ref *remote_refs = transport_get_remote_refs(transport); if (ref_count || tags == TAGS_SET) { + struct ref **old_tail; + for (i = 0; i < ref_count; i++) { get_fetch_map(remote_refs, &refs[i], &tail, 0); if (refs[i].dst && refs[i].dst[0]) @@ -163,9 +169,23 @@ static struct ref *get_ref_map(struct transport *transport, } /* Merge everything on the command line, but not --tags */ for (rm = ref_map; rm; rm = rm->next) - rm->merge = 1; + rm->fetch_head_status = FETCH_HEAD_MERGE; if (tags == TAGS_SET) get_fetch_map(remote_refs, tag_refspec, &tail, 0); + + /* + * For any refs that we happen to be fetching via command-line + * arguments, take the opportunity to update their configured + * counterparts. However, we do not want to mention these + * entries in FETCH_HEAD at all, as they would simply be + * duplicates of existing entries. + */ + old_tail = tail; + for (i = 0; i < transport->remote->fetch_refspec_nr; i++) + get_fetch_map(ref_map, &transport->remote->fetch[i], + &tail, 1); + for (rm = *old_tail; rm; rm = rm->next) + rm->fetch_head_status = FETCH_HEAD_IGNORE; } else { /* Use the defaults */ struct remote *remote = transport->remote; @@ -182,7 +202,7 @@ static struct ref *get_ref_map(struct transport *transport, *autotags = 1; if (!i && !has_merge && ref_map && !remote->fetch[0].pattern) - ref_map->merge = 1; + ref_map->fetch_head_status = FETCH_HEAD_MERGE; } /* * if the remote we're fetching from is the same @@ -198,7 +218,7 @@ static struct ref *get_ref_map(struct transport *transport, ref_map = get_remote_ref(remote_refs, "HEAD"); if (!ref_map) die(_("Couldn't find remote ref HEAD")); - ref_map->merge = 1; + ref_map->fetch_head_status = FETCH_HEAD_MERGE; tail = &ref_map->next; } } @@ -255,9 +275,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; } @@ -271,7 +290,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; } @@ -282,7 +301,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; @@ -317,13 +336,13 @@ static int update_local_ref(struct ref *ref, 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)); @@ -357,7 +376,7 @@ 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; @@ -386,7 +405,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; + int want_status; fp = fopen(filename, "a"); if (!fp) @@ -404,19 +423,22 @@ static int store_updated_refs(const char *raw_url, const char *remote_name, } /* - * 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. + * We do a pass for each fetch_head_status type in their enum order, so + * merged entries are written before not-for-merge. That lets readers + * use FETCH_HEAD as a refname to refer to the ref to be merged. */ - for (want_merge = 1; 0 <= want_merge; want_merge--) { + for (want_status = FETCH_HEAD_MERGE; + want_status <= FETCH_HEAD_IGNORE; + want_status++) { for (rm = ref_map; rm; rm = rm->next) { struct ref *ref = NULL; + const char *merge_status_marker = ""; commit = lookup_commit_reference_gently(rm->old_sha1, 1); if (!commit) - rm->merge = 0; + rm->fetch_head_status = FETCH_HEAD_NOT_FOR_MERGE; - if (rm->merge != want_merge) + if (rm->fetch_head_status != want_status) continue; if (rm->peer_ref) { @@ -462,16 +484,26 @@ static int store_updated_refs(const char *raw_url, const char *remote_name, 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); + switch (rm->fetch_head_status) { + case FETCH_HEAD_NOT_FOR_MERGE: + merge_status_marker = "not-for-merge"; + /* fall-through */ + case FETCH_HEAD_MERGE: + fprintf(fp, "%s\t%s\t%s", + sha1_to_hex(rm->old_sha1), + merge_status_marker, + note.buf); + for (i = 0; i < url_len; ++i) + if ('\n' == url[i]) + fputs("\\n", fp); + else + fputc(url[i], fp); + fputc('\n', fp); + break; + default: + /* do not write anything to FETCH_HEAD */ + break; + } strbuf_reset(¬e); if (ref) { @@ -554,7 +586,7 @@ static int prune_refs(struct refspec *refs, int ref_count, struct ref *ref_map) 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); } @@ -568,7 +600,8 @@ static int add_existing(const char *refname, const unsigned char *sha1, { struct string_list *list = (struct string_list *)cbdata; struct string_list_item *item = string_list_insert(list, refname); - item->util = (void *)sha1; + item->util = xmalloc(20); + hashcpy(item->util, sha1); return 0; } @@ -587,7 +620,7 @@ static void find_non_local_tags(struct transport *transport, struct ref **head, struct ref ***tail) { - struct string_list existing_refs = STRING_LIST_INIT_NODUP; + struct string_list existing_refs = STRING_LIST_INIT_DUP; struct string_list remote_refs = STRING_LIST_INIT_NODUP; const struct ref *ref; struct string_list_item *item = NULL; @@ -633,7 +666,7 @@ static void find_non_local_tags(struct transport *transport, item = string_list_insert(&remote_refs, ref->name); item->util = (void *)ref->old_sha1; } - string_list_clear(&existing_refs, 0); + string_list_clear(&existing_refs, 1); /* * We may have a final lightweight tag that needs to be @@ -690,11 +723,11 @@ static int truncate_fetch_head(void) static int do_fetch(struct transport *transport, struct refspec *refs, int ref_count) { - struct string_list existing_refs = STRING_LIST_INIT_NODUP; - struct string_list_item *peer_item = NULL; + struct string_list existing_refs = STRING_LIST_INIT_DUP; struct ref *ref_map; struct ref *rm; int autotags = (transport->remote->fetch_tags == 1); + int retcode = 0; for_each_ref(add_existing, &existing_refs); @@ -710,9 +743,9 @@ static int do_fetch(struct transport *transport, /* if not appending, truncate FETCH_HEAD */ if (!append && !dry_run) { - int errcode = truncate_fetch_head(); - if (errcode) - return errcode; + retcode = truncate_fetch_head(); + if (retcode) + goto cleanup; } ref_map = get_ref_map(transport, refs, ref_count, tags, &autotags); @@ -721,8 +754,9 @@ static int do_fetch(struct transport *transport, for (rm = ref_map; rm; rm = rm->next) { if (rm->peer_ref) { - peer_item = string_list_lookup(&existing_refs, - rm->peer_ref->name); + struct string_list_item *peer_item = + string_list_lookup(&existing_refs, + rm->peer_ref->name); if (peer_item) hashcpy(rm->peer_ref->old_sha1, peer_item->util); @@ -733,7 +767,8 @@ static int do_fetch(struct transport *transport, transport_set_option(transport, TRANS_OPT_FOLLOWTAGS, "1"); if (fetch_refs(transport, ref_map)) { free_refs(ref_map); - return 1; + retcode = 1; + goto cleanup; } if (prune) { /* If --tags was specified, pretend the user gave us the canonical tags refspec */ @@ -776,7 +811,9 @@ static int do_fetch(struct transport *transport, free_refs(ref_map); } - return 0; + cleanup: + string_list_clear(&existing_refs, 1); + return retcode; } static void set_option(const char *name, const char *value) @@ -841,38 +878,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(); @@ -880,18 +918,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; } @@ -954,6 +996,9 @@ int cmd_fetch(int argc, const char **argv, const char *prefix) struct string_list list = STRING_LIST_INIT_NODUP; struct remote *remote; int result = 0; + static const char *argv_gc_auto[] = { + "gc", "--auto", NULL, + }; packet_trace_identity("fetch"); @@ -965,6 +1010,18 @@ int cmd_fetch(int argc, const char **argv, const char *prefix) argc = parse_options(argc, argv, prefix, builtin_fetch_options, builtin_fetch_usage, 0); + if (unshallow) { + if (depth) + die(_("--depth and --unshallow cannot be used together")); + else if (!is_repository_shallow()) + die(_("--unshallow on a complete repository does not make sense")); + else { + static char inf_depth[12]; + sprintf(inf_depth, "%d", INFINITE_DEPTH); + depth = inf_depth; + } + } + if (recurse_submodules != RECURSE_SUBMODULES_OFF) { if (recurse_submodules_default) { int arg = parse_fetch_recurse_submodules_arg("--recurse-submodules-default", recurse_submodules_default); @@ -1007,18 +1064,21 @@ 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 */ list.strdup_strings = 1; string_list_clear(&list, 0); + run_command_v_opt(argv_gc_auto, RUN_GIT_CMD); + return result; } diff --git a/builtin/fmt-merge-msg.c b/builtin/fmt-merge-msg.c index 2c4d435da1..1c04070869 100644 --- a/builtin/fmt-merge-msg.c +++ b/builtin/fmt-merge-msg.c @@ -10,7 +10,7 @@ #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 }; @@ -232,8 +232,9 @@ static void record_person(int which, struct string_list *people, { char *name_buf, *name, *name_end; struct string_list_item *elem; - const char *field = (which == 'a') ? "\nauthor " : "\ncommitter "; + const char *field; + field = (which == 'a') ? "\nauthor " : "\ncommitter "; name = strstr(commit->buffer, field); if (!name) return; @@ -286,10 +287,10 @@ static void credit_people(struct strbuf *out, const char *me; if (kind == 'a') { - label = "\n# By "; + label = "By"; me = git_author_info(IDENT_NO_DATE); } else { - label = "\n# Via "; + label = "Via"; me = git_committer_info(IDENT_NO_DATE); } @@ -299,7 +300,7 @@ static void credit_people(struct strbuf *out, (me = skip_prefix(me, them->items->string)) != NULL && skip_prefix(me, " <"))) return; - strbuf_addstr(out, label); + strbuf_addf(out, "\n%c %s ", comment_line_char, label); add_people_count(out, them); } @@ -323,7 +324,8 @@ static void add_people_info(struct strbuf *out, static void shortlog(const char *name, struct origin_data *origin_data, struct commit *head, - struct rev_info *rev, int limit, + struct rev_info *rev, + struct fmt_merge_msg_opts *opts, struct strbuf *out) { int i, count = 0; @@ -335,6 +337,7 @@ static void shortlog(const char *name, 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) @@ -351,13 +354,15 @@ static void shortlog(const char *name, if (commit->parents && commit->parents->next) { /* do not list a merge but count committer */ - record_person('c', &committers, commit); + if (opts->credit_people) + record_person('c', &committers, commit); continue; } - if (!count) + if (!count && opts->credit_people) /* the 'tip' committer */ record_person('c', &committers, commit); - record_person('a', &authors, commit); + if (opts->credit_people) + record_person('a', &authors, commit); count++; if (subjects.nr > limit) continue; @@ -372,7 +377,8 @@ static void shortlog(const char *name, string_list_append(&subjects, strbuf_detach(&sb, NULL)); } - add_people_info(out, &authors, &committers); + if (opts->credit_people) + add_people_info(out, &authors, &committers); if (count > limit) strbuf_addf(out, "\n* %s: (%d commits)\n", name, count); else @@ -464,7 +470,7 @@ static void fmt_tag_signature(struct strbuf *tagbuf, strbuf_complete_line(tagbuf); if (sig->len) { strbuf_addch(tagbuf, '\n'); - strbuf_add_lines(tagbuf, "# ", sig->buf, sig->len); + strbuf_add_commented_lines(tagbuf, sig->buf, sig->len); } } @@ -486,7 +492,7 @@ static void fmt_merge_msg_sigs(struct strbuf *out) if (size == len) ; /* merely annotated */ - else if (verify_signed_buffer(buf, len, buf + len, size - len, &sig)) { + else if (verify_signed_buffer(buf, len, buf + len, size - len, &sig, NULL)) { if (!sig.len) strbuf_addstr(&sig, "gpg verification failed.\n"); } @@ -497,14 +503,18 @@ static void fmt_merge_msg_sigs(struct strbuf *out) } else { if (tag_number == 2) { struct strbuf tagline = STRBUF_INIT; - strbuf_addf(&tagline, "\n# %s\n", - origins.items[first_tag].string); + strbuf_addch(&tagline, '\n'); + strbuf_add_commented_lines(&tagline, + origins.items[first_tag].string, + strlen(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); + strbuf_addch(&tagbuf, '\n'); + strbuf_add_commented_lines(&tagbuf, + origins.items[i].string, + strlen(origins.items[i].string)); fmt_tag_signature(&tagbuf, &sig, buf, len); } strbuf_release(&sig); @@ -635,7 +645,7 @@ int fmt_merge_msg(struct strbuf *in, struct strbuf *out, for (i = 0; i < origins.nr; i++) shortlog(origins.items[i].string, origins.items[i].util, - head, &rev, opts->shortlog_len, out); + head, &rev, opts, out); } strbuf_complete_line(out); @@ -650,16 +660,16 @@ int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix) 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() }; @@ -690,6 +700,7 @@ int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix) 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); diff --git a/builtin/for-each-ref.c b/builtin/for-each-ref.c index 0c5294e5e8..7f059c31df 100644 --- a/builtin/for-each-ref.c +++ b/builtin/for-each-ref.c @@ -976,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 }; @@ -991,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 a710227a64..9909b6d519 100644 --- a/builtin/fsck.c +++ b/builtin/fsck.c @@ -112,7 +112,7 @@ static int mark_object(struct object *obj, int type, void *data) return 1; } - add_object_array(obj, (void *) parent, &pending); + add_object_array(obj, NULL, &pending); return 0; } @@ -605,23 +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_BOOL(0, "dangling", &show_dangling, "show dangling objects"), - OPT_BOOLEAN(0, "tags", &show_tags, "report tags"), - OPT_BOOLEAN(0, "root", &show_root, "report root nodes"), - OPT_BOOLEAN(0, "cache", &keep_cache_objects, "make index objects head nodes"), - 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"), - OPT_BOOL(0, "progress", &show_progress, "show progress"), + N_("write dangling objects in .git/lost-found")), + OPT_BOOL(0, "progress", &show_progress, N_("show progress")), OPT_END(), }; diff --git a/builtin/gc.c b/builtin/gc.c index 9b4232c8f3..6be6c8d65b 100644 --- a/builtin/gc.c +++ b/builtin/gc.c @@ -19,7 +19,7 @@ #define FAILED_RUN "failed to run %s" static const char * const builtin_gc_usage[] = { - "git gc [options]", + N_("git gc [options]"), NULL }; @@ -174,12 +174,12 @@ int cmd_gc(int argc, const char **argv, const char *prefix) int quiet = 0; 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() }; @@ -217,9 +217,7 @@ 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 " diff --git a/builtin/grep.c b/builtin/grep.c index 29adb0ac93..0223d701a6 100644 --- a/builtin/grep.c +++ b/builtin/grep.c @@ -19,7 +19,7 @@ #include "dir.h" static char const * const grep_usage[] = { - "git grep [options] [-e] <pattern> [<rev>...] [[--] <path>...]", + N_("git grep [options] [-e] <pattern> [<rev>...] [[--] <path>...]"), NULL }; @@ -86,7 +86,7 @@ static pthread_cond_t cond_result; static int skip_first_line; static void add_work(struct grep_opt *opt, enum grep_source_type type, - const char *name, const void *id) + const char *name, const char *path, const void *id) { grep_lock(); @@ -94,7 +94,7 @@ static void add_work(struct grep_opt *opt, enum grep_source_type type, pthread_cond_wait(&cond_write, &grep_mutex); } - grep_source_init(&todo[todo_end].source, type, name, 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; @@ -209,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); @@ -260,51 +261,12 @@ 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; - - if (userdiff_config(var, value) < 0) - return -1; - - if (!strcmp(var, "grep.extendedregexp")) { - if (git_config_bool(var, value)) - opt->regflags |= REG_EXTENDED; - else - opt->regflags &= ~REG_EXTENDED; - return 0; - } - - if (!strcmp(var, "grep.linenumber")) { - opt->linenum = git_config_bool(var, value); - return 0; - } - - if (!strcmp(var, "color.grep")) - opt->color = git_config_colorbool(var, value); - 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) @@ -318,7 +280,8 @@ static void *lock_and_read_sha1_file(const unsigned char *sha1, enum object_type } 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; @@ -332,7 +295,7 @@ static int grep_sha1(struct grep_opt *opt, const unsigned char *sha1, #ifndef NO_PTHREADS if (use_threads) { - add_work(opt, GREP_SOURCE_SHA1, pathbuf.buf, sha1); + add_work(opt, GREP_SOURCE_SHA1, pathbuf.buf, path, sha1); strbuf_release(&pathbuf); return 0; } else @@ -341,7 +304,7 @@ static int grep_sha1(struct grep_opt *opt, const unsigned char *sha1, struct grep_source gs; int hit; - grep_source_init(&gs, GREP_SOURCE_SHA1, pathbuf.buf, sha1); + grep_source_init(&gs, GREP_SOURCE_SHA1, pathbuf.buf, path, sha1); strbuf_release(&pathbuf); hit = grep_source(opt, &gs); @@ -361,7 +324,7 @@ static int grep_file(struct grep_opt *opt, const char *filename) #ifndef NO_PTHREADS if (use_threads) { - add_work(opt, GREP_SOURCE_FILE, buf.buf, filename); + add_work(opt, GREP_SOURCE_FILE, buf.buf, filename, filename); strbuf_release(&buf); return 0; } else @@ -370,7 +333,7 @@ static int grep_file(struct grep_opt *opt, const char *filename) struct grep_source gs; int hit; - grep_source_init(&gs, GREP_SOURCE_FILE, buf.buf, filename); + grep_source_init(&gs, GREP_SOURCE_FILE, buf.buf, filename, filename); strbuf_release(&buf); hit = grep_source(opt, &gs); @@ -413,7 +376,7 @@ static int grep_cache(struct grep_opt *opt, const struct pathspec *pathspec, int read_cache(); for (nr = 0; nr < active_nr; nr++) { - struct cache_entry *ce = active_cache[nr]; + const struct cache_entry *ce = active_cache[nr]; if (!S_ISREG(ce->ce_mode)) continue; if (!match_pathspec_depth(pathspec, ce->name, ce_namelen(ce), 0, NULL)) @@ -426,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); @@ -444,7 +407,8 @@ 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; enum interesting match = entry_not_interesting; @@ -465,7 +429,8 @@ static int grep_tree(struct grep_opt *opt, const struct pathspec *pathspec, 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; @@ -480,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); @@ -495,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; @@ -518,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; @@ -669,95 +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"), + N_("search in index instead of in the work tree")), OPT_NEGBIT(0, "no-index", &use_index, - "finds in contents not managed by git", 1), + N_("find in contents not managed by git"), 1), OPT_BOOLEAN(0, "untracked", &untracked, - "search in both tracked and untracked files"), + N_("search in both tracked and untracked files")), OPT_SET_INT(0, "exclude-standard", &opt_exclude, - "search also in ignored files", 1), + 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, - "print empty line between matches from different files"), + N_("print empty line between matches from different files")), OPT_BOOLEAN(0, "heading", &opt.heading, - "show filename only once above matches from same file"), + N_("show filename only once above matches from same file")), OPT_GROUP(""), - OPT_CALLBACK('C', "context", &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', "before-context", &opt.pre_context, - "show <n> context lines before matches"), + N_("show <n> context lines before matches")), OPT_INTEGER('A', "after-context", &opt.post_context, - "show <n> context lines after matches"), - OPT_NUMBER_CALLBACK(&opt, "shortcut for -C NUM", + 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, - "show the surrounding function"), + 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, "", @@ -769,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() }; @@ -790,25 +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); + 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 @@ -824,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 */ @@ -894,9 +820,9 @@ int cmd_grep(int argc, const char **argv, const char *prefix) unsigned char sha1[20]; /* Is it a rev? */ if (!get_sha1(arg, sha1)) { - struct object *object = parse_object(sha1); - if (!object) - die(_("bad object %s"), arg); + struct object *object = parse_object_or_die(sha1, arg); + if (!seen_dashdash) + verify_non_filename(prefix, arg); add_object_array(object, arg, &list); continue; } 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 43d3c84449..f1e236b912 100644 --- a/builtin/help.c +++ b/builtin/help.c @@ -1,17 +1,18 @@ /* - * builtin-help.c - * * Builtin help command */ #include "cache.h" #include "builtin.h" #include "exec_cmd.h" -#include "common-cmds.h" #include "parse-options.h" #include "run-command.h" #include "column.h" #include "help.h" +#ifndef DEFAULT_HELP_FORMAT +#define DEFAULT_HELP_FORMAT "man" +#endif + static struct man_viewer_list { struct man_viewer_list *next; char name[FLEX_ARRAY]; @@ -30,21 +31,25 @@ enum help_format { HELP_FORMAT_WEB }; +static const char *html_path; + static int show_all = 0; +static int show_guides = 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_BOOL('a', "all", &show_all, N_("print all available commands")), + OPT_BOOL('g', "guides", &show_guides, N_("print list of useful guides")), + 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] [--guides] [--man|--web|--info] [command]"), NULL }; @@ -231,21 +236,21 @@ static int add_man_viewer_cmd(const char *name, static int add_man_viewer_info(const char *var, const char *value) { - const char *name = var + 4; - const char *subkey = strrchr(name, '.'); + const char *name, *subkey; + int namelen; - if (!subkey) + if (parse_config_key(var, "man", &name, &namelen, &subkey) < 0 || !name) return 0; - if (!strcmp(subkey, ".path")) { + if (!strcmp(subkey, "path")) { if (!value) return config_error_nonbool(var); - return add_man_viewer_path(name, subkey - name, value); + return add_man_viewer_path(name, namelen, value); } - if (!strcmp(subkey, ".cmd")) { + if (!strcmp(subkey, "cmd")) { if (!value) return config_error_nonbool(var); - return add_man_viewer_cmd(name, subkey - name, value); + return add_man_viewer_cmd(name, namelen, value); } return 0; @@ -261,6 +266,12 @@ static int git_help_config(const char *var, const char *value, void *cb) 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); @@ -275,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) || @@ -383,12 +377,15 @@ static void show_info_page(const char *git_cmd) 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); @@ -416,6 +413,37 @@ static void show_html_page(const char *git_cmd) open_html(page_path.buf); } +static struct { + const char *name; + const char *help; +} common_guides[] = { + { "attributes", N_("Defining attributes per path") }, + { "glossary", N_("A Git glossary") }, + { "ignore", N_("Specifies intentionally untracked files to ignore") }, + { "modules", N_("Defining submodule properties") }, + { "revisions", N_("Specifying revisions and ranges for Git") }, + { "tutorial", N_("A tutorial introduction to Git (for version 1.5.1 or newer)") }, + { "workflows", N_("An overview of recommended workflows with Git") }, +}; + +static void list_common_guides_help(void) +{ + int i, longest = 0; + + for (i = 0; i < ARRAY_SIZE(common_guides); i++) { + if (longest < strlen(common_guides[i].name)) + longest = strlen(common_guides[i].name); + } + + puts(_("The common Git guides are:\n")); + for (i = 0; i < ARRAY_SIZE(common_guides); i++) { + printf(" %s ", common_guides[i].name); + mput_char(' ', longest - strlen(common_guides[i].name)); + puts(_(common_guides[i].help)); + } + putchar('\n'); +} + int cmd_help(int argc, const char **argv, const char *prefix) { int nongit; @@ -431,7 +459,16 @@ int cmd_help(int argc, const char **argv, const char *prefix) git_config(git_help_config, NULL); printf(_("usage: %s%s"), _(git_usage_string), "\n\n"); list_commands(colopts, &main_cmds, &other_cmds); + } + + if (show_guides) + list_common_guides_help(); + + if (show_all || show_guides) { printf("%s\n", _(git_more_info_string)); + /* + * We're done. Ignore any remaining args + */ return 0; } @@ -447,6 +484,8 @@ 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])) { diff --git a/builtin/index-pack.c b/builtin/index-pack.c index 470547835c..9c1cfac442 100644 --- a/builtin/index-pack.c +++ b/builtin/index-pack.c @@ -9,6 +9,7 @@ #include "progress.h" #include "fsck.h" #include "exec_cmd.h" +#include "streaming.h" #include "thread-utils.h" static const char index_pack_usage[] = @@ -76,7 +77,10 @@ static int nr_threads; static int from_stdin; static int strict; +static int do_fsck_object; static int verbose; +static int show_stat; +static int check_self_contained_and_connected; static struct progress *progress; @@ -107,6 +111,10 @@ static pthread_mutex_t work_mutex; #define work_lock() lock_mutex(&work_mutex) #define work_unlock() unlock_mutex(&work_mutex) +static pthread_mutex_t deepest_delta_mutex; +#define deepest_delta_lock() lock_mutex(&deepest_delta_mutex) +#define deepest_delta_unlock() unlock_mutex(&deepest_delta_mutex) + static pthread_key_t key; static inline void lock_mutex(pthread_mutex_t *mutex) @@ -129,6 +137,8 @@ static void init_thread(void) init_recursive_mutex(&read_mutex); pthread_mutex_init(&counter_mutex, NULL); pthread_mutex_init(&work_mutex, NULL); + if (show_stat) + pthread_mutex_init(&deepest_delta_mutex, NULL); pthread_key_create(&key, NULL); thread_data = xcalloc(nr_threads, sizeof(*thread_data)); threads_active = 1; @@ -142,6 +152,8 @@ static void cleanup_thread(void) pthread_mutex_destroy(&read_mutex); pthread_mutex_destroy(&counter_mutex); pthread_mutex_destroy(&work_mutex); + if (show_stat) + pthread_mutex_destroy(&deepest_delta_mutex); pthread_key_delete(key); free(thread_data); } @@ -157,6 +169,9 @@ static void cleanup_thread(void) #define work_lock() #define work_unlock() +#define deepest_delta_lock() +#define deepest_delta_unlock() + #endif @@ -174,13 +189,13 @@ static int mark_link(struct object *obj, int type, void *data) /* The content of each linked object must have been checked or it must be already present in the object database */ -static void check_object(struct object *obj) +static unsigned check_object(struct object *obj) { if (!obj) - return; + return 0; if (!(obj->flags & FLAG_LINK)) - return; + return 0; if (!(obj->flags & FLAG_CHECKED)) { unsigned long size; @@ -188,17 +203,20 @@ static void check_object(struct object *obj) if (type != obj->type || type <= 0) die(_("object of unexpected type")); obj->flags |= FLAG_CHECKED; - return; + return 1; } + + return 0; } -static void check_objects(void) +static unsigned check_objects(void) { - unsigned i, max; + unsigned i, max, foreign_nr = 0; max = get_max_object_index(); for (i = 0; i < max; i++) - check_object(get_indexed_object(i)); + foreign_nr += check_object(get_indexed_object(i)); + return foreign_nr; } @@ -290,7 +308,7 @@ static void parse_pack_header(void) if (hdr->hdr_signature != htonl(PACK_SIGNATURE)) 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); @@ -384,30 +402,62 @@ static void unlink_base_data(struct base_data *c) 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); 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; @@ -467,12 +517,14 @@ static void *unpack_raw_entry(struct object_entry *obj, union delta_base *delta_ } 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; @@ -480,13 +532,13 @@ 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; @@ -502,7 +554,20 @@ static void *get_data_from_pack(struct object_entry *obj) 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... */ @@ -511,9 +576,18 @@ static void *get_data_from_pack(struct object_entry *obj) git_inflate_end(&stream); free(inbuf); + if (consume) { + free(data); + data = NULL; + } return data; } +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, @@ -568,25 +642,102 @@ 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) +{ + 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) { - hash_sha1_file(data, size, typename(type), sha1); + void *new_data = NULL; + int collision_test_needed; + + assert(data || obj_entry); + read_lock(); - if (has_sha1_file(sha1)) { + 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)); if (size != has_size || type != has_type || memcmp(data, has_data, size) != 0) die(_("SHA1 COLLISION FOUND WITH %s !"), sha1_to_hex(sha1)); free(has_data); - } else - read_unlock(); + } if (strict) { read_lock(); @@ -601,6 +752,8 @@ static void sha1_object(const void *data, unsigned long size, int eaten; void *buf = (void *) data; + assert(data && "data can only be NULL for large _blobs_"); + /* * we do not need to free the memory here, as the * buf is deleted by the caller. @@ -608,7 +761,8 @@ static void sha1_object(const void *data, unsigned long size, obj = parse_object_buffer(sha1, type, size, buf, &eaten); if (!obj) die(_("invalid %s"), typename(type)); - if (fsck_object(obj, 1, fsck_error_function)) + if (do_fsck_object && + fsck_object(obj, 1, fsck_error_function)) die(_("Error in object")); if (fsck_walk(obj, mark_link, NULL)) die(_("Not all child objects of %s are reachable"), sha1_to_hex(obj->sha1)); @@ -625,11 +779,8 @@ static void sha1_object(const void *data, unsigned long size, } read_unlock(); } -} -static int is_delta_type(enum object_type type) -{ - return (type == OBJ_REF_DELTA || type == OBJ_OFS_DELTA); + free(new_data); } /* @@ -699,9 +850,13 @@ 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; + if (show_stat) { + delta_obj->delta_depth = base->obj->delta_depth + 1; + deepest_delta_lock(); + if (deepest_delta < delta_obj->delta_depth) + deepest_delta = delta_obj->delta_depth; + deepest_delta_unlock(); + } delta_obj->base_object_no = base->obj - objects; delta_data = get_data_from_pack(delta_obj); base_data = get_base_data(base); @@ -711,7 +866,9 @@ static void resolve_delta(struct object_entry *delta_obj, 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, + 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++; @@ -815,8 +972,10 @@ static void *threaded_second_pass(void *data) set_thread_data(data); for (;;) { int i; - work_lock(); + counter_lock(); display_progress(progress, nr_resolved_deltas); + counter_unlock(); + work_lock(); while (nr_dispatched < nr_objects && is_delta_type(objects[nr_dispatched].type)) nr_dispatched++; @@ -841,7 +1000,7 @@ static void *threaded_second_pass(void *data) */ static void parse_pack_objects(unsigned char *sha1) { - int i; + int i, nr_delays = 0; struct delta_entry *delta = deltas; struct stat st; @@ -851,14 +1010,18 @@ static void parse_pack_objects(unsigned char *sha1) 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 (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); } @@ -878,6 +1041,17 @@ static void parse_pack_objects(unsigned char *sha1) if (S_ISREG(st.st_mode) && lseek(input_fd, 0, SEEK_CUR) - input_len != st.st_size) 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()")); } /* @@ -910,7 +1084,8 @@ static void resolve_deltas(void) 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)); + die(_("unable to create thread: %s"), + strerror(ret)); } for (i = 0; i < nr_threads; i++) pthread_join(thread_data[i].thread, NULL); @@ -947,7 +1122,7 @@ static void conclude_pack(int fix_thin_pack, const char *curr_pack, unsigned cha if (fix_thin_pack) { struct sha1file *f; unsigned char read_sha1[20], tail_sha1[20]; - char msg[48]; + struct strbuf msg = STRBUF_INIT; int nr_unresolved = nr_deltas - nr_resolved_deltas; int nr_objects_initial = nr_objects; if (nr_unresolved <= 0) @@ -955,19 +1130,22 @@ static void conclude_pack(int fix_thin_pack, const char *curr_pack, unsigned cha objects = xrealloc(objects, (nr_objects + nr_unresolved + 1) * sizeof(*objects)); + memset(objects + nr_objects + 1, 0, + nr_unresolved * 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); + 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); + die(_("Unexpected tail checksum for %s " + "(disk corruption?)"), curr_pack); } if (nr_deltas != nr_resolved_deltas) die(Q_("pack has %d unresolved delta", @@ -1176,17 +1354,17 @@ static int git_index_pack_config(const char *k, const char *v, void *cb) if (!strcmp(k, "pack.indexversion")) { opts->version = git_config_int(k, v); if (opts->version > 2) - die("bad pack.indexversion=%"PRIu32, opts->version); + 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)", + die(_("invalid number of threads specified (%d)"), nr_threads); #ifdef NO_PTHREADS if (nr_threads != 1) - warning("no threads support, ignoring %s", k); + warning(_("no threads support, ignoring %s"), k); nr_threads = 1; #endif return 0; @@ -1310,7 +1488,7 @@ static void show_pack_info(int stat_only) int cmd_index_pack(int argc, const char **argv, const char *prefix) { - int i, fix_thin_pack = 0, verify = 0, stat_only = 0, stat = 0; + int i, fix_thin_pack = 0, verify = 0, stat_only = 0; const char *curr_pack, *curr_index; const char *index_name = NULL, *pack_name = NULL; const char *keep_name = NULL, *keep_msg = NULL; @@ -1318,6 +1496,7 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix) struct pack_idx_entry **idx_objects; struct pack_idx_option opts; unsigned char pack_sha1[20]; + unsigned foreign_nr = 1; /* zero is a "good" value, assume bad */ if (argc == 2 && !strcmp(argv[1], "-h")) usage(index_pack_usage); @@ -1339,14 +1518,18 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix) fix_thin_pack = 1; } else if (!strcmp(arg, "--strict")) { strict = 1; + do_fsck_object = 1; + } else if (!strcmp(arg, "--check-self-contained-and-connected")) { + strict = 1; + check_self_contained_and_connected = 1; } else if (!strcmp(arg, "--verify")) { verify = 1; } else if (!strcmp(arg, "--verify-stat")) { verify = 1; - stat = 1; + show_stat = 1; } else if (!strcmp(arg, "--verify-stat-only")) { verify = 1; - stat = 1; + show_stat = 1; stat_only = 1; } else if (!strcmp(arg, "--keep")) { keep_msg = ""; @@ -1359,8 +1542,8 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix) usage(index_pack_usage); #ifdef NO_PTHREADS if (nr_threads != 1) - warning("no threads support, " - "ignoring %s", arg); + warning(_("no threads support, " + "ignoring %s"), arg); nr_threads = 1; #endif } else if (!prefixcmp(arg, "--pack_header=")) { @@ -1452,9 +1635,9 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix) conclude_pack(fix_thin_pack, curr_pack, pack_sha1); free(deltas); if (strict) - check_objects(); + foreign_nr = check_objects(); - if (stat) + if (show_stat) show_pack_info(stat_only); idx_objects = xmalloc((nr_objects) * sizeof(struct pack_idx_entry *)); @@ -1478,5 +1661,11 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix) if (index_name == NULL) free((void *) curr_index); + /* + * Let the caller know this pack is not self contained + */ + if (check_self_contained_and_connected && foreign_nr) + return 1; + return 0; } diff --git a/builtin/init-db.c b/builtin/init-db.c index 0dacb8b79c..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; @@ -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 a8249d3c01..2625f9881a 100644 --- a/builtin/log.c +++ b/builtin/log.c @@ -19,8 +19,12 @@ #include "remote.h" #include "string-list.h" #include "parse-options.h" +#include "line-log.h" #include "branch.h" #include "streaming.h" +#include "version.h" +#include "mailmap.h" +#include "gpg-interface.h" /* Set a default date-time format for git log ("log.date" config variable) */ static const char *default_date_mode = NULL; @@ -29,15 +33,22 @@ static int default_abbrev_commit; static int default_show_root = 1; static int decoration_style; static int decoration_given; +static int use_mailmap_config; 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>] [<revision range>] [[--] <path>...]\n") + N_(" or: git show [options] <object>..."), NULL }; +struct line_opt_callback_data { + struct rev_info *rev; + const char *prefix; + struct string_list args; +}; + static int parse_decoration_style(const char *var, const char *value) { switch (git_config_maybe_bool(var, value)) { @@ -72,6 +83,19 @@ static int decorate_callback(const struct option *opt, const char *arg, int unse return 0; } +static int log_line_range_callback(const struct option *option, const char *arg, int unset) +{ + struct line_opt_callback_data *data = option->value; + + if (!arg) + return -1; + + data->rev->line_level_traverse = 1; + string_list_append(&data->args, arg); + + return 0; +} + static void cmd_log_init_defaults(struct rev_info *rev) { if (fmt_pretty) @@ -93,16 +117,25 @@ static void cmd_log_init_finish(int argc, const char **argv, const char *prefix, struct rev_info *rev, struct setup_revision_opt *opt) { struct userformat_want w; - int quiet = 0, source = 0; + int quiet = 0, source = 0, mailmap = 0; + static struct line_opt_callback_data line_cb = {NULL, NULL, STRING_LIST_INIT_DUP}; 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_BOOL(0, "quiet", &quiet, N_("suppress diff output")), + OPT_BOOL(0, "source", &source, N_("show source")), + OPT_BOOL(0, "use-mailmap", &mailmap, N_("Use mail map file")), + { OPTION_CALLBACK, 0, "decorate", NULL, NULL, N_("decorate options"), PARSE_OPT_OPTARG, decorate_callback}, + OPT_CALLBACK('L', NULL, &line_cb, "n,m:file", + "Process line range n,m in file, counting from 1", + log_line_range_callback), OPT_END() }; + line_cb.rev = rev; + line_cb.prefix = prefix; + + mailmap = use_mailmap_config; argc = parse_options(argc, argv, prefix, builtin_log_options, builtin_log_usage, PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN | @@ -135,6 +168,11 @@ static void cmd_log_init_finish(int argc, const char **argv, const char *prefix, if (source) rev->show_source = 1; + if (mailmap) { + rev->mailmap = xcalloc(1, sizeof(struct string_list)); + read_mailmap(rev->mailmap, NULL); + } + if (rev->pretty_given && rev->commit_format == CMIT_FMT_RAW) { /* * "log --pretty=raw" is special; ignore UI oriented @@ -150,6 +188,10 @@ static void cmd_log_init_finish(int argc, const char **argv, const char *prefix, rev->show_decorations = 1; load_ref_decorations(decoration_style); } + + if (rev->line_level_traverse) + line_log_init(rev, line_cb.prefix, &line_cb.args); + setup_pager(); } @@ -195,7 +237,7 @@ static void log_show_early(struct rev_info *revs, struct commit_list *list) int i = revs->early_output; int show_header = 1; - sort_in_topological_order(&list, revs->lifo); + sort_in_topological_order(&list, revs->sort_order); while (list && i) { struct commit *commit = list->item; switch (simplify_commit(revs, commit)) { @@ -350,7 +392,15 @@ 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 (!strcmp(var, "log.mailmap")) { + use_mailmap_config = git_config_bool(var, value); + return 0; + } + if (grep_config(var, value, cb) < 0) + return -1; + if (git_gpg_config(var, value, cb) < 0) + return -1; return git_diff_ui_config(var, value, cb); } @@ -359,6 +409,7 @@ 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); init_revisions(&rev, prefix); @@ -366,6 +417,7 @@ int cmd_whatchanged(int argc, const char **argv, const char *prefix) 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; @@ -448,13 +500,14 @@ 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); 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)); @@ -528,6 +581,7 @@ 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); init_revisions(&rev, prefix); @@ -550,12 +604,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); 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); } @@ -597,6 +653,14 @@ static void add_header(const char *value) static int thread; static int do_signoff; static const char *signature = git_version_string; +static int config_cover_letter; + +enum { + COVER_UNSET, + COVER_OFF, + COVER_ON, + COVER_AUTO +}; static int git_format_config(const char *var, const char *value, void *cb) { @@ -658,6 +722,14 @@ static int git_format_config(const char *var, const char *value, void *cb) } if (!strcmp(var, "format.signature")) return git_config_string(&signature, var, value); + if (!strcmp(var, "format.coverletter")) { + if (value && !strcasecmp(value, "auto")) { + config_cover_letter = COVER_AUTO; + return 0; + } + config_cover_letter = git_config_bool(var, value) ? COVER_ON : COVER_OFF; + return 0; + } return git_log_config(var, value, cb); } @@ -670,7 +742,7 @@ 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; + int suffix_len = strlen(rev->patch_suffix) + 1; if (output_directory) { strbuf_addstr(&filename, output_directory); @@ -681,7 +753,12 @@ static int reopen_stdout(struct commit *commit, const char *subject, strbuf_addch(&filename, '/'); } - get_patch_filename(commit, subject, rev->nr, fmt_patch_suffix, &filename); + if (rev->numbered_files) + strbuf_addf(&filename, "%d", rev->nr); + else if (commit) + fmt_output_commit(&filename, commit, rev); + else + fmt_output_subject(&filename, subject, rev); if (!quiet) fprintf(realstdout, "%s\n", filename.buf + outdir_offset); @@ -693,7 +770,7 @@ static int reopen_stdout(struct commit *commit, const char *subject, 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; @@ -714,7 +791,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"); @@ -723,10 +801,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); } @@ -767,10 +841,37 @@ static void add_branch_description(struct strbuf *buf, const char *branch_name) } } +static char *find_branch_name(struct rev_info *rev) +{ + int i, positive = -1; + unsigned char branch_sha1[20]; + const unsigned char *tip_sha1; + const char *ref; + char *full_ref, *branch = NULL; + + 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; + ref = rev->cmdline.rev[positive].name; + tip_sha1 = rev->cmdline.rev[positive].item->sha1; + if (dwim_ref(ref, strlen(ref), branch_sha1, &full_ref) && + !prefixcmp(full_ref, "refs/heads/") && + !hashcmp(tip_sha1, branch_sha1)) + branch = xstrdup(full_ref + strlen("refs/heads/")); + free(full_ref); + return branch; +} + static void make_cover_letter(struct rev_info *rev, int use_stdout, - int numbered, int numbered_files, struct commit *origin, - int nr, struct commit **list, struct commit *head, + int nr, struct commit **list, const char *branch_name, int quiet) { @@ -784,6 +885,7 @@ static void make_cover_letter(struct rev_info *rev, int use_stdout, struct diff_options opts; int need_8bit_cte = 0; struct pretty_print_context pp = {0}; + struct commit *head = list[0]; if (rev->commit_format != CMIT_FMT_EMAIL) die(_("Cover letter needs email format")); @@ -791,7 +893,7 @@ static void make_cover_letter(struct rev_info *rev, int use_stdout, committer = git_committer_info(0); if (!use_stdout && - reopen_stdout(NULL, numbered_files ? NULL : "cover-letter", rev, quiet)) + reopen_stdout(NULL, rev->numbered_files ? NULL : "cover-letter", rev, quiet)) return; log_write_email_headers(rev, head, &pp.subject, &pp.after_subject, @@ -801,6 +903,9 @@ static void make_cover_letter(struct rev_info *rev, int use_stdout, if (has_non_ascii(list[i]->buffer)) need_8bit_cte = 1; + if (!branch_name) + branch_name = find_branch_name(rev); + msg = body; pp.fmt = CMIT_FMT_EMAIL; pp.date_mode = DATE_RFC2822; @@ -887,7 +992,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 }; @@ -1007,33 +1112,19 @@ static int cc_callback(const struct option *opt, const char *arg, int unset) return 0; } -static char *find_branch_name(struct rev_info *rev) +static int from_callback(const struct option *opt, const char *arg, int unset) { - int i, positive = -1; - unsigned char branch_sha1[20]; - struct strbuf buf = STRBUF_INIT; - const char *branch; + char **from = opt->value; - 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; + free(*from); + + if (unset) + *from = NULL; + else if (arg) + *from = xstrdup(arg); + else + *from = xstrdup(git_committer_info(IDENT_NO_DATE)); + return 0; } int cmd_format_patch(int argc, const char **argv, const char *prefix) @@ -1045,82 +1136,89 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) int nr = 0, total, i; int use_stdout = 0; int start_number = -1; - int numbered_files = 0; /* _just_ numbers */ + int just_numbers = 0; int ignore_if_in_upstream = 0; - int cover_letter = 0; + int cover_letter = -1; int boundary_count = 0; int no_binary_diff = 0; - struct commit *origin = NULL, *head = NULL; + struct commit *origin = NULL; const char *in_reply_to = NULL; struct patch_ids ids; - char *add_signoff = NULL; struct strbuf buf = STRBUF_INIT; int use_patch_format = 0; int quiet = 0; + int reroll_count = -1; char *branch_name = NULL; + char *from = 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(0, "stdout", &use_stdout, - "print patches to standard out"), - OPT_BOOLEAN(0, "cover-letter", &cover_letter, - "generate a cover letter"), - OPT_BOOLEAN(0, "numbered-files", &numbered_files, - "use simple number sequence for output file names"), - OPT_STRING(0, "suffix", &fmt_patch_suffix, "sfx", - "use <sfx> instead of '.patch'"), + OPT_BOOL('s', "signoff", &do_signoff, N_("add Signed-off-by:")), + OPT_BOOL(0, "stdout", &use_stdout, + N_("print patches to standard out")), + OPT_BOOL(0, "cover-letter", &cover_letter, + N_("generate a cover letter")), + OPT_BOOL(0, "numbered-files", &just_numbers, + 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")), + OPT_INTEGER('v', "reroll-count", &reroll_count, + N_("mark the series as Nth re-roll")), + { 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, + { OPTION_CALLBACK, 0, "from", &from, N_("ident"), + N_("set From address to <ident> (or committer ident if absent)"), + PARSE_OPT_OPTARG, from_callback }, + 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; @@ -1131,6 +1229,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; @@ -1147,14 +1246,12 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN | PARSE_OPT_KEEP_DASHDASH); - if (do_signoff) { - const char *committer; - const char *endpos; - committer = git_committer_info(IDENT_STRICT); - endpos = strchr(committer, '>'); - if (!endpos) - die(_("bogus committer info %s"), committer); - add_signoff = xmemdupz(committer, endpos - committer + 1); + if (0 < reroll_count) { + struct strbuf sprefix = STRBUF_INIT; + strbuf_addf(&sprefix, "%s v%d", + rev.subject_prefix, reroll_count); + rev.reroll_count = reroll_count; + rev.subject_prefix = strbuf_detach(&sprefix, NULL); } for (i = 0; i < extra_hdr.nr; i++) { @@ -1186,6 +1283,11 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) rev.extra_headers = strbuf_detach(&buf, NULL); + if (from) { + if (split_ident_line(&rev.from_ident, from, strlen(from))) + die(_("invalid ident line: %s"), from); + } + if (start_number < 0) start_number = 1; @@ -1242,28 +1344,36 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) } if (rev.pending.nr == 1) { + int check_head = 0; + if (rev.max_count < 0 && !rev.show_root_diff) { /* * This is traditional behaviour of "git format-patch * origin" that prepares what the origin side still * does not have. */ - 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 */ + check_head = 1; } /* * Otherwise, it is "format-patch -22 HEAD", and/or * "format-patch --root HEAD". The user wants * get_revision() to do the usual traversal. */ + + if (!strcmp(rev.pending.objects[0].name, "HEAD")) + check_head = 1; + + if (check_head) { + unsigned char sha1[20]; + const char *ref; + 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 */ + } } /* @@ -1272,29 +1382,6 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) */ rev.show_root_diff = 1; - if (cover_letter) { - /* - * 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; - } - /* 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) { /* Don't say anything if head and upstream are the same. */ if (rev.pending.nr == 2) { @@ -1302,7 +1389,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) @@ -1326,28 +1413,38 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) list = xrealloc(list, nr * sizeof(list[0])); list[nr - 1] = commit; } + if (nr == 0) + /* nothing to do */ + return 0; total = nr; if (!keep_subject && auto_number && total > 1) numbered = 1; if (numbered) rev.total = total + start_number - 1; + if (cover_letter == -1) { + if (config_cover_letter == COVER_AUTO) + cover_letter = (total > 1); + else + cover_letter = (config_cover_letter == COVER_ON); + } + if (in_reply_to || thread || cover_letter) rev.ref_message_ids = xcalloc(1, sizeof(struct string_list)); if (in_reply_to) { const char *msgid = clean_message_id(in_reply_to); string_list_append(rev.ref_message_ids, msgid); } - rev.numbered_files = numbered_files; + rev.numbered_files = just_numbers; rev.patch_suffix = fmt_patch_suffix; if (cover_letter) { if (thread) gen_message_id(&rev, "cover"); - make_cover_letter(&rev, use_stdout, numbered, numbered_files, - origin, nr, list, head, branch_name, quiet); + make_cover_letter(&rev, use_stdout, + origin, nr, list, branch_name, quiet); total++; start_number--; } - rev.add_signoff = add_signoff; + rev.add_signoff = do_signoff; while (0 <= --nr) { int shown; commit = list[nr]; @@ -1389,7 +1486,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) } if (!use_stdout && - reopen_stdout(numbered_files ? NULL : commit, NULL, &rev, quiet)) + reopen_stdout(rev.numbered_files ? NULL : commit, NULL, &rev, quiet)) die(_("Failed to create output files")); shown = log_tree_commit(&rev, commit); free(commit->buffer); @@ -1439,7 +1536,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 }; @@ -1473,7 +1570,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() }; @@ -1504,10 +1601,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); @@ -1521,7 +1615,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); @@ -1530,10 +1624,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 b4d8b017a8..80fff23ad9 100644 --- a/builtin/ls-files.c +++ b/builtin/ls-files.c @@ -127,7 +127,7 @@ static void show_killed_files(struct dir_struct *dir) } } -static void show_ce_entry(const char *tag, struct cache_entry *ce) +static void show_ce_entry(const char *tag, const struct cache_entry *ce) { int len = max_prefix_len; @@ -165,11 +165,13 @@ static void show_ce_entry(const char *tag, struct cache_entry *ce) } write_name(ce->name, ce_namelen(ce)); if (debug_mode) { - printf(" ctime: %d:%d\n", ce->ce_ctime.sec, ce->ce_ctime.nsec); - printf(" mtime: %d:%d\n", ce->ce_mtime.sec, ce->ce_mtime.nsec); - printf(" dev: %d\tino: %d\n", ce->ce_dev, ce->ce_ino); - printf(" uid: %d\tgid: %d\n", ce->ce_uid, ce->ce_gid); - printf(" size: %d\tflags: %x\n", ce->ce_size, ce->ce_flags); + const struct stat_data *sd = &ce->ce_stat_data; + + printf(" ctime: %d:%d\n", sd->sd_ctime.sec, sd->sd_ctime.nsec); + printf(" mtime: %d:%d\n", sd->sd_mtime.sec, sd->sd_mtime.nsec); + printf(" dev: %d\tino: %d\n", sd->sd_dev, sd->sd_ino); + printf(" uid: %d\tgid: %d\n", sd->sd_uid, sd->sd_gid); + printf(" size: %d\tflags: %x\n", sd->sd_size, ce->ce_flags); } } @@ -201,19 +203,15 @@ static void show_ru_info(void) } } -static int ce_excluded(struct path_exclude_check *check, struct cache_entry *ce) +static int ce_excluded(struct dir_struct *dir, const struct cache_entry *ce) { int dtype = ce_to_dtype(ce); - return is_path_excluded(check, ce->name, ce_namelen(ce), &dtype); + return is_excluded(dir, ce->name, &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) { @@ -223,11 +221,11 @@ static void show_files(struct dir_struct *dir) if (show_killed) show_killed_files(dir); } - if (show_cached | show_stage) { + if (show_cached || show_stage) { for (i = 0; i < active_nr; i++) { - struct cache_entry *ce = active_cache[i]; + const struct cache_entry *ce = active_cache[i]; if ((dir->flags & DIR_SHOW_IGNORED) && - !ce_excluded(&check, ce)) + !ce_excluded(dir, ce)) continue; if (show_unmerged && !ce_stage(ce)) continue; @@ -237,13 +235,13 @@ static void show_files(struct dir_struct *dir) (ce_skip_worktree(ce) ? tag_skip_worktree : tag_cached), ce); } } - if (show_deleted | show_modified) { + if (show_deleted || show_modified) { for (i = 0; i < active_nr; i++) { - struct cache_entry *ce = active_cache[i]; + const struct cache_entry *ce = active_cache[i]; struct stat st; int err; if ((dir->flags & DIR_SHOW_IGNORED) && - !ce_excluded(&check, ce)) + !ce_excluded(dir, ce)) continue; if (ce->ce_flags & CE_UPDATE) continue; @@ -256,9 +254,6 @@ 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); } /* @@ -278,7 +273,7 @@ static void prune_cache(const char *prefix) last = active_nr; while (last > first) { int next = (last + first) >> 1; - struct cache_entry *ce = active_cache[next]; + const struct cache_entry *ce = active_cache[next]; if (!strncmp(ce->name, prefix, max_prefix_len)) { first = next+1; continue; @@ -338,7 +333,7 @@ void overlay_tree_on_cache(const char *tree_name, const char *prefix) matchbuf[0] = prefix; matchbuf[1] = NULL; init_pathspec(&pathspec, matchbuf); - pathspec.items[0].use_wildcard = 0; + pathspec.items[0].nowildcard_len = pathspec.items[0].len; } else init_pathspec(&pathspec, NULL); if (read_tree(tree, 1, &pathspec)) @@ -406,7 +401,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 }; @@ -460,58 +455,57 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix) struct string_list exclude_list = STRING_LIST_INIT_NODUP; 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", - &exclude_list, "pattern", - "skip files matching pattern", + N_("show resolve-undo information")), + { OPTION_CALLBACK, 'x', "exclude", &exclude_list, 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() }; @@ -579,8 +573,8 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix) die("ls-files --ignored needs some exclude pattern"); /* With no flags, we default to showing the cached files */ - if (!(show_stage | show_deleted | show_others | show_unmerged | - show_killed | show_modified | show_resolve_undo)) + if (!(show_stage || show_deleted || show_others || show_unmerged || + show_killed || show_modified || show_resolve_undo)) show_cached = 1; if (max_prefix) diff --git a/builtin/ls-remote.c b/builtin/ls-remote.c index 41c88a98a2..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 diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c index 6b666e1e87..fb76e38d84 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() }; @@ -168,7 +168,7 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix) init_pathspec(&pathspec, get_pathspec(prefix, argv + 1)); for (i = 0; i < pathspec.nr; i++) - pathspec.items[i].use_wildcard = 0; + pathspec.items[i].nowildcard_len = pathspec.items[i].len; pathspec.has_wildcard = 0; tree = parse_tree_indirect(sha1); if (!tree) diff --git a/builtin/mailinfo.c b/builtin/mailinfo.c index eaf9e157a3..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; } @@ -481,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) @@ -680,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)) @@ -894,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/mailsplit.c b/builtin/mailsplit.c index 2d4327801e..06296d4bdf 100644 --- a/builtin/mailsplit.c +++ b/builtin/mailsplit.c @@ -130,6 +130,27 @@ static int populate_maildir_list(struct string_list *list, const char *path) return 0; } +static int maildir_filename_cmp(const char *a, const char *b) +{ + while (*a && *b) { + if (isdigit(*a) && isdigit(*b)) { + long int na, nb; + na = strtol(a, (char **)&a, 10); + nb = strtol(b, (char **)&b, 10); + if (na != nb) + return na - nb; + /* strtol advanced our pointers */ + } + else { + if (*a != *b) + return (unsigned char)*a - (unsigned char)*b; + a++; + b++; + } + } + return (unsigned char)*a - (unsigned char)*b; +} + static int split_maildir(const char *maildir, const char *dir, int nr_prec, int skip) { @@ -139,6 +160,8 @@ static int split_maildir(const char *maildir, const char *dir, int i; struct string_list list = STRING_LIST_INIT_DUP; + list.cmp = maildir_filename_cmp; + if (populate_maildir_list(&list, maildir) < 0) goto out; diff --git a/builtin/merge-base.c b/builtin/merge-base.c index 4f30f1b0c8..0c4cd2f9f7 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 6f0efef43c..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(), }; diff --git a/builtin/merge-index.c b/builtin/merge-index.c index 2338832587..b416d92849 100644 --- a/builtin/merge-index.c +++ b/builtin/merge-index.c @@ -16,7 +16,7 @@ static int merge_entry(int pos, const char *path) die("git merge-index: %s not in the cache", path); found = 0; do { - struct cache_entry *ce = active_cache[pos]; + const struct cache_entry *ce = active_cache[pos]; int stage = ce_stage(ce); if (strcmp(ce->name, path)) @@ -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)); @@ -58,7 +58,7 @@ static void merge_all(void) { int i; for (i = 0; i < active_nr; i++) { - struct cache_entry *ce = active_cache[i]; + const struct cache_entry *ce = active_cache[i]; if (!ce_stage(ce)) continue; i += merge_entry(i, ce->name)-1; @@ -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..61cbde4094 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) @@ -157,6 +155,11 @@ static int same_entry(struct name_entry *a, struct name_entry *b) a->mode == b->mode; } +static int both_empty(struct name_entry *a, struct name_entry *b) +{ + return !(a->sha1 || b->sha1); +} + static struct merge_list *create_entry(unsigned stage, unsigned mode, const unsigned char *sha1, const char *path) { struct merge_list *res = xcalloc(1, sizeof(*res)); @@ -174,17 +177,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 +195,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 +246,30 @@ 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); + /* + * Treat missing entries as directories so that we return + * after unresolved_directory has handled this. + */ + if (!n[i].mode || S_ISDIR(n[i].mode)) + dirmask |= (1 << i); + } + + unresolved_directory(info, n, dirmask && (dirmask != mask)); - if (unresolved_directory(info, n)) + 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); } @@ -290,40 +306,50 @@ static void unresolved(const struct traverse_info *info, struct name_entry n[3]) static int threeway_callback(int n, unsigned long mask, unsigned long dirmask, struct name_entry *entry, struct traverse_info *info) { /* Same in both? */ - if (same_entry(entry+1, entry+2)) { - if (entry[0].sha1) { - resolve(info, NULL, entry+1); - return mask; - } + if (same_entry(entry+1, entry+2) || both_empty(entry+1, entry+2)) { + /* Modified, added or removed identically */ + resolve(info, NULL, entry+1); + return mask; } 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)) { - resolve(info, NULL, entry+1); - return mask; - } + if (same_entry(entry+0, entry+2) || both_empty(entry+0, entry+2)) { + /* We added, modified or removed, they did not touch -- take ours */ + resolve(info, NULL, entry+1); + return mask; } unresolved(info, entry); 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 e81fde6d79..34a6166b52 100644 --- a/builtin/merge.c +++ b/builtin/merge.c @@ -40,16 +40,16 @@ 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 = -1, squash; -static int option_commit = 1, allow_fast_forward = 1; -static int fast_forward_only, option_edit = -1; -static int allow_trivial = 1, have_message; +static int option_commit = 1; +static int option_edit = -1; +static int allow_trivial = 1, have_message, verify_signatures; static int overwrite_ignore = 1; static struct strbuf merge_msg = STRBUF_INIT; static struct strategy **use_strategies; @@ -76,6 +76,14 @@ static struct strategy all_strategy[] = { static const char *pull_twohead, *pull_octopus; +enum ff_type { + FF_NO, + FF_ALLOW, + FF_ONLY +}; + +static enum ff_type fast_forward = FF_ALLOW; + static int option_parse_message(const struct option *opt, const char *arg, int unset) { @@ -178,41 +186,50 @@ static int option_parse_n(const struct option *opt, return 0; } +static int option_parse_ff_only(const struct option *opt, + const char *arg, int unset) +{ + fast_forward = FF_ONLY; + return 0; +} + static struct option builtin_merge_options[] = { { OPTION_CALLBACK, 'n', NULL, NULL, NULL, - "do not show a diffstat at the end of the merge", + 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, - "edit message before committing"), - OPT_BOOLEAN(0, "ff", &allow_fast_forward, - "allow fast-forward (default)"), - OPT_BOOLEAN(0, "ff-only", &fast_forward_only, - "abort if fast-forward is not possible"), + N_("edit message before committing")), + OPT_SET_INT(0, "ff", &fast_forward, N_("allow fast-forward (default)"), FF_ALLOW), + { OPTION_CALLBACK, 0, "ff-only", NULL, NULL, + N_("abort if fast-forward is not possible"), + PARSE_OPT_NOARG | PARSE_OPT_NONEG, option_parse_ff_only }, 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_BOOL(0, "verify-signatures", &verify_signatures, + N_("Verify that the named commit has a valid GPG signature")), + 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), - { OPTION_STRING, 'S', "gpg-sign", &sign_commit, "key id", - "GPG sign commit", PARSE_OPT_OPTARG, NULL, (intptr_t) "" }, - OPT_BOOLEAN(0, "overwrite-ignore", &overwrite_ignore, "update ignored files (default)"), + 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() }; @@ -516,6 +533,19 @@ static void merge_name(const char *remote, struct strbuf *msg) strbuf_release(&line); goto cleanup; } + + if (remote_head->util) { + struct merge_remote_desc *desc; + desc = merge_remote_util(remote_head); + if (desc && desc->obj && desc->obj->type == OBJ_TAG) { + strbuf_addf(msg, "%s\t\t%s '%s'\n", + sha1_to_hex(desc->obj->sha1), + typename(desc->obj->type), + remote); + goto cleanup; + } + } + strbuf_addf(msg, "%s\t\tcommit '%s'\n", sha1_to_hex(remote_head->object.sha1), remote); cleanup: @@ -566,10 +596,9 @@ static int git_merge_config(const char *k, const char *v, void *cb) else if (!strcmp(k, "merge.ff")) { int boolval = git_config_maybe_bool(k, v); if (0 <= boolval) { - allow_fast_forward = boolval; + fast_forward = boolval ? FF_ALLOW : FF_NO; } else if (v && !strcmp(v, "only")) { - allow_fast_forward = 1; - fast_forward_only = 1; + fast_forward = FF_ONLY; } /* do not barf on values from future versions of git */ return 0; } else if (!strcmp(k, "merge.defaulttoupstream")) { @@ -628,59 +657,6 @@ 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, struct commit_list *remoteheads, struct commit *head, const char *head_arg) @@ -762,56 +738,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)); - if (overwrite_ignore) { - memset(&dir, 0, sizeof(dir)); - dir.flags |= DIR_SHOW_IGNORED; - setup_standard_excludes(&dir); - opts.dir = &dir; - } - - opts.head_idx = 1; - opts.src_index = &the_index; - 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) { @@ -891,20 +817,20 @@ 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" + "Lines starting with '%c' 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)); + strbuf_commented_addf(&msg, _(merge_editor_comment), comment_line_char); write_merge_msg(&msg); - run_hook(get_index_file(), "prepare-commit-msg", - git_path("MERGE_MSG"), "merge", NULL, NULL); + 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); @@ -951,7 +877,7 @@ static int finish_automerge(struct commit *head, free_commit_list(common); parents = remoteheads; - if (!head_subsumed || !allow_fast_forward) + if (!head_subsumed || fast_forward == FF_NO) commit_list_insert(head, &parents); strbuf_addch(&merge_msg, '\n'); prepare_to_commit(remoteheads); @@ -977,7 +903,7 @@ static int suggest_conflicts(int renormalizing) 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]; + const struct cache_entry *ce = active_cache[pos]; if (ce_stage(ce)) { fprintf(fp, "\t%s\n", ce->name); @@ -1036,7 +962,7 @@ static int evaluate_result(void) } /* - * Pretend as if the user told us to merge with the tracking + * Pretend as if the user told us to merge with the remote-tracking * branch we have for the upstream of the current branch */ static int setup_with_upstream(const char ***argv) @@ -1055,7 +981,7 @@ static int setup_with_upstream(const char ***argv) args = xcalloc(branch->merge_nr + 1, sizeof(char *)); for (i = 0; i < branch->merge_nr; i++) { if (!branch->merge[i]->dst) - die(_("No remote tracking branch for %s from %s"), + die(_("No remote-tracking branch for %s from %s"), branch->merge[i]->src, branch->remote_name); args[i] = branch->merge[i]->dst; } @@ -1096,7 +1022,7 @@ static void write_merge_state(struct commit_list *remoteheads) if (fd < 0) die_errno(_("Could not open '%s' for writing"), filename); strbuf_reset(&buf); - if (!allow_fast_forward) + if (fast_forward == FF_NO) strbuf_addf(&buf, "no-ff"); if (write_in_full(fd, buf.buf, buf.len) != buf.len) die_errno(_("Could not write to '%s'"), filename); @@ -1142,7 +1068,8 @@ static struct commit_list *collect_parents(struct commit *head_commit, 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]); + help_unknown_ref(argv[i], "merge", + "not something we can merge"); remotes = &commit_list_insert(commit, remotes)->next; } *remotes = NULL; @@ -1244,14 +1171,11 @@ int cmd_merge(int argc, const char **argv, const char *prefix) show_diffstat = 0; if (squash) { - if (!allow_fast_forward) + if (fast_forward == FF_NO) die(_("You cannot combine --squash with --no-ff.")); option_commit = 0; } - if (!allow_fast_forward && fast_forward_only) - die(_("You cannot combine --no-ff with --ff-only.")); - if (!abort_current_merge) { if (!argc) { if (default_to_upstream) @@ -1293,7 +1217,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) "empty head")); if (squash) die(_("Squash commit into empty head not supported yet")); - if (!allow_fast_forward) + if (fast_forward == FF_NO) die(_("Non-fast-forward commit does not make sense into " "an empty head")); remoteheads = collect_parents(head_commit, &head_subsumed, argc, argv); @@ -1324,6 +1248,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) 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) @@ -1335,6 +1260,39 @@ int cmd_merge(int argc, const char **argv, const char *prefix) usage_with_options(builtin_merge_usage, builtin_merge_options); + if (verify_signatures) { + for (p = remoteheads; p; p = p->next) { + struct commit *commit = p->item; + char hex[41]; + struct signature_check signature_check; + memset(&signature_check, 0, sizeof(signature_check)); + + check_commit_signature(commit, &signature_check); + + strcpy(hex, find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV)); + switch (signature_check.result) { + case 'G': + break; + case 'U': + die(_("Commit %s has an untrusted GPG signature, " + "allegedly by %s."), hex, signature_check.signer); + case 'B': + die(_("Commit %s has a bad GPG signature " + "allegedly by %s."), hex, signature_check.signer); + default: /* 'N' */ + die(_("Commit %s does not have a GPG signature."), hex); + } + if (verbosity >= 0 && signature_check.result == 'G') + printf(_("Commit %s has a good GPG signature by %s\n"), + hex, signature_check.signer); + + free(signature_check.gpg_output); + free(signature_check.gpg_status); + free(signature_check.signer); + free(signature_check.key); + } + } + strbuf_addstr(&buf, "merge"); for (p = remoteheads; p; p = p->next) strbuf_addf(&buf, " %s", merge_remote_util(p->item)->name); @@ -1347,11 +1305,11 @@ int cmd_merge(int argc, const char **argv, const char *prefix) sha1_to_hex(commit->object.sha1)); setenv(buf.buf, merge_remote_util(commit)->name, 1); strbuf_reset(&buf); - if (!fast_forward_only && + if (fast_forward != FF_ONLY && merge_remote_util(commit) && merge_remote_util(commit)->obj && merge_remote_util(commit)->obj->type == OBJ_TAG) - allow_fast_forward = 0; + fast_forward = FF_NO; } if (option_edit < 0) @@ -1368,7 +1326,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) for (i = 0; i < use_strategies_nr; i++) { if (use_strategies[i]->attr & NO_FAST_FORWARD) - allow_fast_forward = 0; + fast_forward = FF_NO; if (use_strategies[i]->attr & NO_TRIVIAL) allow_trivial = 0; } @@ -1398,7 +1356,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) */ finish_up_to_date("Already up-to-date."); goto done; - } else if (allow_fast_forward && !remoteheads->next && + } else if (fast_forward != FF_NO && !remoteheads->next && !common->next && !hashcmp(common->item->object.sha1, head_commit->object.sha1)) { /* Again the most common case of merging one remote. */ @@ -1424,7 +1382,8 @@ int cmd_merge(int argc, const char **argv, const char *prefix) } if (checkout_fast_forward(head_commit->object.sha1, - commit->object.sha1)) { + commit->object.sha1, + overwrite_ignore)) { ret = 1; goto done; } @@ -1444,7 +1403,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) * only one common. */ refresh_cache(REFRESH_QUIET); - if (allow_trivial && !fast_forward_only) { + if (allow_trivial && fast_forward != FF_ONLY) { /* See if it is really trivial. */ git_committer_info(IDENT_STRICT); printf(_("Trying really trivial in-index merge...\n")); @@ -1485,7 +1444,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) } } - if (fast_forward_only) + if (fast_forward == FF_ONLY) die(_("Not possible to fast-forward, aborting.")); /* We are going to make a new commit. */ diff --git a/builtin/mktree.c b/builtin/mktree.c index 4ae1c412d4..f92ba404ab 100644 --- a/builtin/mktree.c +++ b/builtin/mktree.c @@ -64,7 +64,7 @@ static void write_tree(unsigned char *sha1) } static const char *mktree_usage[] = { - "git mktree [-z] [--missing] [--batch]", + N_("git mktree [-z] [--missing] [--batch]"), NULL }; @@ -150,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 2a144b011c..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 }; @@ -59,10 +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__VERBOSE(&verbose, "be verbose"), - OPT__DRY_RUN(&show_only, "dry run"), - OPT__FORCE(&force, "force move/rename even if target exists"), - OPT_BOOLEAN('k', NULL, &ignore_errors, "skip move/rename errors"), + OPT__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; diff --git a/builtin/name-rev.c b/builtin/name-rev.c index 1b374583c2..87d485496f 100644 --- a/builtin/name-rev.c +++ b/builtin/name-rev.c @@ -82,6 +82,20 @@ copy_data: } } +static int subpath_matches(const char *path, const char *filter) +{ + const char *subpath = path; + + while (subpath) { + if (!fnmatch(filter, subpath, 0)) + return subpath - path; + subpath = strchr(subpath, '/'); + if (subpath) + subpath++; + } + return -1; +} + struct name_ref_data { int tags_only; int name_only; @@ -92,13 +106,23 @@ static int name_ref(const char *path, const unsigned char *sha1, int flags, void { struct object *o = parse_object(sha1); struct name_ref_data *data = cb_data; + int can_abbreviate_output = data->tags_only && data->name_only; int deref = 0; if (data->tags_only && prefixcmp(path, "refs/tags/")) return 0; - if (data->ref_filter && fnmatch(data->ref_filter, path, 0)) - return 0; + if (data->ref_filter) { + switch (subpath_matches(path, data->ref_filter)) { + case -1: /* did not match */ + return 0; + case 0: /* matched fully */ + break; + default: /* matched subpath */ + can_abbreviate_output = 1; + break; + } + } while (o && o->type == OBJ_TAG) { struct tag *t = (struct tag *) o; @@ -110,12 +134,10 @@ static int name_ref(const char *path, const unsigned char *sha1, int flags, void if (o && o->type == OBJ_COMMIT) { struct commit *commit = (struct commit *)o; - if (!prefixcmp(path, "refs/heads/")) + if (can_abbreviate_output) + path = shorten_unambiguous_ref(path, 0); + else if (!prefixcmp(path, "refs/heads/")) path = path + 11; - else if (data->tags_only - && data->name_only - && !prefixcmp(path, "refs/tags/")) - path = path + 10; else if (!prefixcmp(path, "refs/")) path = path + 5; @@ -172,9 +194,9 @@ static void show_name(const struct object *obj, } static char const * const name_rev_usage[] = { - "git name-rev [options] <commit>...", - "git name-rev [options] --all", - "git name-rev [options] --stdin", + N_("git name-rev [options] <commit>..."), + N_("git name-rev [options] --all"), + N_("git name-rev [options] --stdin"), NULL }; @@ -226,16 +248,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 3644d140ec..e4100c4982 100644 --- a/builtin/notes.c +++ b/builtin/notes.c @@ -4,7 +4,7 @@ * Copyright (c) 2010 Johan Herland <johan@herland.net> * * Based on git-notes.sh by Johannes Schindelin, - * and builtin-tag.c by Kristian Høgsberg and Carlos Rica. + * and builtin/tag.c by Kristian Høgsberg and Carlos Rica. */ #include "cache.h" @@ -18,81 +18,79 @@ #include "parse-options.h" #include "string-list.h" #include "notes-merge.h" +#include "notes-utils.h" 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 }; static const char note_template[] = - "\n" - "#\n" - "# Write/edit the notes for the following object:\n" - "#\n"; + "\nWrite/edit the notes for the following object:\n"; struct msg_arg { int given; @@ -126,7 +124,7 @@ static void write_commented_object(int fd, const unsigned char *object) {"show", "--stat", "--no-notes", sha1_to_hex(object), NULL}; struct child_process show; struct strbuf buf = STRBUF_INIT; - FILE *show_out; + struct strbuf cbuf = STRBUF_INIT; /* Invoke "git show --stat --no-notes $object" */ memset(&show, 0, sizeof(show)); @@ -139,21 +137,14 @@ static void write_commented_object(int fd, const unsigned char *object) die(_("unable to start 'show' for object '%s'"), sha1_to_hex(object)); - /* Open the output as FILE* so strbuf_getline() can be used. */ - show_out = xfdopen(show.out, "r"); - if (show_out == NULL) - die_errno(_("can't fdopen 'show' output fd")); + if (strbuf_read(&buf, show.out, 0) < 0) + die_errno(_("could not read 'show' output")); + strbuf_add_commented_lines(&cbuf, buf.buf, buf.len); + write_or_die(fd, cbuf.buf, cbuf.len); - /* Prepend "# " to each output line and write result to 'fd' */ - while (strbuf_getline(&buf, show_out, '\n') != EOF) { - write_or_die(fd, "# ", 2); - write_or_die(fd, buf.buf, buf.len); - write_or_die(fd, "\n", 1); - } + strbuf_release(&cbuf); strbuf_release(&buf); - if (fclose(show_out)) - die_errno(_("failed to close pipe to 'show' for object '%s'"), - sha1_to_hex(object)); + if (finish_command(&show)) die(_("failed to finish 'show' for object '%s'"), sha1_to_hex(object)); @@ -167,6 +158,7 @@ static void create_note(const unsigned char *object, struct msg_arg *msg, if (msg->use_editor || !msg->given) { int fd; + struct strbuf buf = STRBUF_INIT; /* write the template message before editing: */ path = git_pathdup("NOTES_EDITMSG"); @@ -178,11 +170,16 @@ static void create_note(const unsigned char *object, struct msg_arg *msg, write_or_die(fd, msg->buf.buf, msg->buf.len); else if (prev && !append_only) write_note_data(fd, prev); - write_or_die(fd, note_template, strlen(note_template)); + + strbuf_addch(&buf, '\n'); + strbuf_add_commented_lines(&buf, note_template, strlen(note_template)); + strbuf_addch(&buf, '\n'); + write_or_die(fd, buf.buf, buf.len); write_commented_object(fd, object); close(fd); + strbuf_release(&buf); strbuf_reset(&(msg->buf)); if (launch_editor(path, &(msg->buf), NULL)) { @@ -288,139 +285,13 @@ 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) -{ - struct strbuf buf = STRBUF_INIT; - unsigned char commit_sha1[20]; - - if (!t) - t = &default_notes_tree; - if (!t->initialized || !t->ref || !*t->ref) - die(_("Cannot commit uninitialized/unreferenced notes tree")); - if (!t->dirty) - return; /* don't have to commit an unchanged tree */ - - /* Prepare commit message and reflog message */ - 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, 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) -{ - if (!strcasecmp(v, "overwrite")) - return combine_notes_overwrite; - else if (!strcasecmp(v, "ignore")) - return combine_notes_ignore; - else if (!strcasecmp(v, "concatenate")) - return combine_notes_concatenate; - else if (!strcasecmp(v, "cat_sort_uniq")) - return combine_notes_cat_sort_uniq; - else - return NULL; -} - -static int notes_rewrite_config(const char *k, const char *v, void *cb) -{ - struct notes_rewrite_cfg *c = cb; - if (!prefixcmp(k, "notes.rewrite.") && !strcmp(k+14, c->cmd)) { - c->enabled = git_config_bool(k, v); - return 0; - } else if (!c->mode_from_env && !strcmp(k, "notes.rewritemode")) { - if (!v) - config_error_nonbool(k); - c->combine = parse_combine_notes_fn(v); - if (!c->combine) { - error(_("Bad notes.rewriteMode value: '%s'"), v); - return 1; - } - return 0; - } else if (!c->refs_from_env && !strcmp(k, "notes.rewriteref")) { - /* note that a refs/ prefix is implied in the - * underlying for_each_glob_ref */ - if (!prefixcmp(v, "refs/notes/")) - string_list_add_refs_by_glob(c->refs, v); - else - warning(_("Refusing to rewrite notes in %s" - " (outside of refs/notes/)"), v); - return 0; - } - - return 0; -} - - -struct notes_rewrite_cfg *init_copy_notes_for_rewrite(const char *cmd) -{ - struct notes_rewrite_cfg *c = xmalloc(sizeof(struct notes_rewrite_cfg)); - const char *rewrite_mode_env = getenv(GIT_NOTES_REWRITE_MODE_ENVIRONMENT); - const char *rewrite_refs_env = getenv(GIT_NOTES_REWRITE_REF_ENVIRONMENT); - c->cmd = cmd; - c->enabled = 1; - c->combine = combine_notes_concatenate; - c->refs = xcalloc(1, sizeof(struct string_list)); - c->refs->strdup_strings = 1; - c->refs_from_env = 0; - c->mode_from_env = 0; - if (rewrite_mode_env) { - c->mode_from_env = 1; - c->combine = parse_combine_notes_fn(rewrite_mode_env); - if (!c->combine) - /* TRANSLATORS: The first %s is the name of the - environment variable, the second %s is its value */ - error(_("Bad %s value: '%s'"), GIT_NOTES_REWRITE_MODE_ENVIRONMENT, - rewrite_mode_env); - } - if (rewrite_refs_env) { - c->refs_from_env = 1; - string_list_add_refs_from_colon_sep(c->refs, rewrite_refs_env); - } - git_config(notes_rewrite_config, c); - if (!c->enabled || !c->refs->nr) { - string_list_clear(c->refs, 0); - free(c->refs); - free(c); - return NULL; - } - c->trees = load_notes_trees(c->refs); - string_list_clear(c->refs, 0); - free(c->refs); - return c; -} - -int copy_note_for_rewrite(struct notes_rewrite_cfg *c, - const unsigned char *from_obj, const unsigned char *to_obj) -{ - int ret = 0; - int i; - for (i = 0; c->trees[i]; i++) - ret = copy_note(c->trees[i], from_obj, to_obj, 1, c->combine) || ret; - return ret; -} - -void finish_copy_notes_for_rewrite(struct notes_rewrite_cfg *c) -{ - int i; - for (i = 0; c->trees[i]; i++) { - commit_notes(c->trees[i], "Notes added by 'git notes copy'"); - free_notes(c->trees[i]); - } - free(c->trees); - free(c); -} - static int notes_copy_from_stdin(int force, const char *rewrite_cmd) { struct strbuf buf = STRBUF_INIT; struct notes_rewrite_cfg *c = NULL; struct notes_tree *t = NULL; int ret = 0; + const char *msg = "Notes added by 'git notes copy'"; if (rewrite_cmd) { c = init_copy_notes_for_rewrite(rewrite_cmd); @@ -462,10 +333,10 @@ static int notes_copy_from_stdin(int force, const char *rewrite_cmd) } if (!rewrite_cmd) { - commit_notes(t, "Notes added by 'git notes copy'"); + commit_notes(t, msg); free_notes(t); } else { - finish_copy_notes_for_rewrite(c); + finish_copy_notes_for_rewrite(c, msg); } return ret; } @@ -531,19 +402,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 +482,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 +559,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() }; @@ -861,19 +732,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() }; @@ -980,10 +851,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; @@ -1064,8 +935,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 ccfcbad146..f069462cb0 100644 --- a/builtin/pack-objects.c +++ b/builtin/pack-objects.c @@ -16,11 +16,12 @@ #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 --stdout [options...] [< ref-list | < object-list]", - "git pack-objects [options...] base-name [< ref-list | < object-list]", + N_("git pack-objects --stdout [options...] [< ref-list | < object-list]"), + N_("git pack-objects [options...] base-name [< ref-list | < object-list]"), NULL }; @@ -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. @@ -208,11 +249,18 @@ static unsigned long write_no_reuse_object(struct sha1file *f, struct object_ent unsigned hdrlen; enum object_type type; void *buf; + struct git_istream *st = NULL; 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)); + 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. @@ -233,7 +281,9 @@ static unsigned long write_no_reuse_object(struct sha1file *f, struct object_ent OBJ_OFS_DELTA : OBJ_REF_DELTA; } - if (entry->z_delta_size) + 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); @@ -256,6 +306,8 @@ static unsigned long write_no_reuse_object(struct sha1file *f, struct object_ent 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; } @@ -268,6 +320,8 @@ static unsigned long write_no_reuse_object(struct sha1file *f, struct object_ent * an additional 20 bytes for the base sha1. */ if (limit && hdrlen + 20 + datalen + 20 >= limit) { + if (st) + close_istream(st); free(buf); return 0; } @@ -276,13 +330,20 @@ static unsigned long write_no_reuse_object(struct sha1file *f, struct object_ent hdrlen += 20; } else { if (limit && hdrlen + datalen + 20 >= limit) { + if (st) + close_istream(st); free(buf); return 0; } sha1write(f, header, hdrlen); } - sha1write(f, buf, datalen); - free(buf); + 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; } @@ -1972,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; @@ -2312,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); } @@ -2384,67 +2444,67 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) int rev_list_unpacked = 0, rev_list_all = 0, rev_list_reflog = 0; struct option pack_objects_options[] = { OPT_SET_INT('q', "quiet", &progress, - "do not show progress meter", 0), + N_("do not show progress meter"), 0), OPT_SET_INT(0, "progress", &progress, - "show progress meter", 1), + N_("show progress meter"), 1), OPT_SET_INT(0, "all-progress", &progress, - "show progress meter during object writing phase", 2), + N_("show progress meter during object writing phase"), 2), OPT_BOOL(0, "all-progress-implied", &all_progress_implied, - "similar to --all-progress when progress meter is shown"), - { OPTION_CALLBACK, 0, "index-version", NULL, "version[,offset]", - "write the pack index file in the specified idx format version", + 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, - "maximum size of each output pack file"), + N_("maximum size of each output pack file")), OPT_BOOL(0, "local", &local, - "ignore borrowed objects from alternate object store"), + N_("ignore borrowed objects from alternate object store")), OPT_BOOL(0, "incremental", &incremental, - "ignore packed objects"), + N_("ignore packed objects")), OPT_INTEGER(0, "window", &window, - "limit pack window by objects"), + N_("limit pack window by objects")), OPT_ULONG(0, "window-memory", &window_memory_limit, - "limit pack window by memory in addition to object limit"), + N_("limit pack window by memory in addition to object limit")), OPT_INTEGER(0, "depth", &depth, - "maximum length of delta chain allowed in the resulting pack"), + N_("maximum length of delta chain allowed in the resulting pack")), OPT_BOOL(0, "reuse-delta", &reuse_delta, - "reuse existing deltas"), + N_("reuse existing deltas")), OPT_BOOL(0, "reuse-object", &reuse_object, - "reuse existing objects"), + N_("reuse existing objects")), OPT_BOOL(0, "delta-base-offset", &allow_ofs_delta, - "use OFS_DELTA objects"), + N_("use OFS_DELTA objects")), OPT_INTEGER(0, "threads", &delta_search_threads, - "use threads when searching for best delta matches"), + N_("use threads when searching for best delta matches")), OPT_BOOL(0, "non-empty", &non_empty, - "do not create an empty pack output"), + N_("do not create an empty pack output")), OPT_BOOL(0, "revs", &use_internal_rev_list, - "read revision arguments from standard input"), + N_("read revision arguments from standard input")), { OPTION_SET_INT, 0, "unpacked", &rev_list_unpacked, NULL, - "limit the objects to those that are not yet packed", + 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, - "include objects reachable from any reference", + N_("include objects reachable from any reference"), PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, 1 }, { OPTION_SET_INT, 0, "reflog", &rev_list_reflog, NULL, - "include objects referred by reflog entries", + N_("include objects referred by reflog entries"), PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, 1 }, OPT_BOOL(0, "stdout", &pack_to_stdout, - "output pack to stdout"), + N_("output pack to stdout")), OPT_BOOL(0, "include-tag", &include_tag, - "include tag objects that refer to objects to be packed"), + N_("include tag objects that refer to objects to be packed")), OPT_BOOL(0, "keep-unreachable", &keep_unreachable, - "keep unreachable objects"), - { OPTION_CALLBACK, 0, "unpack-unreachable", NULL, "time", - "unpack unreachable objects newer than <time>", + 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, - "create thin packs"), + N_("create thin packs")), OPT_BOOL(0, "honor-pack-keep", &ignore_packed_keep, - "ignore packs that have companion .keep file"), + N_("ignore packs that have companion .keep file")), OPT_INTEGER(0, "compression", &pack_compression_level, - "pack compression level"), + N_("pack compression level")), OPT_SET_INT(0, "keep-true-parents", &grafts_replace_parents, - "do not hide commits by grafts", 0), + N_("do not hide commits by grafts"), 0), OPT_END(), }; 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..b20b1ec4c1 100644 --- a/builtin/pack-refs.c +++ b/builtin/pack-refs.c @@ -1,9 +1,9 @@ #include "builtin.h" #include "parse-options.h" -#include "pack-refs.h" +#include "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/prune-packed.c b/builtin/prune-packed.c index b58a2e1eb2..fa6ce42f44 100644 --- a/builtin/prune-packed.c +++ b/builtin/prune-packed.c @@ -4,13 +4,10 @@ #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 }; -#define DRY_RUN 01 -#define VERBOSE 02 - static struct progress *progress; static void prune_dir(int i, DIR *dir, char *pathname, int len, int opts) @@ -29,7 +26,7 @@ static void prune_dir(int i, DIR *dir, char *pathname, int len, int opts) if (!has_sha1_pack(sha1)) continue; memcpy(pathname + len, de->d_name, 38); - if (opts & DRY_RUN) + if (opts & PRUNE_PACKED_DRY_RUN) printf("rm -f %s\n", pathname); else unlink_or_warn(pathname); @@ -44,7 +41,7 @@ void prune_packed_objects(int opts) const char *dir = get_object_directory(); int len = strlen(dir); - if (opts == VERBOSE) + if (opts & PRUNE_PACKED_VERBOSE) progress = start_progress_delay("Removing duplicate objects", 256, 95, 2); @@ -71,10 +68,12 @@ void prune_packed_objects(int opts) int cmd_prune_packed(int argc, const char **argv, const char *prefix) { - int opts = isatty(2) ? VERBOSE : 0; + int opts = isatty(2) ? PRUNE_PACKED_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"), + PRUNE_PACKED_DRY_RUN), + OPT_NEGBIT('q', "quiet", &opts, N_("be quiet"), + PRUNE_PACKED_VERBOSE), OPT_END() }; diff --git a/builtin/prune.c b/builtin/prune.c index b99b635e44..6366917c6d 100644 --- a/builtin/prune.c +++ b/builtin/prune.c @@ -9,7 +9,7 @@ #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; @@ -25,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; @@ -128,11 +129,11 @@ 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_BOOL(0, "progress", &show_progress, "show progress"), - OPT_DATE(0, "expire", &expire, - "expire objects older than <time>"), + 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_EXPIRY_DATE(0, "expire", &expire, + N_("expire objects older than <time>")), OPT_END() }; char *s; @@ -148,9 +149,7 @@ int cmd_prune(int argc, const char **argv, const char *prefix) const char *name = *argv++; if (!get_sha1(name, sha1)) { - struct object *object = parse_object(sha1); - if (!object) - die("bad object: %s", name); + struct object *object = parse_object_or_die(sha1, name); add_pending_object(&revs, object, ""); } else @@ -166,9 +165,9 @@ int cmd_prune(int argc, const char **argv, const char *prefix) stop_progress(&progress); prune_object_dir(get_object_directory()); - prune_packed_objects(show_only); + prune_packed_objects(show_only ? PRUNE_PACKED_DRY_RUN : 0); 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 fdfcc6c716..6d36c24268 100644 --- a/builtin/push.c +++ b/builtin/push.c @@ -11,7 +11,7 @@ #include "submodule.h" static const char * const push_usage[] = { - "git push [<options>] [<repository> [<refspec>...]]", + N_("git push [<options>] [<repository> [<refspec>...]]"), NULL, }; @@ -113,17 +113,20 @@ static NORETURN int die_push_simple(struct branch *branch, struct remote *remote remote->name, branch->name, advice_maybe); } -static void setup_push_upstream(struct remote *remote, int simple) +static const char message_detached_head_die[] = + N_("You are not currently on a branch.\n" + "To push the history leading to the current (detached HEAD)\n" + "state now, use\n" + "\n" + " git push %s HEAD:<name-of-remote-branch>\n"); + +static void setup_push_upstream(struct remote *remote, struct branch *branch, + int triangular) { struct strbuf refspec = STRBUF_INIT; - struct branch *branch = branch_get(NULL); + if (!branch) - die(_("You are not currently on a branch.\n" - "To push the history leading to the current (detached HEAD)\n" - "state now, use\n" - "\n" - " git push %s HEAD:<name-of-remote-branch>\n"), - remote->name); + die(_(message_detached_head_die), remote->name); if (!branch->merge_nr || !branch->merge || !branch->remote_name) die(_("The current branch %s has no upstream branch.\n" "To push the current branch and set the remote as upstream, use\n" @@ -135,39 +138,87 @@ static void setup_push_upstream(struct remote *remote, int simple) 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)) + if (triangular) 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); + + if (push_default == PUSH_DEFAULT_SIMPLE) { + /* Additional safety */ + if (strcmp(branch->refname, branch->merge[0]->src)) + die_push_simple(branch, remote); + } strbuf_addf(&refspec, "%s:%s", branch->name, branch->merge[0]->src); add_refspec(refspec.buf); } +static void setup_push_current(struct remote *remote, struct branch *branch) +{ + if (!branch) + die(_(message_detached_head_die), remote->name); + add_refspec(branch->name); +} + +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 int is_workflow_triangular(struct remote *remote) +{ + struct remote *fetch_remote = remote_get(NULL); + return (fetch_remote && fetch_remote != remote); +} + static void setup_default_push_refspecs(struct remote *remote) { + struct branch *branch = branch_get(NULL); + int triangular = is_workflow_triangular(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); + if (triangular) + setup_push_current(remote, branch); + else + setup_push_upstream(remote, branch, triangular); break; case PUSH_DEFAULT_UPSTREAM: - setup_push_upstream(remote, 0); + setup_push_upstream(remote, branch, triangular); break; case PUSH_DEFAULT_CURRENT: - add_refspec("HEAD"); + setup_push_current(remote, branch); break; case PUSH_DEFAULT_NOTHING: @@ -179,47 +230,83 @@ 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" + "its remote counterpart. Integrate the remote changes (e.g.\n" + "'git pull ...') before pushing again.\n" "See the 'Note about fast-forwards' in 'git push --help' for details."); static const char message_advice_use_upstream[] = N_("Updates were rejected because a pushed branch tip is behind its remote\n" "counterpart. If you did not intend to push that branch, you may want to\n" - "specify branches to push or set the 'push.default' configuration\n" - "variable to 'current' or 'upstream' to push only the current branch."); + "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" + "counterpart. Check out this branch and integrate 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 const char message_advice_ref_fetch_first[] = + N_("Updates were rejected because the remote contains work that you do\n" + "not have locally. This is usually caused by another repository pushing\n" + "to the same ref. You may want to first integrate 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 const char message_advice_ref_already_exists[] = + N_("Updates were rejected because the tag already exists in the remote."); + +static const char message_advice_ref_needs_force[] = + N_("You cannot update a remote ref that points at a non-commit object,\n" + "or update a remote ref to make it point at a non-commit object,\n" + "without using the '--force' option.\n"); + static void advise_pull_before_push(void) { - if (!advice_push_non_ff_current || !advice_push_nonfastforward) + if (!advice_push_non_ff_current || !advice_push_update_rejected) return; advise(_(message_advice_pull_before_push)); } static void advise_use_upstream(void) { - if (!advice_push_non_ff_default || !advice_push_nonfastforward) + if (!advice_push_non_ff_default || !advice_push_update_rejected) return; advise(_(message_advice_use_upstream)); } static void advise_checkout_pull_push(void) { - if (!advice_push_non_ff_matching || !advice_push_nonfastforward) + if (!advice_push_non_ff_matching || !advice_push_update_rejected) return; advise(_(message_advice_checkout_pull_push)); } +static void advise_ref_already_exists(void) +{ + if (!advice_push_already_exists || !advice_push_update_rejected) + return; + advise(_(message_advice_ref_already_exists)); +} + +static void advise_ref_fetch_first(void) +{ + if (!advice_push_fetch_first || !advice_push_update_rejected) + return; + advise(_(message_advice_ref_fetch_first)); +} + +static void advise_ref_needs_force(void) +{ + if (!advice_push_needs_force || !advice_push_update_rejected) + return; + advise(_(message_advice_ref_needs_force)); +} + static int push_with_options(struct transport *transport, int flags) { int err; - int nonfastforward; + unsigned int reject_reasons; transport_set_verbosity(transport, verbosity, progress); @@ -232,7 +319,7 @@ static int push_with_options(struct transport *transport, int flags) if (verbosity > 0) fprintf(stderr, _("Pushing to %s\n"), transport->url); err = transport_push(transport, refspec_nr, refspec, flags, - &nonfastforward); + &reject_reasons); if (err != 0) error(_("failed to push some refs to '%s'"), transport->url); @@ -240,18 +327,19 @@ static int push_with_options(struct transport *transport, int flags) if (!err) return 0; - switch (nonfastforward) { - default: - break; - case NON_FF_HEAD: + if (reject_reasons & REJECT_NON_FF_HEAD) { advise_pull_before_push(); - break; - case NON_FF_OTHER: + } else if (reject_reasons & REJECT_NON_FF_OTHER) { if (default_matching_used) advise_use_upstream(); else advise_checkout_pull_push(); - break; + } else if (reject_reasons & REJECT_ALREADY_EXISTS) { + advise_ref_already_exists(); + } else if (reject_reasons & REJECT_FETCH_FIRST) { + advise_ref_fetch_first(); + } else if (reject_reasons & REJECT_NEEDS_FORCE) { + advise_ref_needs_force(); } return 1; @@ -260,7 +348,7 @@ static int push_with_options(struct transport *transport, int flags) static int do_push(const char *repo, int flags) { int i, errs; - struct remote *remote = remote_get(repo); + struct remote *remote = pushremote_get(repo); const char **url; int url_nr; @@ -354,26 +442,29 @@ 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), - { OPTION_CALLBACK, 0, "recurse-submodules", &flags, "check", - "controls recursive pushing of submodules", + 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, "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 , "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_BOOL(0, "progress", &progress, "force progress reporting"), - OPT_BIT(0, "prune", &flags, "prune locally removed refs", + OPT_BOOL(0, "progress", &progress, N_("force progress reporting")), + OPT_BIT(0, "prune", &flags, N_("prune locally removed refs"), TRANSPORT_PUSH_PRUNE), + OPT_BIT(0, "no-verify", &flags, N_("bypass pre-push hook"), TRANSPORT_PUSH_NO_HOOK), + OPT_BIT(0, "follow-tags", &flags, N_("push missing but relevant tags"), + TRANSPORT_PUSH_FOLLOW_TAGS), OPT_END() }; diff --git a/builtin/read-tree.c b/builtin/read-tree.c index df6c4c8819..0f5d7fe23f 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 }; @@ -66,7 +66,7 @@ static int exclude_per_directory_cb(const struct option *opt, const char *arg, return 0; } -static void debug_stage(const char *label, struct cache_entry *ce, +static void debug_stage(const char *label, const struct cache_entry *ce, struct unpack_trees_options *o) { printf("%s ", label); @@ -80,7 +80,8 @@ static void debug_stage(const char *label, struct cache_entry *ce, sha1_to_hex(ce->sha1)); } -static int debug_merge(struct cache_entry **stages, struct unpack_trees_options *o) +static int debug_merge(const struct cache_entry * const *stages, + struct unpack_trees_options *o) { int i; @@ -104,37 +105,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 3f05d971ec..e3eb5fc058 100644 --- a/builtin/receive-pack.c +++ b/builtin/receive-pack.c @@ -12,6 +12,7 @@ #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>"; @@ -58,6 +59,11 @@ static enum deny_action parse_deny_action(const char *var, const char *value) static int receive_pack_config(const char *var, const char *value, void *cb) { + int status = parse_hide_refs_config(var, value, "receive"); + + if (status) + return status; + if (strcmp(var, "receive.denydeletes") == 0) { deny_deletes = git_config_bool(var, value); return 0; @@ -118,13 +124,17 @@ static int receive_pack_config(const char *var, const char *value, void *cb) static void show_ref(const char *path, const unsigned char *sha1) { + if (ref_is_hidden(path)) + return; + 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 quiet", - prefer_ofs_delta ? " ofs-delta" : ""); + prefer_ofs_delta ? " ofs-delta" : "", + git_user_agent_sanitized()); sent_capabilities = 1; } @@ -180,9 +190,6 @@ struct command { char ref_name[FLEX_ARRAY]; /* more */ }; -static const char pre_receive_hook[] = "hooks/pre-receive"; -static const char post_receive_hook[] = "hooks/post-receive"; - static void rp_error(const char *err, ...) __attribute__((format (printf, 1, 2))); static void rp_warning(const char *err, ...) __attribute__((format (printf, 1, 2))); @@ -240,10 +247,10 @@ static int run_and_feed_hook(const char *hook_name, feed_fn feed, void *feed_sta const char *argv[2]; int code; - if (access(hook_name, X_OK) < 0) + argv[0] = find_hook(hook_name); + if (!argv[0]) return 0; - argv[0] = hook_name; argv[1] = NULL; memset(&proc, 0, sizeof(proc)); @@ -329,15 +336,14 @@ static int run_receive_hook(struct command *commands, const char *hook_name, static int run_update_hook(struct command *cmd) { - static const char update_hook[] = "hooks/update"; const char *argv[5]; struct child_process proc; int code; - if (access(update_hook, X_OK) < 0) + argv[0] = find_hook("update"); + if (!argv[0]) return 0; - argv[0] = update_hook; argv[1] = cmd->ref_name; argv[2] = sha1_to_hex(cmd->old_sha1); argv[3] = sha1_to_hex(cmd->new_sha1); @@ -478,7 +484,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); @@ -491,12 +496,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"; @@ -536,24 +536,25 @@ static const char *update(struct command *cmd) } } -static char update_post_hook[] = "hooks/post-update"; - static void run_update_post_hook(struct command *commands) { struct command *cmd; int argc; const char **argv; struct child_process proc; + char *hook; + hook = find_hook("post-update"); for (argc = 0, cmd = commands; cmd; cmd = cmd->next) { if (cmd->error_string || cmd->did_not_exist) continue; argc++; } - if (!argc || access(update_post_hook, X_OK) < 0) + if (!argc || !hook) return; + argv = xmalloc(sizeof(*argv) * (2 + argc)); - argv[0] = update_post_hook; + argv[0] = hook; for (argc = 1, cmd = commands; cmd; cmd = cmd->next) { char *p; @@ -692,6 +693,20 @@ static int iterate_receive_command_list(void *cb_data, unsigned char sha1[20]) return -1; /* end of list */ } +static void reject_updates_to_hidden(struct command *commands) +{ + struct command *cmd; + + for (cmd = commands; cmd; cmd = cmd->next) { + if (cmd->error_string || !ref_is_hidden(cmd->ref_name)) + continue; + if (is_null_sha1(cmd->new_sha1)) + cmd->error_string = "deny deleting a hidden ref"; + else + cmd->error_string = "deny updating a hidden ref"; + } +} + static void execute_commands(struct command *commands, const char *unpacker_error) { struct command *cmd; @@ -699,7 +714,7 @@ 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; } @@ -708,7 +723,9 @@ static void execute_commands(struct command *commands, const char *unpacker_erro 0, &cmd)) set_connectivity_errors(commands); - if (run_receive_hook(commands, pre_receive_hook, 0)) { + reject_updates_to_hidden(commands); + + if (run_receive_hook(commands, "pre-receive", 0)) { for (cmd = commands; cmd; cmd = cmd->next) { if (!cmd->error_string) cmd->error_string = "pre-receive hook declined"; @@ -737,17 +754,15 @@ static struct command *read_head_info(void) struct command *commands = NULL; struct command **p = &commands; for (;;) { - static char line[1000]; + char *line; unsigned char old_sha1[20], new_sha1[20]; struct command *cmd; char *refname; int len, reflen; - len = packet_read_line(0, line, sizeof(line)); - if (!len) + line = packet_read_line(0, &len); + if (!line) break; - if (line[len-1] == '\n') - line[--len] = 0; if (len < 83 || line[40] != ' ' || line[81] != ' ' || @@ -799,7 +814,7 @@ 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; @@ -811,14 +826,18 @@ static const char *unpack(void) : 0); hdr_err = parse_pack_header(&hdr); - if (hdr_err) + if (hdr_err) { + if (err_fd > 0) + close(err_fd); return hdr_err; + } snprintf(hdr_arg, sizeof(hdr_arg), "--pack_header=%"PRIu32",%"PRIu32, ntohl(hdr.hdr_version), ntohl(hdr.hdr_entries)); if (ntohl(hdr.hdr_entries) < unpack_limit) { int code, i = 0; + struct child_process child; const char *unpacker[5]; unpacker[i++] = "unpack-objects"; if (quiet) @@ -827,7 +846,12 @@ static const char *unpack(void) 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"; @@ -852,6 +876,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) { @@ -868,6 +893,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; @@ -888,7 +933,7 @@ static void report(struct command *commands, const char *unpack_status) if (use_sideband) send_sideband(1, 1, buf.buf, buf.len, use_sideband); else - safe_write(1, buf.buf, buf.len); + write_or_die(1, buf.buf, buf.len); strbuf_release(&buf); } @@ -965,13 +1010,13 @@ 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, 1); + run_receive_hook(commands, "post-receive", 1); run_update_post_hook(commands); if (auto_gc) { const char *argv_gc_auto[] = { diff --git a/builtin/reflog.c b/builtin/reflog.c index 062d7dad1b..54184b3d13 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; } @@ -412,7 +414,7 @@ static int expire_reflog(const char *ref, const unsigned char *sha1, int unused, if (cb.unreachable_expire_kind == UE_HEAD) { struct commit_list *elem; for (elem = tips; elem; elem = elem->next) - clear_commit_marks(tip_commit, REACHABLE); + clear_commit_marks(elem->item, REACHABLE); free_commit_list(tips); } else { clear_commit_marks(tip_commit, REACHABLE); @@ -494,11 +496,9 @@ static int parse_expire_cfg_value(const char *var, const char *value, unsigned l { if (!value) return config_error_nonbool(var); - if (!strcmp(value, "never") || !strcmp(value, "false")) { - *expire = 0; - return 0; - } - *expire = approxidate(value); + if (parse_expiry_date(value, expire)) + return error(_("%s' for '%s' is not a valid timestamp"), + value, var); return 0; } @@ -508,26 +508,27 @@ static int parse_expire_cfg_value(const char *var, const char *value, unsigned l static int reflog_expire_config(const char *var, const char *value, void *cb) { - const char *lastdot = strrchr(var, '.'); + const char *pattern, *key; + int pattern_len; unsigned long expire; int slot; struct reflog_expire_cfg *ent; - if (!lastdot || prefixcmp(var, "gc.")) + if (parse_config_key(var, "gc", &pattern, &pattern_len, &key) < 0) return git_default_config(var, value, cb); - if (!strcmp(lastdot, ".reflogexpire")) { + if (!strcmp(key, "reflogexpire")) { slot = EXPIRE_TOTAL; if (parse_expire_cfg_value(var, value, &expire)) return -1; - } else if (!strcmp(lastdot, ".reflogexpireunreachable")) { + } else if (!strcmp(key, "reflogexpireunreachable")) { slot = EXPIRE_UNREACH; if (parse_expire_cfg_value(var, value, &expire)) return -1; } else return git_default_config(var, value, cb); - if (lastdot == var + 2) { + if (!pattern) { switch (slot) { case EXPIRE_TOTAL: default_reflog_expire = expire; @@ -539,7 +540,7 @@ static int reflog_expire_config(const char *var, const char *value, void *cb) return 0; } - ent = find_cfg_ent(var + 3, lastdot - (var+3)); + ent = find_cfg_ent(pattern, pattern_len); if (!ent) return -1; switch (slot) { @@ -611,11 +612,13 @@ static int cmd_reflog_expire(int argc, const char **argv, const char *prefix) if (!strcmp(arg, "--dry-run") || !strcmp(arg, "-n")) cb.dry_run = 1; else if (!prefixcmp(arg, "--expire=")) { - cb.expire_total = approxidate(arg + 9); + if (parse_expiry_date(arg + 9, &cb.expire_total)) + die(_("'%s' is not a valid timestamp"), arg); explicit_expiry |= EXPIRE_TOTAL; } else if (!prefixcmp(arg, "--expire-unreachable=")) { - cb.expire_unreachable = approxidate(arg + 21); + if (parse_expiry_date(arg + 21, &cb.expire_unreachable)) + die(_("'%s' is not a valid timestamp"), arg); explicit_expiry |= EXPIRE_UNREACH; } else if (!strcmp(arg, "--stale-fix")) diff --git a/builtin/remote.c b/builtin/remote.c index 920262d76e..5e54d367b8 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] [--tags|--no-tags] [--mirror=<fetch|push>] <name> <url>", - "git remote rename <old> <new>", - "git remote rm <name>", - "git remote set-head <name> (-a | -d | <branch>)", - "git remote [-v | --verbose] show [-n] <name>", - "git remote prune [-n | --dry-run] <name>", - "git remote [-v | --verbose] update [-p | --prune] [(<group> | <remote>)...]", - "git remote set-branches [--add] <name> <branch>...", - "git remote set-url [--push] <name> <newurl> [<oldurl>]", - "git remote set-url --add <name> <newurl>", - "git remote set-url --delete <name> <url>", + 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 }; @@ -160,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_STRING_LIST('t', "track", &track, "branch", - "branch(es) to 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() }; @@ -178,7 +178,7 @@ static int add(int argc, const char **argv) argc = parse_options(argc, argv, NULL, options, builtin_remote_add_usage, 0); - if (argc < 2) + if (argc != 2) usage_with_options(builtin_remote_add_usage, options); if (mirror && master) @@ -1088,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; @@ -1196,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, @@ -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() }; @@ -1318,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; @@ -1404,7 +1404,7 @@ 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() }; @@ -1433,11 +1433,11 @@ 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_seturl_usage, @@ -1566,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; @@ -1580,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); diff --git a/builtin/replace.c b/builtin/replace.c index 4a8970e9c9..59d31152d0 100644 --- a/builtin/replace.c +++ b/builtin/replace.c @@ -3,7 +3,7 @@ * * Copyright (c) 2008 Christian Couder <chriscool@tuxfamily.org> * - * Based on builtin-tag.c by Kristian Høgsberg <krh@redhat.com> + * Based on builtin/tag.c by Kristian Høgsberg <krh@redhat.com> * and Carlos Rica <jasampler@gmail.com> that was itself based on * git-tag.sh and mktag.c by Linus Torvalds. */ @@ -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; } + 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.", *p); + 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; @@ -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 4cc34c9084..afa6e020e8 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] <tree-ish> [--] <paths>..."), + N_("git reset --patch [<tree-ish>] [--] [<paths>...]"), NULL }; @@ -38,14 +38,12 @@ static inline int is_merge(void) return !access(git_path("MERGE_HEAD"), F_OK); } -static int reset_index_file(const unsigned char *sha1, int reset_type, int quiet) +static int reset_index(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)); memset(&opts, 0, sizeof(opts)); opts.head_idx = 1; @@ -67,8 +65,6 @@ static int reset_index_file(const unsigned char *sha1, int reset_type, int quiet opts.reset = 1; } - newfd = hold_locked_index(lock, 1); - read_cache_unmerged(); if (reset_type == KEEP) { @@ -91,20 +87,18 @@ static int reset_index_file(const unsigned char *sha1, int reset_type, int quiet prime_cache_tree(&active_cache_tree, tree); } - if (write_cache(newfd, active_cache, active_nr) || - commit_locked_index(lock)) - return error(_("Could not write new index file.")); - return 0; } static void print_new_head_line(struct commit *commit) { const char *hex, *body; + char *msg; hex = find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV); printf(_("HEAD is now at %s"), hex); - body = strstr(commit->buffer, "\n\n"); + msg = logmsg_reencode(commit, NULL, get_log_output_encoding()); + body = strstr(msg, "\n\n"); if (body) { const char *eol; size_t len; @@ -115,38 +109,13 @@ static void print_new_head_line(struct commit *commit) } else printf("\n"); -} - -static int update_index_refresh(int fd, struct lock_file *index_lock, int flags) -{ - int result; - - if (!index_lock) { - index_lock = xcalloc(1, sizeof(struct lock_file)); - fd = hold_locked_index(index_lock, 1); - } - - if (read_cache() < 0) - return error(_("Could not read index")); - - result = refresh_index(&the_index, (flags), NULL, NULL, - _("Unstaged changes after reset:")) ? 1 : 0; - if (write_cache(fd, active_cache, active_nr) || - commit_locked_index(index_lock)) - return error ("Could not refresh index"); - return result; + logmsg_free(msg, commit); } static void update_index_from_diff(struct diff_queue_struct *q, struct diff_options *opt, void *data) { int i; - int *discard_flag = data; - - /* do_diff_cache() mangled the index */ - discard_cache(); - *discard_flag = 1; - read_cache(); for (i = 0; i < q->nr; i++) { struct diff_filespec *one = q->queue[i]->one; @@ -164,32 +133,15 @@ static void update_index_from_diff(struct diff_queue_struct *q, } } -static int interactive_reset(const char *revision, const char **argv, - const char *prefix) +static int read_from_tree(const char **pathspec, unsigned char *tree_sha1) { - const char **pathspec = NULL; - - if (*argv) - pathspec = get_pathspec(prefix, argv); - - return run_add_interactive(revision, "--patch=reset", pathspec); -} - -static int read_from_tree(const char *prefix, const char **argv, - unsigned char *tree_sha1, int refresh_flags) -{ - struct lock_file *lock = xcalloc(1, sizeof(struct lock_file)); - int index_fd, index_was_discarded = 0; struct diff_options opt; memset(&opt, 0, sizeof(opt)); - diff_tree_setup_paths(get_pathspec(prefix, (const char **)argv), &opt); + diff_tree_setup_paths(pathspec, &opt); opt.output_format = DIFF_FORMAT_CALLBACK; opt.format_callback = update_index_from_diff; - opt.format_callback_data = &index_was_discarded; - index_fd = hold_locked_index(lock, 1); - index_was_discarded = 0; read_cache(); if (do_diff_cache(tree_sha1, &opt)) return 1; @@ -197,10 +149,7 @@ static int read_from_tree(const char *prefix, const char **argv, diff_flush(&opt); diff_tree_release_paths(&opt); - if (!index_was_discarded) - /* The index is still clobbered from do_diff_cache() */ - discard_cache(); - return update_index_refresh(index_fd, lock, refresh_flags); + return 0; } static void set_reflog_message(struct strbuf *sb, const char *action, @@ -225,95 +174,137 @@ static void die_if_unmerged_cache(int reset_type) } -int cmd_reset(int argc, const char **argv, const char *prefix) +static const char **parse_args(const char **argv, const char *prefix, const char **rev_ret) { - int i = 0, reset_type = NONE, update_ref_status = 0, quiet = 0; - int patch_mode = 0; const char *rev = "HEAD"; - unsigned char sha1[20], *orig = NULL, sha1_orig[20], - *old_orig = NULL, sha1_old_orig[20]; - struct commit *commit; - struct strbuf msg = STRBUF_INIT; - const struct option options[] = { - OPT__QUIET(&quiet, "be quiet, only report errors"), - OPT_SET_INT(0, "mixed", &reset_type, - "reset HEAD and index", MIXED), - OPT_SET_INT(0, "soft", &reset_type, "reset only HEAD", SOFT), - OPT_SET_INT(0, "hard", &reset_type, - "reset HEAD, index and working tree", HARD), - OPT_SET_INT(0, "merge", &reset_type, - "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"), - OPT_END() - }; - - git_config(git_default_config, NULL); - - argc = parse_options(argc, argv, prefix, options, git_reset_usage, - PARSE_OPT_KEEP_DASHDASH); - + unsigned char unused[20]; /* * Possible arguments are: * - * git reset [-opts] <rev> <paths>... - * git reset [-opts] <rev> -- <paths>... - * git reset [-opts] -- <paths>... + * git reset [-opts] [<rev>] + * git reset [-opts] <tree> [<paths>...] + * git reset [-opts] <tree> -- [<paths>...] + * git reset [-opts] -- [<paths>...] * git reset [-opts] <paths>... * - * At this point, argv[i] points immediately after [-opts]. + * At this point, argv points immediately after [-opts]. */ - if (i < argc) { - if (!strcmp(argv[i], "--")) { - i++; /* reset to HEAD, possibly with paths */ - } else if (i + 1 < argc && !strcmp(argv[i+1], "--")) { - rev = argv[i]; - i += 2; + if (argv[0]) { + if (!strcmp(argv[0], "--")) { + argv++; /* reset to HEAD, possibly with paths */ + } else if (argv[1] && !strcmp(argv[1], "--")) { + rev = argv[0]; + argv += 2; } /* - * Otherwise, argv[i] could be either <rev> or <paths> and - * has to be unambiguous. + * Otherwise, argv[0] could be either <rev> or <paths> and + * has to be unambiguous. If there is a single argument, it + * can not be a tree */ - else if (!get_sha1(argv[i], sha1)) { + else if ((!argv[1] && !get_sha1_committish(argv[0], unused)) || + (argv[1] && !get_sha1_treeish(argv[0], unused))) { /* - * Ok, argv[i] looks like a rev; it should not + * Ok, argv[0] looks like a commit/tree; it should not * be a filename. */ - verify_non_filename(prefix, argv[i]); - rev = argv[i++]; + verify_non_filename(prefix, argv[0]); + rev = *argv++; } else { /* Otherwise we treat this as a filename */ - verify_filename(prefix, argv[i], 1); + verify_filename(prefix, argv[0], 1); } } + *rev_ret = rev; + return argv[0] ? get_pathspec(prefix, argv) : NULL; +} + +static int update_refs(const char *rev, const unsigned char *sha1) +{ + int update_ref_status; + struct strbuf msg = STRBUF_INIT; + unsigned char *orig = NULL, sha1_orig[20], + *old_orig = NULL, sha1_old_orig[20]; + + if (!get_sha1("ORIG_HEAD", sha1_old_orig)) + old_orig = sha1_old_orig; + if (!get_sha1("HEAD", sha1_orig)) { + orig = sha1_orig; + set_reflog_message(&msg, "updating ORIG_HEAD", NULL); + update_ref(msg.buf, "ORIG_HEAD", orig, old_orig, 0, MSG_ON_ERR); + } else if (old_orig) + delete_ref("ORIG_HEAD", old_orig, 0); + set_reflog_message(&msg, "updating HEAD", rev); + update_ref_status = update_ref(msg.buf, "HEAD", sha1, orig, 0, MSG_ON_ERR); + strbuf_release(&msg); + return update_ref_status; +} + +int cmd_reset(int argc, const char **argv, const char *prefix) +{ + int reset_type = NONE, update_ref_status = 0, quiet = 0; + int patch_mode = 0, unborn; + const char *rev; + unsigned char sha1[20]; + const char **pathspec = NULL; + const struct option options[] = { + OPT__QUIET(&quiet, N_("be quiet, only report errors")), + OPT_SET_INT(0, "mixed", &reset_type, + 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, + N_("reset HEAD, index and working tree"), HARD), + OPT_SET_INT(0, "merge", &reset_type, + N_("reset HEAD, index and working tree"), MERGE), + OPT_SET_INT(0, "keep", &reset_type, + N_("reset HEAD but keep local changes"), KEEP), + OPT_BOOLEAN('p', "patch", &patch_mode, N_("select hunks interactively")), + OPT_END() + }; - if (get_sha1(rev, sha1)) - die(_("Failed to resolve '%s' as a valid ref."), rev); + git_config(git_default_config, NULL); - commit = lookup_commit_reference(sha1); - if (!commit) - die(_("Could not parse object '%s'."), rev); - hashcpy(sha1, commit->object.sha1); + argc = parse_options(argc, argv, prefix, options, git_reset_usage, + PARSE_OPT_KEEP_DASHDASH); + pathspec = parse_args(argv, prefix, &rev); + + unborn = !strcmp(rev, "HEAD") && get_sha1("HEAD", sha1); + if (unborn) { + /* reset on unborn branch: treat as reset to empty tree */ + hashcpy(sha1, EMPTY_TREE_SHA1_BIN); + } else if (!pathspec) { + struct commit *commit; + if (get_sha1_committish(rev, sha1)) + die(_("Failed to resolve '%s' as a valid revision."), rev); + commit = lookup_commit_reference(sha1); + if (!commit) + die(_("Could not parse object '%s'."), rev); + hashcpy(sha1, commit->object.sha1); + } else { + struct tree *tree; + if (get_sha1_treeish(rev, sha1)) + die(_("Failed to resolve '%s' as a valid tree."), rev); + tree = parse_tree_indirect(sha1); + if (!tree) + die(_("Could not parse object '%s'."), rev); + hashcpy(sha1, tree->object.sha1); + } if (patch_mode) { if (reset_type != NONE) die(_("--patch is incompatible with --{hard,mixed,soft}")); - return interactive_reset(rev, argv + i, prefix); + return run_add_interactive(sha1_to_hex(sha1), "--patch=reset", pathspec); } /* git reset tree [--] paths... can be used to * load chosen paths from the tree into the index without * affecting the working tree nor HEAD. */ - if (i < argc) { + if (pathspec) { if (reset_type == MIXED) warning(_("--mixed with paths is deprecated; use 'git reset -- <paths>' instead.")); else if (reset_type != NONE) die(_("Cannot do %s reset with paths."), _(reset_type_names[reset_type])); - return read_from_tree(prefix, argv + i, sha1, - quiet ? REFRESH_QUIET : REFRESH_IN_PORCELAIN); } if (reset_type == NONE) reset_type = MIXED; /* by default */ @@ -328,49 +319,44 @@ int cmd_reset(int argc, const char **argv, const char *prefix) /* Soft reset does not touch the index file nor the working tree * at all, but requires them in a good order. Other resets reset * the index file to the tree object we are switching to. */ - if (reset_type == SOFT) + if (reset_type == SOFT || reset_type == KEEP) die_if_unmerged_cache(reset_type); - else { - int err; - if (reset_type == KEEP) - die_if_unmerged_cache(reset_type); - err = reset_index_file(sha1, reset_type, quiet); - if (reset_type == KEEP) - err = err || reset_index_file(sha1, MIXED, quiet); - if (err) - die(_("Could not reset index file to revision '%s'."), rev); - } - /* Any resets update HEAD to the head being switched to, - * saving the previous head in ORIG_HEAD before. */ - if (!get_sha1("ORIG_HEAD", sha1_old_orig)) - old_orig = sha1_old_orig; - if (!get_sha1("HEAD", sha1_orig)) { - orig = sha1_orig; - set_reflog_message(&msg, "updating ORIG_HEAD", NULL); - update_ref(msg.buf, "ORIG_HEAD", orig, old_orig, 0, MSG_ON_ERR); - } - else if (old_orig) - delete_ref("ORIG_HEAD", old_orig, 0); - set_reflog_message(&msg, "updating HEAD", rev); - update_ref_status = update_ref(msg.buf, "HEAD", sha1, orig, 0, MSG_ON_ERR); + if (reset_type != SOFT) { + struct lock_file *lock = xcalloc(1, sizeof(struct lock_file)); + int newfd = hold_locked_index(lock, 1); + if (reset_type == MIXED) { + if (read_from_tree(pathspec, sha1)) + return 1; + } else { + int err = reset_index(sha1, reset_type, quiet); + if (reset_type == KEEP && !err) + err = reset_index(sha1, MIXED, quiet); + if (err) + die(_("Could not reset index file to revision '%s'."), rev); + } - switch (reset_type) { - case HARD: - if (!update_ref_status && !quiet) - print_new_head_line(commit); - break; - case SOFT: /* Nothing else to do. */ - break; - case MIXED: /* Report what has not been updated. */ - update_index_refresh(0, NULL, - quiet ? REFRESH_QUIET : REFRESH_IN_PORCELAIN); - break; + if (reset_type == MIXED) { /* Report what has not been updated. */ + int flags = quiet ? REFRESH_QUIET : REFRESH_IN_PORCELAIN; + refresh_index(&the_index, flags, NULL, NULL, + _("Unstaged changes after reset:")); + } + + if (write_cache(newfd, active_cache, active_nr) || + commit_locked_index(lock)) + die(_("Could not write new index file.")); } - remove_branch_state(); + if (!pathspec && !unborn) { + /* Any resets without paths update HEAD to the head being + * switched to, saving the previous head in ORIG_HEAD before. */ + update_ref_status = update_refs(rev, sha1); - strbuf_release(&msg); + if (reset_type == HARD && !update_ref_status && !quiet) + print_new_head_line(lookup_commit_reference(sha1)); + } + if (!pathspec) + remove_branch_state(); return update_ref_status; } diff --git a/builtin/rev-list.c b/builtin/rev-list.c index ff5a38372d..a5ec30d74e 100644 --- a/builtin/rev-list.c +++ b/builtin/rev-list.c @@ -111,6 +111,7 @@ static void show_commit(struct commit *commit, void *data) ctx.date_mode = revs->date_mode; ctx.date_mode_explicit = revs->date_mode_explicit; ctx.fmt = revs->commit_format; + ctx.output_encoding = get_log_output_encoding(); pretty_print_commit(&ctx, commit, &buf); if (revs->graph) { if (buf.len) { @@ -201,55 +202,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 print_var_str(const char *var, const char *val) { printf("%s='%s'\n", var, val); diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c index 47b4e7adb9..de894c7577 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]; @@ -206,11 +212,17 @@ static void show_datestring(const char *flag, const char *datestr) show(buffer); } -static int show_file(const char *arg) +static int show_file(const char *arg, int output_prefix) { show_default(); if ((filter & (DO_NONFLAGS|DO_NOREV)) == (DO_NONFLAGS|DO_NOREV)) { - show(arg); + if (output_prefix) { + const char *prefix = startup_info->prefix; + show(prefix_filename(prefix, + prefix ? strlen(prefix) : 0, + arg)); + } else + show(arg); return 1; } return 0; @@ -250,7 +262,7 @@ static int try_difference(const char *arg) return 0; } - if (!get_sha1(this, sha1) && !get_sha1(next, end)) { + 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) { @@ -290,7 +302,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) @@ -330,15 +342,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(), }; @@ -455,15 +467,16 @@ 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) { int i, as_is = 0, verify = 0, quiet = 0, revs_count = 0, type = 0; + int output_prefix = 0; unsigned char sha1[20]; const char *name = NULL; @@ -497,7 +510,7 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) const char *arg = argv[i]; if (as_is) { - if (show_file(arg) && as_is < 2) + if (show_file(arg, output_prefix) && as_is < 2) verify_filename(prefix, arg, 0); continue; } @@ -521,7 +534,7 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) as_is = 2; /* Pass on the "--" if we show anything but files.. */ if (filter & (DO_FLAGS | DO_REVS)) - show_file(arg); + show_file(arg, 0); continue; } if (!strcmp(arg, "--default")) { @@ -529,6 +542,13 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) i++; continue; } + if (!strcmp(arg, "--prefix")) { + prefix = argv[i+1]; + startup_info->prefix = prefix; + output_prefix = 1; + i++; + continue; + } if (!strcmp(arg, "--revs-only")) { filter &= ~DO_NOREV; continue; @@ -601,6 +621,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); @@ -744,7 +768,7 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) if (verify) die_no_single_rev(quiet); as_is = 1; - if (!show_file(arg)) + if (!show_file(arg, output_prefix)) continue; verify_filename(prefix, arg, 1); } diff --git a/builtin/revert.c b/builtin/revert.c index 82d1bf844b..1d2648b756 100644 --- a/builtin/revert.c +++ b/builtin/revert.c @@ -19,14 +19,14 @@ */ static const char * const revert_usage[] = { - "git revert [options] <commit-ish>", - "git revert <subcommand>", + N_("git revert [options] <commit-ish>..."), + N_("git revert <subcommand>"), NULL }; static const char * const cherry_pick_usage[] = { - "git cherry-pick [options] <commit-ish>", - "git cherry-pick <subcommand>", + N_("git cherry-pick [options] <commit-ish>..."), + N_("git cherry-pick <subcommand>"), NULL }; @@ -54,6 +54,7 @@ static int option_parse_x(const struct option *opt, return 0; } +LAST_ARG_MUST_BE_NULL static void verify_opt_compatible(const char *me, const char *base_opt, ...) { const char *this_opt; @@ -70,6 +71,7 @@ static void verify_opt_compatible(const char *me, const char *base_opt, ...) die(_("%s: %s cannot be used with %s"), me, this_opt, base_opt); } +LAST_ARG_MUST_BE_NULL static void verify_opt_mutually_compatible(const char *me, ...) { const char *opt1, *opt2 = NULL; @@ -100,18 +102,19 @@ static void parse_args(int argc, const char **argv, struct replay_opts *opts) int contin = 0; int rollback = 0; struct option options[] = { - OPT_BOOLEAN(0, "quit", &remove_state, "end revert or cherry-pick sequence"), - OPT_BOOLEAN(0, "continue", &contin, "resume revert or cherry-pick sequence"), - OPT_BOOLEAN(0, "abort", &rollback, "cancel revert or cherry-pick sequence"), - OPT_BOOLEAN('n', "no-commit", &opts->no_commit, "don't automatically commit"), - OPT_BOOLEAN('e', "edit", &opts->edit, "edit the commit message"), + OPT_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, "add Signed-off-by:"), - OPT_INTEGER('m', "mainline", &opts->mainline, "parent number"), + 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, "strategy", "merge strategy"), - OPT_CALLBACK('X', "strategy-option", &opts, "option", - "option for merge strategy", option_parse_x), + 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(), @@ -121,10 +124,11 @@ static void parse_args(int argc, const char **argv, struct replay_opts *opts) if (opts->action == REPLAY_PICK) { struct option cp_extra[] = { - OPT_BOOLEAN('x', NULL, &opts->record_origin, "append commit name"), - OPT_BOOLEAN(0, "ff", &opts->allow_ff, "allow fast-forward"), - OPT_BOOLEAN(0, "allow-empty", &opts->allow_empty, "preserve initially empty commits"), - OPT_BOOLEAN(0, "keep-redundant-commits", &opts->keep_redundant_commits, "keep redundant, empty commits"), + OPT_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)) @@ -193,7 +197,7 @@ static void parse_args(int argc, const char **argv, struct replay_opts *opts) struct setup_revision_opt s_r_opt; opts->revs = xmalloc(sizeof(*opts->revs)); init_revisions(opts->revs, NULL); - opts->revs->no_walk = 1; + 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)); diff --git a/builtin/rm.c b/builtin/rm.c index 90c8a5047c..18916e022a 100644 --- a/builtin/rm.c +++ b/builtin/rm.c @@ -9,17 +9,97 @@ #include "cache-tree.h" #include "tree-walk.h" #include "parse-options.h" +#include "string-list.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 void print_error_files(struct string_list *files_list, + const char *main_msg, + const char *hints_msg, + int *errs) +{ + if (files_list->nr) { + int i; + struct strbuf err_msg = STRBUF_INIT; + + strbuf_addstr(&err_msg, main_msg); + for (i = 0; i < files_list->nr; i++) + strbuf_addf(&err_msg, + "\n %s", + files_list->items[i].string); + if (advice_rm_hints) + strbuf_addstr(&err_msg, hints_msg); + *errs = error("%s", err_msg.buf); + strbuf_release(&err_msg); + } +} + +static int check_submodules_use_gitfiles(void) +{ + int i; + int errs = 0; + struct string_list files = STRING_LIST_INIT_NODUP; + + for (i = 0; i < list.nr; i++) { + const char *name = list.entry[i].name; + int pos; + const 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)) + string_list_append(&files, name); + } + print_error_files(&files, + Q_("the following submodule (or one of its nested " + "submodules)\n uses a .git directory:", + "the following submodules (or one of its nested " + "submodules)\n use a .git directory:", + files.nr), + _("\n(use 'rm -rf' if you really want to remove " + "it including all of its history)"), + &errs); + string_list_clear(&files, 0); + + return errs; +} + static int check_local_mod(unsigned char *head, int index_only) { /* @@ -31,25 +111,40 @@ static int check_local_mod(unsigned char *head, int index_only) */ int i, no_head; int errs = 0; + struct string_list files_staged = STRING_LIST_INIT_NODUP; + struct string_list files_cached = STRING_LIST_INIT_NODUP; + struct string_list files_submodule = STRING_LIST_INIT_NODUP; + struct string_list files_local = STRING_LIST_INIT_NODUP; no_head = is_null_sha1(head); for (i = 0; i < list.nr; i++) { struct stat st; int pos; - struct cache_entry *ce; - const char *name = list.name[i]; + const struct cache_entry *ce; + 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) { - if (errno != ENOENT) + if (errno != ENOENT && errno != ENOTDIR) warning("'%s': %s", ce->name, strerror(errno)); /* It already vanished from the working tree */ continue; @@ -58,9 +153,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 +176,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; /* @@ -106,21 +205,58 @@ static int check_local_mod(unsigned char *head, int index_only) */ if (local_changes && staged_changes) { if (!index_only || !(ce->ce_flags & CE_INTENT_TO_ADD)) - errs = error(_("'%s' has staged content different " - "from both the file and the HEAD\n" - "(use -f to force removal)"), name); + string_list_append(&files_staged, name); } else if (!index_only) { if (staged_changes) - 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); + string_list_append(&files_cached, name); + if (local_changes) { + if (S_ISGITLINK(ce->ce_mode) && + !submodule_uses_gitfile(name)) + string_list_append(&files_submodule, name); + else + string_list_append(&files_local, name); + } } } + print_error_files(&files_staged, + Q_("the following file has staged content different " + "from both the\nfile and the HEAD:", + "the following files have staged content different" + " from both the\nfile and the HEAD:", + files_staged.nr), + _("\n(use -f to force removal)"), + &errs); + string_list_clear(&files_staged, 0); + print_error_files(&files_cached, + Q_("the following file has changes " + "staged in the index:", + "the following files have changes " + "staged in the index:", files_cached.nr), + _("\n(use --cached to keep the file," + " or -f to force removal)"), + &errs); + string_list_clear(&files_cached, 0); + print_error_files(&files_submodule, + Q_("the following submodule (or one of its nested " + "submodule)\nuses a .git directory:", + "the following submodules (or one of its nested " + "submodule)\nuse a .git directory:", + files_submodule.nr), + _("\n(use 'rm -rf' if you really " + "want to remove it including all " + "of its history)"), + &errs); + string_list_clear(&files_submodule, 0); + print_error_files(&files_local, + Q_("the following file has local modifications:", + "the following files have local modifications:", + files_local.nr), + _("\n(use --cached to keep the file," + " or -f to force removal)"), + &errs); + string_list_clear(&files_local, 0); + return errs; } @@ -130,13 +266,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 +297,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); @@ -170,11 +321,12 @@ int cmd_rm(int argc, const char **argv, const char *prefix) seen = xcalloc(i, 1); for (i = 0; i < active_nr; i++) { - struct cache_entry *ce = active_cache[i]; + const 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 +367,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 +377,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 +399,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 d5d7105ba2..152c4ea092 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 || !args->progress) - 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; @@ -201,6 +44,21 @@ static void print_helper_status(struct ref *ref) msg = "non-fast forward"; break; + case REF_STATUS_REJECT_FETCH_FIRST: + res = "error"; + msg = "fetch first"; + break; + + case REF_STATUS_REJECT_NEEDS_FORCE: + res = "error"; + msg = "needs force"; + break; + + case REF_STATUS_REJECT_ALREADY_EXISTS: + res = "error"; + msg = "already exists"; + break; + case REF_STATUS_REJECT_NODELETE: case REF_STATUS_REMOTE_REJECT: res = "error"; @@ -221,178 +79,11 @@ static void print_helper_status(struct ref *ref) } strbuf_addch(&buf, '\n'); - safe_write(1, buf.buf, buf.len); + write_or_die(1, buf.buf, buf.len); } 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; - int quiet_supported = 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 (server_supports("quiet")) - quiet_supported = 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); - int quiet = quiet_supported && (args->quiet || !args->progress); - - if (!cmds_sent && (status_report || use_sideband || args->quiet)) { - packet_buf_write(&req_buf, "%s %s %s%c%s%s%s", - old_hex, new_hex, ref->name, 0, - status_report ? " report-status" : "", - use_sideband ? " side-band-64k" : "", - quiet ? " quiet" : ""); - } - else - packet_buf_write(&req_buf, "%s %s %s", - old_hex, new_hex, ref->name); - 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("send-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; @@ -409,7 +100,7 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix) int send_all = 0; const char *receivepack = "git-receive-pack"; int flags; - int nonfastforward = 0; + unsigned int reject_reasons; int progress = -1; argv++; @@ -516,7 +207,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, REF_NORMAL, &extra_have); + get_remote_heads(fd[0], NULL, 0, &remote_refs, REF_NORMAL, &extra_have); transport_verify_remote_names(nr_refspecs, refspecs); @@ -547,7 +238,7 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix) ret |= finish_connect(conn); if (!helper_status) - transport_print_push_status(dest, remote_refs, args.verbose, 0, &nonfastforward); + transport_print_push_status(dest, remote_refs, args.verbose, 0, &reject_reasons); if (!args.dry_run && remote) { struct ref *ref; diff --git a/builtin/shortlog.c b/builtin/shortlog.c index 37f3193a33..1434f8fee4 100644 --- a/builtin/shortlog.c +++ b/builtin/shortlog.c @@ -10,9 +10,7 @@ #include "parse-options.h" static char const * const shortlog_usage[] = { - "git shortlog [-n] [-s] [-e] [-w] [rev-opts] [--] [<commit-id>... ]", - "", - "[rev-opts] are documented in git-rev-list(1)", + N_("git shortlog [<options>] [<revision range>] [[--] [<path>...]]"), NULL }; @@ -36,52 +34,28 @@ static void insert_one_record(struct shortlog *log, const char *dot3 = log->common_repo_prefix; char *buffer, *p; struct string_list_item *item; - char namebuf[1024]; - char emailbuf[1024]; - size_t len; + const char *mailbuf, *namebuf; + size_t namelen, maillen; const char *eol; - const char *boemail, *eoemail; struct strbuf subject = STRBUF_INIT; + struct strbuf namemailbuf = STRBUF_INIT; + struct ident_split ident; - boemail = strchr(author, '<'); - if (!boemail) - return; - eoemail = strchr(boemail, '>'); - if (!eoemail) + if (split_ident_line(&ident, author, strlen(author))) return; - /* copy author name to namebuf, to support matching on both name and email */ - memcpy(namebuf, author, boemail - author); - len = boemail - author; - while (len > 0 && isspace(namebuf[len-1])) - len--; - namebuf[len] = 0; - - /* copy email name to emailbuf, to allow email replacement as well */ - memcpy(emailbuf, boemail+1, eoemail - boemail); - emailbuf[eoemail - boemail - 1] = 0; - - if (!map_user(&log->mailmap, emailbuf, sizeof(emailbuf), namebuf, sizeof(namebuf))) { - while (author < boemail && isspace(*author)) - author++; - for (len = 0; - len < sizeof(namebuf) - 1 && author + len < boemail; - len++) - namebuf[len] = author[len]; - while (0 < len && isspace(namebuf[len-1])) - len--; - namebuf[len] = '\0'; - } - else - len = strlen(namebuf); + namebuf = ident.name_begin; + mailbuf = ident.mail_begin; + namelen = ident.name_end - ident.name_begin; + maillen = ident.mail_end - ident.mail_begin; - if (log->email) { - size_t room = sizeof(namebuf) - len - 1; - int maillen = strlen(emailbuf); - snprintf(namebuf + len, room, " <%.*s>", maillen, emailbuf); - } + map_user(&log->mailmap, &mailbuf, &maillen, &namebuf, &namelen); + strbuf_add(&namemailbuf, namebuf, namelen); + + if (log->email) + strbuf_addf(&namemailbuf, " <%.*s>", (int)maillen, mailbuf); - item = string_list_insert(&log->list, namebuf); + item = string_list_insert(&log->list, namemailbuf.buf); if (item->util == NULL) item->util = xcalloc(1, sizeof(struct string_list)); @@ -163,6 +137,7 @@ void shortlog_add_commit(struct shortlog *log, struct commit *commit) ctx.subject = ""; ctx.after_subject = ""; ctx.date_mode = DATE_NORMAL; + ctx.output_encoding = get_log_output_encoding(); pretty_print_commit(&ctx, commit, &ufbuf); buffer = ufbuf.buf; } else if (*buffer) { @@ -250,13 +225,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 +281,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 a59e088cf5..9788eb115b 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 }; @@ -162,29 +162,28 @@ static void name_commits(struct commit_list *list, nth = 0; while (parents) { struct commit *p = parents->item; - char newname[1000], *en; + struct strbuf newname = STRBUF_INIT; parents = parents->next; nth++; if (p->util) continue; - en = newname; switch (n->generation) { case 0: - en += sprintf(en, "%s", n->head_name); + strbuf_addstr(&newname, n->head_name); break; case 1: - en += sprintf(en, "%s^", n->head_name); + strbuf_addf(&newname, "%s^", n->head_name); break; default: - en += sprintf(en, "%s~%d", - n->head_name, n->generation); + strbuf_addf(&newname, "%s~%d", + n->head_name, n->generation); break; } if (nth == 1) - en += sprintf(en, "^"); + strbuf_addch(&newname, '^'); else - en += sprintf(en, "^%d", nth); - name_commit(p, xstrdup(newname), 0); + strbuf_addf(&newname, "^%d", nth); + name_commit(p, strbuf_detach(&newname, NULL), 0); i++; name_first_parent_chain(p); } @@ -631,7 +630,7 @@ int cmd_show_branch(int ac, const char **av, const char *prefix) int num_rev, i, extra = 0; int all_heads = 0, all_remotes = 0; int all_mask, all_revs; - int lifo = 1; + enum rev_sort_order sort_order = REV_SORT_IN_GRAPH_ORDER; char head[128]; const char *head_p; int head_len; @@ -648,36 +647,38 @@ 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"), - OPT_BOOLEAN(0, "topo-order", &lifo, - "show commits in topological order"), + N_("show refs unreachable from any other ref")), + OPT_SET_INT(0, "topo-order", &sort_order, + N_("show commits in topological order"), + REV_SORT_IN_GRAPH_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), - 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 merges reachable from only one tip"), 0), + OPT_SET_INT(0, "date-order", &sort_order, + N_("topologically sort, maintaining date order " + "where possible"), + REV_SORT_BY_COMMIT_DATE), + { 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() @@ -901,7 +902,7 @@ int cmd_show_branch(int ac, const char **av, const char *prefix) exit(0); /* Sort topologically */ - sort_in_topological_order(&seen, lifo); + sort_in_topological_order(&seen, sort_order); /* Give names to commits */ if (!sha1_name && !no_name) diff --git a/builtin/show-ref.c b/builtin/show-ref.c index 3911661900..87806ad5b0 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,10 +28,12 @@ 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]; + if (show_head && !strcmp(refname, "HEAD")) + goto match; + if (tags_only || heads_only) { int match; @@ -79,25 +81,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; } @@ -120,7 +106,7 @@ static int add_existing(const char *refname, const unsigned char *sha1, int flag */ static int exclude_existing(const char *match) { - static struct string_list existing_refs = STRING_LIST_INIT_NODUP; + static struct string_list existing_refs = STRING_LIST_INIT_DUP; char buf[1024]; int matchlen = match ? strlen(match) : 0; @@ -179,26 +165,27 @@ 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, even if it would be filtered out"), 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, even if it would be filtered out")), 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() }; diff --git a/builtin/stripspace.c b/builtin/stripspace.c index f16986c0ae..e981dfb9f0 100644 --- a/builtin/stripspace.c +++ b/builtin/stripspace.c @@ -30,7 +30,8 @@ static size_t cleanup(char *line, size_t len) * * If last line does not have a newline at the end, one is added. * - * Enable skip_comments to skip every line starting with "#". + * Enable skip_comments to skip every line starting with comment + * character. */ void stripspace(struct strbuf *sb, int skip_comments) { @@ -45,7 +46,7 @@ void stripspace(struct strbuf *sb, int skip_comments) eol = memchr(sb->buf + i, '\n', sb->len - i); len = eol ? eol - (sb->buf + i) + 1 : sb->len - i; - if (skip_comments && len && sb->buf[i] == '#') { + if (skip_comments && len && sb->buf[i] == comment_line_char) { newlen = 0; continue; } @@ -66,21 +67,53 @@ void stripspace(struct strbuf *sb, int skip_comments) strbuf_setlen(sb, j); } +static void comment_lines(struct strbuf *buf) +{ + char *msg; + size_t len; + + msg = strbuf_detach(buf, &len); + strbuf_add_commented_lines(buf, msg, len); + free(msg); +} + +static const char *usage_msg = "\n" +" git stripspace [-s | --strip-comments] < input\n" +" git stripspace [-c | --comment-lines] < input"; + int cmd_stripspace(int argc, const char **argv, const char *prefix) { struct strbuf buf = STRBUF_INIT; int strip_comments = 0; + enum { INVAL = 0, STRIP_SPACE = 1, COMMENT_LINES = 2 } mode = STRIP_SPACE; + + if (argc == 2) { + if (!strcmp(argv[1], "-s") || + !strcmp(argv[1], "--strip-comments")) { + strip_comments = 1; + } else if (!strcmp(argv[1], "-c") || + !strcmp(argv[1], "--comment-lines")) { + mode = COMMENT_LINES; + } else { + mode = INVAL; + } + } else if (argc > 1) { + mode = INVAL; + } + + if (mode == INVAL) + usage(usage_msg); - if (argc == 2 && (!strcmp(argv[1], "-s") || - !strcmp(argv[1], "--strip-comments"))) - strip_comments = 1; - else if (argc > 1) - usage("git stripspace [-s | --strip-comments] < input"); + if (strip_comments || mode == COMMENT_LINES) + git_config(git_default_config, NULL); if (strbuf_read(&buf, 0, 1024) < 0) die_errno("could not read the input"); - stripspace(&buf, strip_comments); + if (mode == STRIP_SPACE) + stripspace(&buf, strip_comments); + else + comment_lines(&buf); write_or_die(1, buf.buf, buf.len); strbuf_release(&buf); diff --git a/builtin/symbolic-ref.c b/builtin/symbolic-ref.c index 801d62ece5..f481959421 100644 --- a/builtin/symbolic-ref.c +++ b/builtin/symbolic-ref.c @@ -4,13 +4,12 @@ #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 int shorten; - -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; @@ -22,22 +21,26 @@ static void check_symref(const char *HEAD, int quiet) if (!quiet) die("ref %s is not a symbolic ref", HEAD); else - exit(1); + return 1; + } + if (print) { + if (shorten) + refname = shorten_unambiguous_ref(refname, 0); + puts(refname); } - 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_BOOL(0, "short", &shorten, "shorten ref output"), - 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(), }; @@ -46,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") && @@ -59,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 7b1be85e48..af3af3f649 100644 --- a/builtin/tag.c +++ b/builtin/tag.c @@ -19,11 +19,11 @@ #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>]] [--contains <commit>] [--points-at <object>] " - "\n\t\t[<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 }; @@ -246,19 +246,13 @@ static int do_sign(struct strbuf *buffer) } static const char tag_template[] = - N_("\n" - "#\n" - "# Write a tag message\n" - "# Lines starting with '#' will be ignored.\n" - "#\n"); + N_("\nWrite a tag message\n" + "Lines starting with '%c' will be ignored.\n"); 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"); + N_("\nWrite a tag message\n" + "Lines starting with '%c' will be kept; you may remove them" + " yourself if you want to.\n"); static int git_tag_config(const char *var, const char *value, void *cb) { @@ -346,14 +340,18 @@ static void create_tag(const unsigned char *object, const char *tag, if (fd < 0) die_errno(_("could not create file '%s'"), path); - if (!is_null_sha1(prev)) + 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_nocleanup), - strlen(_(tag_template_nocleanup))); + } else { + struct strbuf buf = STRBUF_INIT; + strbuf_addch(&buf, '\n'); + if (opt->cleanup_mode == CLEANUP_ALL) + strbuf_commented_addf(&buf, _(tag_template), comment_line_char); + else + strbuf_commented_addf(&buf, _(tag_template_nocleanup), comment_line_char); + write_or_die(fd, buf.buf, buf.len); + strbuf_release(&buf); + } close(fd); if (launch_editor(path, buf, NULL)) { @@ -444,37 +442,37 @@ int cmd_tag(int argc, const char **argv, const char *prefix) struct msg_arg msg = { 0, STRBUF_INIT }; struct commit_list *with_commit = NULL; struct option options[] = { - OPT_BOOLEAN('l', "list", &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', "delete", &delete, "delete tags"), - OPT_BOOLEAN('v', "verify", &verify, "verify tags"), + OPT_BOOLEAN('d', "delete", &delete, N_("delete tags")), + OPT_BOOLEAN('v', "verify", &verify, N_("verify tags")), - OPT_GROUP("Tag creation options"), + OPT_GROUP(N_("Tag creation options")), OPT_BOOLEAN('a', "annotate", &annotate, - "annotated tag, needs a message"), - OPT_CALLBACK('m', "message", &msg, "message", - "tag message", parse_msg_arg), - OPT_FILENAME('F', "file", &msgfile, "read message from file"), - OPT_BOOLEAN('s', "sign", &opt.sign, "annotated and GPG-signed tag"), - OPT_STRING(0, "cleanup", &cleanup_arg, "mode", - "how to strip spaces and #comments from message"), - OPT_STRING('u', "local-user", &keyid, "key-id", - "use another key to sign the tag"), - OPT__FORCE(&force, "replace the tag if exists"), - OPT_COLUMN(0, "column", &colopts, "show tag list in columns"), - - OPT_GROUP("Tag listing options"), + 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, "object", - "print only tags of the object", 0, parse_opt_points_at + OPTION_CALLBACK, 0, "points-at", NULL, N_("object"), + N_("print only tags of the object"), 0, parse_opt_points_at }, OPT_END() }; @@ -584,7 +582,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix) die(_("%s: cannot lock the ref"), ref.buf); if (write_ref_sha1(lock, object, NULL) < 0) die(_("%s: cannot update the ref"), ref.buf); - if (force && hashcmp(prev, object)) + if (force && !is_null_sha1(prev) && hashcmp(prev, object)) printf(_("Updated tag '%s' (was %s)\n"), tag, find_unique_abbrev(prev, DEFAULT_ABBREV)); strbuf_release(&buf); diff --git a/builtin/update-index.c b/builtin/update-index.c index 5a4e9ea55a..c317981516 100644 --- a/builtin/update-index.c +++ b/builtin/update-index.c @@ -83,7 +83,7 @@ static int process_lstat_error(const char *path, int err) return error("lstat(\"%s\"): %s", path, strerror(errno)); } -static int add_one_path(struct cache_entry *old, const char *path, int len, struct stat *st) +static int add_one_path(const struct cache_entry *old, const char *path, int len, struct stat *st) { int option, size; struct cache_entry *ce; @@ -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); @@ -141,7 +142,7 @@ static int process_directory(const char *path, int len, struct stat *st) /* Exact match: file or existing gitlink */ if (pos >= 0) { - struct cache_entry *ce = active_cache[pos]; + const struct cache_entry *ce = active_cache[pos]; if (S_ISGITLINK(ce->ce_mode)) { /* Do nothing to the index if there is no HEAD! */ @@ -157,7 +158,7 @@ static int process_directory(const char *path, int len, struct stat *st) /* Inexact match: is there perhaps a subdirectory match? */ pos = -pos-1; while (pos < active_nr) { - struct cache_entry *ce = active_cache[pos++]; + const struct cache_entry *ce = active_cache[pos++]; if (strncmp(ce->name, path, len)) break; @@ -182,7 +183,7 @@ static int process_path(const char *path) { int pos, len; struct stat st; - struct cache_entry *ce; + const struct cache_entry *ce; len = strlen(path); if (has_symlink_leading_path(path, len)) @@ -229,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; @@ -396,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 }; @@ -427,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; } @@ -445,7 +448,7 @@ static int unresolve_one(const char *path) /* already merged */ pos = unmerge_cache_entry_at(pos); if (pos < active_nr) { - struct cache_entry *ce = active_cache[pos]; + const struct cache_entry *ce = active_cache[pos]; if (ce_stage(ce) && ce_namelen(ce) == namelen && !memcmp(ce->name, path, namelen)) @@ -459,7 +462,7 @@ static int unresolve_one(const char *path) */ pos = -pos-1; if (pos < active_nr) { - struct cache_entry *ce = active_cache[pos]; + const struct cache_entry *ce = active_cache[pos]; if (ce_namelen(ce) == namelen && !memcmp(ce->name, path, namelen)) { fprintf(stderr, @@ -555,7 +558,7 @@ static int do_reupdate(int ac, const char **av, has_head = 0; redo: for (pos = 0; pos < active_nr; pos++) { - struct cache_entry *ce = active_cache[pos]; + const struct cache_entry *ce = active_cache[pos]; struct cache_entry *old = NULL; int save_nr; @@ -590,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; } @@ -711,88 +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, - "write index in this 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); 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 0d63c4498c..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,7 +11,7 @@ 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() }; diff --git a/builtin/upload-archive.c b/builtin/upload-archive.c index b928beb8ed..af2da35e7d 100644 --- a/builtin/upload-archive.c +++ b/builtin/upload-archive.c @@ -7,6 +7,7 @@ #include "pkt-line.h" #include "sideband.h" #include "run-command.h" +#include "argv-array.h" static const char upload_archive_usage[] = "git upload-archive <repo>"; @@ -18,51 +19,31 @@ static const char deadchild[] = int cmd_upload_archive_writer(int argc, const char **argv, const char *prefix) { - const char *sent_argv[MAX_ARGS]; + struct argv_array sent_argv = ARGV_ARRAY_INIT; const char *arg_cmd = "argument "; - char *p, buf[4096]; - int sent_argc; - int len; if (argc != 2) usage(upload_archive_usage); - if (strlen(argv[1]) + 1 > sizeof(buf)) - die("insanely long repository name"); - - strcpy(buf, argv[1]); /* enter-repo smudges its argument */ - - if (!enter_repo(buf, 0)) - die("'%s' does not appear to be a git repository", buf); + if (!enter_repo(argv[1], 0)) + die("'%s' does not appear to be a git repository", argv[1]); /* put received options in sent_argv[] */ - sent_argc = 1; - sent_argv[0] = "git-upload-archive"; - for (p = buf;;) { - /* This will die if not enough free space in buf */ - len = packet_read_line(0, p, (buf + sizeof buf) - p); - if (len == 0) + argv_array_push(&sent_argv, "git-upload-archive"); + for (;;) { + char *buf = packet_read_line(0, NULL); + if (!buf) break; /* got a flush */ - if (sent_argc > MAX_ARGS - 2) - die("Too many options (>%d)", MAX_ARGS - 2); + if (sent_argv.argc > MAX_ARGS) + die("Too many options (>%d)", MAX_ARGS - 1); - if (p[len-1] == '\n') { - p[--len] = 0; - } - if (len < strlen(arg_cmd) || - strncmp(arg_cmd, p, strlen(arg_cmd))) + if (prefixcmp(buf, arg_cmd)) die("'argument' token or flush expected"); - - len -= strlen(arg_cmd); - memmove(p, p + strlen(arg_cmd), len); - sent_argv[sent_argc++] = p; - p += len; - *p++ = 0; + argv_array_push(&sent_argv, buf + strlen(arg_cmd)); } - sent_argv[sent_argc] = NULL; /* parse all options sent by the client */ - return write_archive(sent_argc, sent_argv, prefix, 0, NULL, 1); + return write_archive(sent_argv.argc, sent_argv.argv, prefix, 0, NULL, 1); } __attribute__((format (printf, 1, 2))) diff --git a/builtin/verify-pack.c b/builtin/verify-pack.c index e841b4a38d..66cd2df0f8 100644 --- a/builtin/verify-pack.c +++ b/builtin/verify-pack.c @@ -53,7 +53,7 @@ static int verify_one_pack(const char *path, unsigned int flags) } 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 }; @@ -63,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() }; diff --git a/builtin/verify-tag.c b/builtin/verify-tag.c index 986789f706..9cdf332333 100644 --- a/builtin/verify-tag.c +++ b/builtin/verify-tag.c @@ -14,7 +14,7 @@ #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 }; @@ -29,7 +29,7 @@ static int run_gpg_verify(const char *buf, unsigned long size, int verbose) if (size == len) return error("no signature found"); - return verify_signed_buffer(buf, len, buf + len, size - len, NULL); + return verify_signed_buffer(buf, len, buf + len, size - len, NULL, NULL); } static int verify_tag(const char *name, int verbose) @@ -70,7 +70,7 @@ 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() }; 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() |