diff options
Diffstat (limited to 'builtin')
84 files changed, 5329 insertions, 3054 deletions
diff --git a/builtin/add.c b/builtin/add.c index 56a4e0af6b..c59b0c98fe 100644 --- a/builtin/add.c +++ b/builtin/add.c @@ -21,12 +21,32 @@ static const char * const builtin_add_usage[] = { static int patch_interactive, add_interactive, edit_interactive; static int take_worktree_changes; -struct update_callback_data -{ +struct update_callback_data { int flags; int add_errors; }; +static int fix_unmerged_status(struct diff_filepair *p, + struct update_callback_data *data) +{ + if (p->status != DIFF_STATUS_UNMERGED) + return p->status; + if (!(data->flags & ADD_CACHE_IGNORE_REMOVAL) && !p->two->mode) + /* + * This is not an explicit add request, and the + * path is missing from the working tree (deleted) + */ + return DIFF_STATUS_DELETED; + else + /* + * Either an explicit add request, or path exists + * in the working tree. An attempt to explicitly + * add a path that does not exist in the working tree + * will be caught as an error by the caller immediately. + */ + return DIFF_STATUS_MODIFIED; +} + static void update_callback(struct diff_queue_struct *q, struct diff_options *opt, void *cbdata) { @@ -36,35 +56,14 @@ static void update_callback(struct diff_queue_struct *q, for (i = 0; i < q->nr; i++) { struct diff_filepair *p = q->queue[i]; const char *path = p->one->path; - switch (p->status) { + switch (fix_unmerged_status(p, data)) { default: - die("unexpected diff status %c", p->status); - case DIFF_STATUS_UNMERGED: - /* - * ADD_CACHE_IGNORE_REMOVAL is unset if "git - * add -u" is calling us, In such a case, a - * missing work tree file needs to be removed - * if there is an unmerged entry at stage #2, - * but such a diff record is followed by - * another with DIFF_STATUS_DELETED (and if - * there is no stage #2, we won't see DELETED - * nor MODIFIED). We can simply continue - * either way. - */ - if (!(data->flags & ADD_CACHE_IGNORE_REMOVAL)) - continue; - /* - * Otherwise, it is "git add path" is asking - * to explicitly add it; we fall through. A - * missing work tree file is an error and is - * caught by add_file_to_index() in such a - * case. - */ + die(_("unexpected diff status %c"), p->status); case DIFF_STATUS_MODIFIED: case DIFF_STATUS_TYPE_CHANGED: if (add_file_to_index(&the_index, path, data->flags)) { if (!(data->flags & ADD_CACHE_IGNORE_ERRORS)) - die("updating files failed"); + die(_("updating files failed")); data->add_errors++; } break; @@ -74,7 +73,7 @@ static void update_callback(struct diff_queue_struct *q, if (!(data->flags & ADD_CACHE_PRETEND)) remove_file_from_index(&the_index, path); if (data->flags & (ADD_CACHE_PRETEND|ADD_CACHE_VERBOSE)) - printf("remove '%s'\n", path); + printf(_("remove '%s'\n"), path); break; } } @@ -86,12 +85,13 @@ int add_files_to_cache(const char *prefix, const char **pathspec, int flags) struct rev_info rev; init_revisions(&rev, prefix); setup_revisions(0, NULL, &rev, NULL); - rev.prune_data = pathspec; + init_pathspec(&rev.prune_data, pathspec); rev.diffopt.output_format = DIFF_FORMAT_CALLBACK; rev.diffopt.format_callback = update_callback; data.flags = flags; data.add_errors = 0; rev.diffopt.format_callback_data = &data; + rev.max_count = 0; /* do not compare unmerged paths with stage #2 */ run_diff_files(&rev, DIFF_RACY_IS_MODIFIED); return !!data.add_errors; } @@ -172,7 +172,7 @@ static void treat_gitlinks(const char **pathspec) /* strip trailing slash */ pathspec[j] = xstrndup(ce->name, len); else - die ("Path '%s' is in submodule '%.*s'", + die (_("Path '%s' is in submodule '%.*s'"), pathspec[j], len, ce->name); } } @@ -188,10 +188,10 @@ static void refresh(int verbose, const char **pathspec) /* nothing */; seen = xcalloc(specs, 1); refresh_index(&the_index, verbose ? REFRESH_IN_PORCELAIN : REFRESH_QUIET, - pathspec, seen, "Unstaged changes after refreshing the index:"); + pathspec, seen, _("Unstaged changes after refreshing the index:")); for (i = 0; i < specs; i++) { if (!seen[i]) - die("pathspec '%s' did not match any files", pathspec[i]); + die(_("pathspec '%s' did not match any files"), pathspec[i]); } free(seen); } @@ -205,7 +205,7 @@ static const char **validate_pathspec(int argc, const char **argv, const char *p for (p = pathspec; *p; p++) { if (has_symlink_leading_path(*p, strlen(*p))) { int len = prefix ? strlen(prefix) : 0; - die("'%s' is beyond a symbolic link", *p + len); + die(_("'%s' is beyond a symbolic link"), *p + len); } } } @@ -242,7 +242,7 @@ int run_add_interactive(const char *revision, const char *patch_mode, return status; } -int interactive_add(int argc, const char **argv, const char *prefix) +int interactive_add(int argc, const char **argv, const char *prefix, int patch) { const char **pathspec = NULL; @@ -253,7 +253,7 @@ int interactive_add(int argc, const char **argv, const char *prefix) } return run_add_interactive(NULL, - patch_interactive ? "--patch" : NULL, + patch ? "--patch" : NULL, pathspec); } @@ -272,7 +272,7 @@ static int edit_patch(int argc, const char **argv, const char *prefix) git_config(git_diff_basic_config, NULL); /* no "diff" UI options */ if (read_cache() < 0) - die ("Could not read the index"); + die (_("Could not read the index")); init_revisions(&rev, prefix); rev.diffopt.context = 7; @@ -281,24 +281,24 @@ static int edit_patch(int argc, const char **argv, const char *prefix) rev.diffopt.output_format = DIFF_FORMAT_PATCH; out = open(file, O_CREAT | O_WRONLY, 0644); if (out < 0) - die ("Could not open '%s' for writing.", file); + die (_("Could not open '%s' for writing."), file); rev.diffopt.file = xfdopen(out, "w"); rev.diffopt.close_file = 1; if (run_diff_files(&rev, 0)) - die ("Could not write patch"); + die (_("Could not write patch")); launch_editor(file, NULL, NULL); if (stat(file, &st)) - die_errno("Could not stat '%s'", file); + die_errno(_("Could not stat '%s'"), file); if (!st.st_size) - die("Empty patch. Aborted."); + die(_("Empty patch. Aborted.")); memset(&child, 0, sizeof(child)); child.git_cmd = 1; child.argv = apply_argv; if (run_command(&child)) - die ("Could not apply '%s'", file); + die (_("Could not apply '%s'"), file); unlink(file); return 0; @@ -307,22 +307,22 @@ static int edit_patch(int argc, const char **argv, const char *prefix) static struct lock_file lock_file; static const char ignore_error[] = -"The following paths are ignored by one of your .gitignore files:\n"; +N_("The following paths are ignored by one of your .gitignore files:\n"); static int verbose = 0, show_only = 0, ignored_too = 0, refresh_only = 0; static int ignore_add_errors, addremove, intent_to_add, ignore_missing = 0; static struct option builtin_add_options[] = { - OPT__DRY_RUN(&show_only), - OPT__VERBOSE(&verbose), + OPT__DRY_RUN(&show_only, "dry run"), + OPT__VERBOSE(&verbose, "be verbose"), OPT_GROUP(""), OPT_BOOLEAN('i', "interactive", &add_interactive, "interactive picking"), - OPT_BOOLEAN('p', "patch", &patch_interactive, "interactive patching"), + OPT_BOOLEAN('p', "patch", &patch_interactive, "select hunks interactively"), OPT_BOOLEAN('e', "edit", &edit_interactive, "edit current diff and apply"), - OPT_BOOLEAN('f', "force", &ignored_too, "allow adding otherwise ignored files"), + OPT__FORCE(&ignored_too, "allow adding otherwise ignored files"), OPT_BOOLEAN('u', "update", &take_worktree_changes, "update tracked files"), OPT_BOOLEAN('N', "intent-to-add", &intent_to_add, "record only the fact that the path will be added later"), - OPT_BOOLEAN('A', "all", &addremove, "add all, noticing removal of tracked files"), + OPT_BOOLEAN('A', "all", &addremove, "add changes from all tracked and untracked files"), OPT_BOOLEAN( 0 , "refresh", &refresh_only, "don't add, only refresh the index"), OPT_BOOLEAN( 0 , "ignore-errors", &ignore_add_errors, "just skip files which cannot be added because of errors"), OPT_BOOLEAN( 0 , "ignore-missing", &ignore_missing, "check if - even missing - files are ignored in dry run"), @@ -331,7 +331,8 @@ static struct option builtin_add_options[] = { static int add_config(const char *var, const char *value, void *cb) { - if (!strcasecmp(var, "add.ignore-errors")) { + if (!strcmp(var, "add.ignoreerrors") || + !strcmp(var, "add.ignore-errors")) { ignore_add_errors = git_config_bool(var, value); return 0; } @@ -343,17 +344,17 @@ static int add_files(struct dir_struct *dir, int flags) int i, exit_status = 0; if (dir->ignored_nr) { - fprintf(stderr, ignore_error); + fprintf(stderr, _(ignore_error)); for (i = 0; i < dir->ignored_nr; i++) fprintf(stderr, "%s\n", dir->ignored[i]->name); - fprintf(stderr, "Use -f if you really want to add them.\n"); - die("no files added"); + fprintf(stderr, _("Use -f if you really want to add them.\n")); + die(_("no files added")); } for (i = 0; i < dir->nr; i++) if (add_file_to_cache(dir->entries[i]->name, flags)) { if (!ignore_add_errors) - die("adding files failed"); + die(_("adding files failed")); exit_status = 1; } return exit_status; @@ -377,7 +378,7 @@ int cmd_add(int argc, const char **argv, const char *prefix) if (patch_interactive) add_interactive = 1; if (add_interactive) - exit(interactive_add(argc - 1, argv + 1, prefix)); + exit(interactive_add(argc - 1, argv + 1, prefix, patch_interactive)); if (edit_interactive) return(edit_patch(argc, argv, prefix)); @@ -385,9 +386,9 @@ int cmd_add(int argc, const char **argv, const char *prefix) argv++; if (addremove && take_worktree_changes) - die("-A and -u are mutually incompatible"); + die(_("-A and -u are mutually incompatible")); if (!show_only && ignore_missing) - die("Option --ignore-missing can only be used together with --dry-run"); + die(_("Option --ignore-missing can only be used together with --dry-run")); if ((addremove || take_worktree_changes) && !argc) { static const char *here[2] = { ".", NULL }; argc = 1; @@ -407,14 +408,14 @@ int cmd_add(int argc, const char **argv, const char *prefix) ? ADD_CACHE_IGNORE_REMOVAL : 0)); if (require_pathspec && argc == 0) { - fprintf(stderr, "Nothing specified, nothing added.\n"); - fprintf(stderr, "Maybe you wanted to say 'git add .'?\n"); + fprintf(stderr, _("Nothing specified, nothing added.\n")); + fprintf(stderr, _("Maybe you wanted to say 'git add .'?\n")); return 0; } pathspec = validate_pathspec(argc, argv, prefix); if (read_cache() < 0) - die("index file corrupt"); + die(_("index file corrupt")); treat_gitlinks(pathspec); if (add_new_files) { @@ -446,10 +447,11 @@ int cmd_add(int argc, const char **argv, const char *prefix) if (!seen[i] && pathspec[i][0] && !file_exists(pathspec[i])) { if (ignore_missing) { - if (excluded(&dir, pathspec[i], DT_UNKNOWN)) + int dtype = DT_UNKNOWN; + if (excluded(&dir, pathspec[i], &dtype)) dir_add_ignored(&dir, pathspec[i], strlen(pathspec[i])); } else - die("pathspec '%s' did not match any files", + die(_("pathspec '%s' did not match any files"), pathspec[i]); } } @@ -465,7 +467,7 @@ int cmd_add(int argc, const char **argv, const char *prefix) if (active_cache_changed) { if (write_cache(newfd, active_cache, active_nr) || commit_locked_index(&lock_file)) - die("Unable to write new index file"); + die(_("Unable to write new index file")); } return exit_status; diff --git a/builtin/apply.c b/builtin/apply.c index 12ef9ea8af..f2edc52818 100644 --- a/builtin/apply.c +++ b/builtin/apply.c @@ -43,6 +43,7 @@ static int apply = 1; static int apply_in_reverse; static int apply_with_reject; static int apply_verbosely; +static int allow_overlap; static int no_add; static const char *fake_ancestor; static int line_termination = '\n'; @@ -204,6 +205,7 @@ struct line { unsigned hash : 24; unsigned flag : 8; #define LINE_COMMON 1 +#define LINE_PATCHED 2 }; /* @@ -416,48 +418,210 @@ static char *squash_slash(char *name) return name; } -static char *find_name(const char *line, char *def, int p_value, int terminate) +static char *find_name_gnu(const char *line, char *def, int p_value) { - int len; - const char *start = NULL; + struct strbuf name = STRBUF_INIT; + char *cp; - if (p_value == 0) - start = line; + /* + * Proposed "new-style" GNU patch/diff format; see + * http://marc.theaimsgroup.com/?l=git&m=112927316408690&w=2 + */ + if (unquote_c_style(&name, line, NULL)) { + strbuf_release(&name); + return NULL; + } - if (*line == '"') { - struct strbuf name = STRBUF_INIT; + for (cp = name.buf; p_value; p_value--) { + cp = strchr(cp, '/'); + if (!cp) { + strbuf_release(&name); + return NULL; + } + cp++; + } - /* - * Proposed "new-style" GNU patch/diff format; see - * http://marc.theaimsgroup.com/?l=git&m=112927316408690&w=2 - */ - if (!unquote_c_style(&name, line, NULL)) { - char *cp; + /* name can later be freed, so we need + * to memmove, not just return cp + */ + strbuf_remove(&name, 0, cp - name.buf); + free(def); + if (root) + strbuf_insert(&name, 0, root, root_len); + return squash_slash(strbuf_detach(&name, NULL)); +} - for (cp = name.buf; p_value; p_value--) { - cp = strchr(cp, '/'); - if (!cp) - break; - cp++; - } - if (cp) { - /* name can later be freed, so we need - * to memmove, not just return cp - */ - strbuf_remove(&name, 0, cp - name.buf); - free(def); - if (root) - strbuf_insert(&name, 0, root, root_len); - return squash_slash(strbuf_detach(&name, NULL)); - } - } - strbuf_release(&name); +static size_t sane_tz_len(const char *line, size_t len) +{ + const char *tz, *p; + + if (len < strlen(" +0500") || line[len-strlen(" +0500")] != ' ') + return 0; + tz = line + len - strlen(" +0500"); + + if (tz[1] != '+' && tz[1] != '-') + return 0; + + for (p = tz + 2; p != line + len; p++) + if (!isdigit(*p)) + return 0; + + return line + len - tz; +} + +static size_t tz_with_colon_len(const char *line, size_t len) +{ + const char *tz, *p; + + if (len < strlen(" +08:00") || line[len - strlen(":00")] != ':') + return 0; + tz = line + len - strlen(" +08:00"); + + if (tz[0] != ' ' || (tz[1] != '+' && tz[1] != '-')) + return 0; + p = tz + 2; + if (!isdigit(*p++) || !isdigit(*p++) || *p++ != ':' || + !isdigit(*p++) || !isdigit(*p++)) + return 0; + + return line + len - tz; +} + +static size_t date_len(const char *line, size_t len) +{ + const char *date, *p; + + if (len < strlen("72-02-05") || line[len-strlen("-05")] != '-') + return 0; + p = date = line + len - strlen("72-02-05"); + + if (!isdigit(*p++) || !isdigit(*p++) || *p++ != '-' || + !isdigit(*p++) || !isdigit(*p++) || *p++ != '-' || + !isdigit(*p++) || !isdigit(*p++)) /* Not a date. */ + return 0; + + if (date - line >= strlen("19") && + isdigit(date[-1]) && isdigit(date[-2])) /* 4-digit year */ + date -= strlen("19"); + + return line + len - date; +} + +static size_t short_time_len(const char *line, size_t len) +{ + const char *time, *p; + + if (len < strlen(" 07:01:32") || line[len-strlen(":32")] != ':') + return 0; + p = time = line + len - strlen(" 07:01:32"); + + /* Permit 1-digit hours? */ + if (*p++ != ' ' || + !isdigit(*p++) || !isdigit(*p++) || *p++ != ':' || + !isdigit(*p++) || !isdigit(*p++) || *p++ != ':' || + !isdigit(*p++) || !isdigit(*p++)) /* Not a time. */ + return 0; + + return line + len - time; +} + +static size_t fractional_time_len(const char *line, size_t len) +{ + const char *p; + size_t n; + + /* Expected format: 19:41:17.620000023 */ + if (!len || !isdigit(line[len - 1])) + return 0; + p = line + len - 1; + + /* Fractional seconds. */ + while (p > line && isdigit(*p)) + p--; + if (*p != '.') + return 0; + + /* Hours, minutes, and whole seconds. */ + n = short_time_len(line, p - line); + if (!n) + return 0; + + return line + len - p + n; +} + +static size_t trailing_spaces_len(const char *line, size_t len) +{ + const char *p; + + /* Expected format: ' ' x (1 or more) */ + if (!len || line[len - 1] != ' ') + return 0; + + p = line + len; + while (p != line) { + p--; + if (*p != ' ') + return line + len - (p + 1); } - for (;;) { + /* All spaces! */ + return len; +} + +static size_t diff_timestamp_len(const char *line, size_t len) +{ + const char *end = line + len; + size_t n; + + /* + * Posix: 2010-07-05 19:41:17 + * GNU: 2010-07-05 19:41:17.620000023 -0500 + */ + + if (!isdigit(end[-1])) + return 0; + + n = sane_tz_len(line, end - line); + if (!n) + n = tz_with_colon_len(line, end - line); + end -= n; + + n = short_time_len(line, end - line); + if (!n) + n = fractional_time_len(line, end - line); + end -= n; + + n = date_len(line, end - line); + if (!n) /* No date. Too bad. */ + return 0; + end -= n; + + if (end == line) /* No space before date. */ + return 0; + if (end[-1] == '\t') { /* Success! */ + end--; + return line + len - end; + } + if (end[-1] != ' ') /* No space before date. */ + return 0; + + /* Whitespace damage. */ + end -= trailing_spaces_len(line, end - line); + return line + len - end; +} + +static char *find_name_common(const char *line, char *def, int p_value, + const char *end, int terminate) +{ + int len; + const char *start = NULL; + + if (p_value == 0) + start = line; + while (line != end) { char c = *line; - if (isspace(c)) { + if (!end && isspace(c)) { if (c == '\n') break; if (name_terminate(start, line-start, c, terminate)) @@ -497,6 +661,37 @@ static char *find_name(const char *line, char *def, int p_value, int terminate) return squash_slash(xmemdupz(start, len)); } +static char *find_name(const char *line, char *def, int p_value, int terminate) +{ + if (*line == '"') { + char *name = find_name_gnu(line, def, p_value); + if (name) + return name; + } + + return find_name_common(line, def, p_value, NULL, terminate); +} + +static char *find_name_traditional(const char *line, char *def, int p_value) +{ + size_t len = strlen(line); + size_t date_len; + + if (*line == '"') { + char *name = find_name_gnu(line, def, p_value); + if (name) + return name; + } + + len = strchrnul(line, '\n') - line; + date_len = diff_timestamp_len(line, len); + if (!date_len) + return find_name_common(line, def, p_value, NULL, TERM_TAB); + len -= date_len; + + return find_name_common(line, def, p_value, line + len, 0); +} + static int count_slashes(const char *cp) { int cnt = 0; @@ -519,7 +714,7 @@ static int guess_p_value(const char *nameline) if (is_dev_null(nameline)) return -1; - name = find_name(nameline, NULL, 0, TERM_SPACE | TERM_TAB); + name = find_name_traditional(nameline, NULL, 0); if (!name) return -1; cp = strchr(name, '/'); @@ -560,8 +755,8 @@ static int has_epoch_timestamp(const char *nameline) " " "[0-2][0-9]:[0-5][0-9]:00(\\.0+)?" " " - "([-+][0-2][0-9][0-5][0-9])\n"; - const char *timestamp = NULL, *cp; + "([-+][0-2][0-9]:?[0-5][0-9])\n"; + const char *timestamp = NULL, *cp, *colon; static regex_t *stamp; regmatch_t m[10]; int zoneoffset; @@ -591,8 +786,11 @@ static int has_epoch_timestamp(const char *nameline) return 0; } - zoneoffset = strtol(timestamp + m[3].rm_so + 1, NULL, 10); - zoneoffset = (zoneoffset / 100) * 60 + (zoneoffset % 100); + zoneoffset = strtol(timestamp + m[3].rm_so + 1, (char **) &colon, 10); + if (*colon == ':') + zoneoffset = zoneoffset * 60 + strtol(colon + 1, NULL, 10); + else + zoneoffset = (zoneoffset / 100) * 60 + (zoneoffset % 100); if (timestamp[m[3].rm_so] == '-') zoneoffset = -zoneoffset; @@ -638,16 +836,16 @@ static void parse_traditional_patch(const char *first, const char *second, struc if (is_dev_null(first)) { patch->is_new = 1; patch->is_delete = 0; - name = find_name(second, NULL, p_value, TERM_SPACE | TERM_TAB); + name = find_name_traditional(second, NULL, p_value); patch->new_name = name; } else if (is_dev_null(second)) { patch->is_new = 0; patch->is_delete = 1; - name = find_name(first, NULL, p_value, TERM_SPACE | TERM_TAB); + name = find_name_traditional(first, NULL, p_value); patch->old_name = name; } else { - name = find_name(first, NULL, p_value, TERM_SPACE | TERM_TAB); - name = find_name(second, name, p_value, TERM_SPACE | TERM_TAB); + name = find_name_traditional(first, NULL, p_value); + name = find_name_traditional(second, name, p_value); if (has_epoch_timestamp(first)) { patch->is_new = 1; patch->is_delete = 0; @@ -746,28 +944,28 @@ static int gitdiff_newfile(const char *line, struct patch *patch) static int gitdiff_copysrc(const char *line, struct patch *patch) { patch->is_copy = 1; - patch->old_name = find_name(line, NULL, 0, 0); + patch->old_name = find_name(line, NULL, p_value ? p_value - 1 : 0, 0); return 0; } static int gitdiff_copydst(const char *line, struct patch *patch) { patch->is_copy = 1; - patch->new_name = find_name(line, NULL, 0, 0); + patch->new_name = find_name(line, NULL, p_value ? p_value - 1 : 0, 0); return 0; } static int gitdiff_renamesrc(const char *line, struct patch *patch) { patch->is_rename = 1; - patch->old_name = find_name(line, NULL, 0, 0); + patch->old_name = find_name(line, NULL, p_value ? p_value - 1 : 0, 0); return 0; } static int gitdiff_renamedst(const char *line, struct patch *patch) { patch->is_rename = 1; - patch->new_name = find_name(line, NULL, 0, 0); + patch->new_name = find_name(line, NULL, p_value ? p_value - 1 : 0, 0); return 0; } @@ -852,7 +1050,7 @@ static char *git_header_name(char *line, int llen) { const char *name; const char *second = NULL; - size_t len; + size_t len, line_len; line += strlen("diff --git "); llen -= strlen("diff --git "); @@ -952,6 +1150,10 @@ static char *git_header_name(char *line, int llen) * Accept a name only if it shows up twice, exactly the same * form. */ + second = strchr(name, '\n'); + if (!second) + return NULL; + line_len = second - name; for (len = 0 ; ; len++) { switch (name[len]) { default: @@ -959,15 +1161,11 @@ static char *git_header_name(char *line, int llen) case '\n': return NULL; case '\t': case ' ': - second = name+len; - for (;;) { - char c = *second++; - if (c == '\n') - return NULL; - if (c == '/') - break; - } - if (second[len] == '\n' && !memcmp(name, second, len)) { + second = stop_at_slash(name + len, line_len - len); + if (!second) + return NULL; + second++; + if (second[len] == '\n' && !strncmp(name, second, len)) { return xmemdupz(name, len); } } @@ -1436,7 +1634,7 @@ static inline int metadata_changes(struct patch *patch) static char *inflate_it(const void *data, unsigned long size, unsigned long inflated_size) { - z_stream stream; + git_zstream stream; void *out; int st; @@ -1889,7 +2087,8 @@ static int match_fragment(struct image *img, /* Quick hash check */ for (i = 0; i < preimage_limit; i++) - if (preimage->line[i].hash != img->line[try_lno + i].hash) + if ((img->line[try_lno + i].flag & LINE_PATCHED) || + (preimage->line[i].hash != img->line[try_lno + i].hash)) return 0; if (preimage_limit == preimage->nr) { @@ -2232,11 +2431,15 @@ static void update_image(struct image *img, memcpy(img->line + applied_pos, postimage->line, postimage->nr * sizeof(*img->line)); + if (!allow_overlap) + for (i = 0; i < postimage->nr; i++) + img->line[applied_pos + i].flag |= LINE_PATCHED; img->nr = nr; } static int apply_one_fragment(struct image *img, struct fragment *frag, - int inaccurate_eof, unsigned ws_rule) + int inaccurate_eof, unsigned ws_rule, + int nth_fragment) { int match_beginning, match_end; const char *patch = frag->patch; @@ -2442,6 +2645,15 @@ static int apply_one_fragment(struct image *img, struct fragment *frag, apply = 0; } + if (apply_verbosely && applied_pos != pos) { + int offset = applied_pos - pos; + if (apply_in_reverse) + offset = 0 - offset; + fprintf(stderr, + "Hunk #%d succeeded at %d (offset %d lines).\n", + nth_fragment, applied_pos + 1, offset); + } + /* * Warn if it was necessary to reduce the number * of context lines. @@ -2472,6 +2684,12 @@ static int apply_binary_fragment(struct image *img, struct patch *patch) unsigned long len; void *dst; + if (!fragment) + return error("missing binary patch data for '%s'", + patch->new_name ? + patch->new_name : + patch->old_name); + /* Binary patch is irreversible without the optional second hunk */ if (apply_in_reverse) { if (!fragment->next) @@ -2583,12 +2801,14 @@ static int apply_fragments(struct image *img, struct patch *patch) const char *name = patch->old_name ? patch->old_name : patch->new_name; unsigned ws_rule = patch->ws_rule; unsigned inaccurate_eof = patch->inaccurate_eof; + int nth = 0; if (patch->is_binary) return apply_binary(img, patch); while (frag) { - if (apply_one_fragment(img, frag, inaccurate_eof, ws_rule)) { + nth++; + if (apply_one_fragment(img, frag, inaccurate_eof, ws_rule, nth)) { error("patch failed: %s:%ld", name, frag->oldpos); if (!apply_with_reject) return -1; @@ -2979,8 +3199,7 @@ static void build_fake_ancestor(struct patch *list, const char *filename) else if (get_sha1(patch->old_sha1_prefix, sha1)) /* git diff has no index line for mode/type changes */ if (!patch->lines_added && !patch->lines_deleted) { - if (get_current_sha1(patch->new_name, sha1) || - get_current_sha1(patch->old_name, sha1)) + if (get_current_sha1(patch->old_name, sha1)) die("mode change for %s, which is not " "in current HEAD", name); sha1_ptr = sha1; @@ -3607,11 +3826,11 @@ static int option_parse_directory(const struct option *opt, return 0; } -int cmd_apply(int argc, const char **argv, const char *unused_prefix) +int cmd_apply(int argc, const char **argv, const char *prefix_) { int i; int errs = 0; - int is_not_gitdir; + int is_not_gitdir = !startup_info->have_repository; int binary; int force_apply = 0; @@ -3671,7 +3890,9 @@ int cmd_apply(int argc, const char **argv, const char *unused_prefix) "don't expect at least one line of context"), OPT_BOOLEAN(0, "reject", &apply_with_reject, "leave the rejected hunks in corresponding *.rej files"), - OPT__VERBOSE(&apply_verbosely), + OPT_BOOLEAN(0, "allow-overlap", &allow_overlap, + "allow overlapping hunks"), + OPT__VERBOSE(&apply_verbosely, "be verbose"), OPT_BIT(0, "inaccurate-eof", &options, "tolerate incorrectly detected missing new-line at the end of file", INACCURATE_EOF), @@ -3684,7 +3905,7 @@ int cmd_apply(int argc, const char **argv, const char *unused_prefix) OPT_END() }; - prefix = setup_git_directory_gently(&is_not_gitdir); + prefix = prefix_; prefix_length = prefix ? strlen(prefix) : 0; git_config(git_apply_config, NULL); if (apply_default_whitespace) diff --git a/builtin/archive.c b/builtin/archive.c index 6a887f5a9d..883c0092ad 100644 --- a/builtin/archive.c +++ b/builtin/archive.c @@ -14,17 +14,18 @@ static void create_output_file(const char *output_file) { int output_fd = open(output_file, O_CREAT | O_WRONLY | O_TRUNC, 0666); if (output_fd < 0) - die_errno("could not create archive file '%s'", output_file); + die_errno(_("could not create archive file '%s'"), output_file); if (output_fd != 1) { if (dup2(output_fd, 1) < 0) - die_errno("could not redirect output"); + die_errno(_("could not redirect output")); else close(output_fd); } } static int run_remote_archiver(int argc, const char **argv, - const char *remote, const char *exec) + const char *remote, const char *exec, + const char *name_hint) { char buf[LARGE_PACKET_MAX]; int fd[2], i, len, rv; @@ -33,28 +34,39 @@ static int run_remote_archiver(int argc, const char **argv, _remote = remote_get(remote); if (!_remote->url[0]) - die("git archive: Remote with no URL"); + die(_("git archive: Remote with no URL")); transport = transport_get(_remote, _remote->url[0]); transport_connect(transport, "git-upload-archive", exec, fd); + /* + * Inject a fake --format field at the beginning of the + * arguments, with the format inferred from our output + * filename. This way explicit --format options can override + * it. + */ + if (name_hint) { + const char *format = archive_format_from_filename(name_hint); + if (format) + packet_write(fd[1], "argument --format=%s\n", format); + } for (i = 1; i < argc; i++) packet_write(fd[1], "argument %s\n", argv[i]); packet_flush(fd[1]); len = packet_read_line(fd[0], buf, sizeof(buf)); if (!len) - die("git archive: expected ACK/NAK, got EOF"); + die(_("git archive: expected ACK/NAK, got EOF")); if (buf[len-1] == '\n') buf[--len] = 0; if (strcmp(buf, "ACK")) { if (len > 5 && !prefixcmp(buf, "NACK ")) - die("git archive: NACK %s", buf + 5); - die("git archive: protocol error"); + die(_("git archive: NACK %s"), buf + 5); + die(_("git archive: protocol error")); } len = packet_read_line(fd[0], buf, sizeof(buf)); if (len) - die("git archive: expected a flush"); + die(_("git archive: expected a flush")); /* Now, start reading from fd[0] and spit it out to stdout */ rv = recv_sideband("archive", fd[0], 1); @@ -63,17 +75,6 @@ static int run_remote_archiver(int argc, const char **argv, return !!rv; } -static const char *format_from_name(const char *filename) -{ - const char *ext = strrchr(filename, '.'); - if (!ext) - return NULL; - ext++; - if (!strcasecmp(ext, "zip")) - return "--format=zip"; - return NULL; -} - #define PARSE_OPT_KEEP_ALL ( PARSE_OPT_KEEP_DASHDASH | \ PARSE_OPT_KEEP_ARGV0 | \ PARSE_OPT_KEEP_UNKNOWN | \ @@ -84,7 +85,6 @@ int cmd_archive(int argc, const char **argv, const char *prefix) const char *exec = "git-upload-archive"; const char *output = NULL; const char *remote = NULL; - const char *format_option = NULL; struct option local_opts[] = { OPT_STRING('o', "output", &output, "file", "write the archive to this file"), @@ -98,32 +98,13 @@ int cmd_archive(int argc, const char **argv, const char *prefix) argc = parse_options(argc, argv, prefix, local_opts, NULL, PARSE_OPT_KEEP_ALL); - if (output) { + if (output) create_output_file(output); - format_option = format_from_name(output); - } - - /* - * We have enough room in argv[] to muck it in place, because - * --output must have been given on the original command line - * if we get to this point, and parse_options() must have eaten - * it, i.e. we can add back one element to the array. - * - * We add a fake --format option at the beginning, with the - * format inferred from our output filename. This way explicit - * --format options can override it, and the fake option is - * inserted before any "--" that might have been given. - */ - if (format_option) { - memmove(argv + 2, argv + 1, sizeof(*argv) * argc); - argv[1] = format_option; - argv[++argc] = NULL; - } if (remote) - return run_remote_archiver(argc, argv, remote, exec); + return run_remote_archiver(argc, argv, remote, exec, output); setvbuf(stderr, NULL, _IOLBF, BUFSIZ); - return write_archive(argc, argv, prefix, 1); + return write_archive(argc, argv, prefix, 1, output, 0); } diff --git a/builtin/bisect--helper.c b/builtin/bisect--helper.c index 5b226399e1..8d325a5179 100644 --- a/builtin/bisect--helper.c +++ b/builtin/bisect--helper.c @@ -4,16 +4,19 @@ #include "bisect.h" static const char * const git_bisect_helper_usage[] = { - "git bisect--helper --next-all", + "git bisect--helper --next-all [--no-checkout]", NULL }; int cmd_bisect__helper(int argc, const char **argv, const char *prefix) { int next_all = 0; + int no_checkout = 0; struct option options[] = { OPT_BOOLEAN(0, "next-all", &next_all, "perform 'git bisect next'"), + OPT_BOOLEAN(0, "no-checkout", &no_checkout, + "update BISECT_HEAD instead of checking out the current commit"), OPT_END() }; @@ -24,5 +27,5 @@ int cmd_bisect__helper(int argc, const char **argv, const char *prefix) usage_with_options(git_bisect_helper_usage, options); /* next-all */ - return bisect_next_all(prefix); + return bisect_next_all(prefix, no_checkout); } diff --git a/builtin/blame.c b/builtin/blame.c index 01e62fdeb0..26a5d424b8 100644 --- a/builtin/blame.c +++ b/builtin/blame.c @@ -41,6 +41,7 @@ static int reverse; static int blank_boundary; static int incremental; static int xdl_opts; +static int abbrev = -1; static enum date_mode blame_date_mode = DATE_ISO8601; static size_t blame_date_width; @@ -83,6 +84,7 @@ struct origin { struct commit *commit; mmfile_t file; unsigned char blob_sha1[20]; + unsigned mode; char path[FLEX_ARRAY]; }; @@ -92,6 +94,7 @@ struct origin { * Return 1 if the conversion succeeds, 0 otherwise. */ int textconv_object(const char *path, + unsigned mode, const unsigned char *sha1, char **buf, unsigned long *buf_size) @@ -100,7 +103,7 @@ int textconv_object(const char *path, struct userdiff_driver *textconv; df = alloc_filespec(path); - fill_filespec(df, sha1, S_IFREG | 0664); + fill_filespec(df, sha1, mode); textconv = get_textconv(df); if (!textconv) { free_filespec(df); @@ -125,7 +128,7 @@ static void fill_origin_blob(struct diff_options *opt, num_read_blob++; if (DIFF_OPT_TST(opt, ALLOW_TEXTCONV) && - textconv_object(o->path, o->blob_sha1, &file->ptr, &file_size)) + textconv_object(o->path, o->mode, o->blob_sha1, &file->ptr, &file_size)) ; else file->ptr = read_sha1_file(o->blob_sha1, &type, &file_size); @@ -313,21 +316,23 @@ static struct origin *get_origin(struct scoreboard *sb, * for an origin is also used to pass the blame for the entire file to * the parent to detect the case where a child's blob is identical to * that of its parent's. + * + * This also fills origin->mode for corresponding tree path. */ -static int fill_blob_sha1(struct origin *origin) +static int fill_blob_sha1_and_mode(struct origin *origin) { - unsigned mode; if (!is_null_sha1(origin->blob_sha1)) return 0; if (get_tree_entry(origin->commit->object.sha1, origin->path, - origin->blob_sha1, &mode)) + origin->blob_sha1, &origin->mode)) goto error_out; if (sha1_object_info(origin->blob_sha1, NULL) != OBJ_BLOB) goto error_out; return 0; error_out: hashclr(origin->blob_sha1); + origin->mode = S_IFINVALID; return -1; } @@ -360,12 +365,14 @@ static struct origin *find_origin(struct scoreboard *sb, /* * If the origin was newly created (i.e. get_origin * would call make_origin if none is found in the - * scoreboard), it does not know the blob_sha1, + * scoreboard), it does not know the blob_sha1/mode, * so copy it. Otherwise porigin was in the - * scoreboard and already knows blob_sha1. + * scoreboard and already knows blob_sha1/mode. */ - if (porigin->refcnt == 1) + if (porigin->refcnt == 1) { hashcpy(porigin->blob_sha1, cached->blob_sha1); + porigin->mode = cached->mode; + } return porigin; } /* otherwise it was not very useful; free it */ @@ -400,6 +407,7 @@ static struct origin *find_origin(struct scoreboard *sb, /* The path is the same as parent */ porigin = get_origin(sb, parent, origin->path); hashcpy(porigin->blob_sha1, origin->blob_sha1); + porigin->mode = origin->mode; } else { /* * Since origin->path is a pathspec, if the parent @@ -425,6 +433,7 @@ static struct origin *find_origin(struct scoreboard *sb, case 'M': porigin = get_origin(sb, parent, origin->path); hashcpy(porigin->blob_sha1, p->one->sha1); + porigin->mode = p->one->mode; break; case 'A': case 'T': @@ -444,6 +453,7 @@ static struct origin *find_origin(struct scoreboard *sb, cached = make_origin(porigin->commit, porigin->path); hashcpy(cached->blob_sha1, porigin->blob_sha1); + cached->mode = porigin->mode; parent->util = cached; } return porigin; @@ -486,6 +496,7 @@ static struct origin *find_rename(struct scoreboard *sb, !strcmp(p->two->path, origin->path)) { porigin = get_origin(sb, parent, p->one->path); hashcpy(porigin->blob_sha1, p->one->sha1); + porigin->mode = p->one->mode; break; } } @@ -1099,6 +1110,7 @@ static int find_copy_in_parent(struct scoreboard *sb, norigin = get_origin(sb, parent, p->one->path); hashcpy(norigin->blob_sha1, p->one->sha1); + norigin->mode = p->one->mode; fill_origin_blob(&sb->revs->diffopt, norigin, &file_p); if (!file_p.ptr) continue; @@ -1301,8 +1313,7 @@ static void pass_blame(struct scoreboard *sb, struct origin *origin, int opt) /* * Information on commits, used for output. */ -struct commit_info -{ +struct commit_info { const char *author; const char *author_mail; unsigned long author_time; @@ -1367,7 +1378,7 @@ static void get_ac_line(const char *inbuf, const char *what, timepos = tmp; *tmp = 0; - while (person < tmp && *tmp != ' ') + while (person < tmp && !(*tmp == ' ' && tmp[1] == '<')) tmp--; if (tmp <= person) return; @@ -1407,7 +1418,8 @@ static void get_commit_info(struct commit *commit, int detailed) { int len; - char *tmp, *endp, *reencoded, *message; + const char *subject; + char *reencoded, *message; static char author_name[1024]; static char author_mail[1024]; static char committer_name[1024]; @@ -1449,22 +1461,13 @@ static void get_commit_info(struct commit *commit, &ret->committer_time, &ret->committer_tz); ret->summary = summary_buf; - tmp = strstr(message, "\n\n"); - if (!tmp) { - error_out: + 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); - return; } - tmp += 2; - endp = strchr(tmp, '\n'); - if (!endp) - endp = tmp + strlen(tmp); - len = endp - tmp; - if (len >= sizeof(summary_buf) || len == 0) - goto error_out; - memcpy(summary_buf, tmp, len); - summary_buf[len] = 0; free(reencoded); } @@ -1481,13 +1484,14 @@ static void write_filename_info(const char *path) /* * Porcelain/Incremental format wants to show a lot of details per * commit. Instead of repeating this every line, emit it only once, - * the first time each commit appears in the output. + * the first time each commit appears in the output (unless the + * user has specifically asked for us to repeat). */ -static int emit_one_suspect_detail(struct origin *suspect) +static int emit_one_suspect_detail(struct origin *suspect, int repeat) { struct commit_info ci; - if (suspect->commit->object.flags & METAINFO_SHOWN) + if (!repeat && (suspect->commit->object.flags & METAINFO_SHOWN)) return 0; suspect->commit->object.flags |= METAINFO_SHOWN; @@ -1526,7 +1530,7 @@ static void found_guilty_entry(struct blame_entry *ent) printf("%s %d %d %d\n", sha1_to_hex(suspect->commit->object.sha1), ent->s_lno + 1, ent->lno + 1, ent->num_lines); - emit_one_suspect_detail(suspect); + emit_one_suspect_detail(suspect, 0); write_filename_info(suspect->path); maybe_flush_or_die(stdout, "stdout"); } @@ -1614,9 +1618,20 @@ static const char *format_time(unsigned long time, const char *tz_str, #define OUTPUT_SHOW_NUMBER 040 #define OUTPUT_SHOW_SCORE 0100 #define OUTPUT_NO_AUTHOR 0200 +#define OUTPUT_SHOW_EMAIL 0400 +#define OUTPUT_LINE_PORCELAIN 01000 -static void emit_porcelain(struct scoreboard *sb, struct blame_entry *ent) +static void emit_porcelain_details(struct origin *suspect, int repeat) { + if (emit_one_suspect_detail(suspect, repeat) || + (suspect->commit->object.flags & MORE_THAN_ONE_PATH)) + write_filename_info(suspect->path); +} + +static void emit_porcelain(struct scoreboard *sb, struct blame_entry *ent, + int opt) +{ + int repeat = opt & OUTPUT_LINE_PORCELAIN; int cnt; const char *cp; struct origin *suspect = ent->suspect; @@ -1629,17 +1644,18 @@ static void emit_porcelain(struct scoreboard *sb, struct blame_entry *ent) ent->s_lno + 1, ent->lno + 1, ent->num_lines); - if (emit_one_suspect_detail(suspect) || - (suspect->commit->object.flags & MORE_THAN_ONE_PATH)) - write_filename_info(suspect->path); + emit_porcelain_details(suspect, repeat); cp = nth_line(sb, ent->lno); for (cnt = 0; cnt < ent->num_lines; cnt++) { char ch; - if (cnt) + if (cnt) { printf("%s %d %d\n", hex, ent->s_lno + 1 + cnt, ent->lno + 1 + cnt); + if (repeat) + emit_porcelain_details(suspect, 1); + } putchar('\t'); do { ch = *cp++; @@ -1667,7 +1683,7 @@ static void emit_other(struct scoreboard *sb, struct blame_entry *ent, int opt) cp = nth_line(sb, ent->lno); for (cnt = 0; cnt < ent->num_lines; cnt++) { char ch; - int length = (opt & OUTPUT_LONG_OBJECT_NAME) ? 40 : 8; + int length = (opt & OUTPUT_LONG_OBJECT_NAME) ? 40 : abbrev; if (suspect->commit->object.flags & UNINTERESTING) { if (blank_boundary) @@ -1679,12 +1695,17 @@ static void emit_other(struct scoreboard *sb, struct blame_entry *ent, int opt) } printf("%.*s", length, hex); - if (opt & OUTPUT_ANNOTATE_COMPAT) - printf("\t(%10s\t%10s\t%d)", ci.author, + if (opt & OUTPUT_ANNOTATE_COMPAT) { + const char *name; + if (opt & OUTPUT_SHOW_EMAIL) + name = ci.author_mail; + else + name = ci.author; + printf("\t(%10s\t%10s\t%d)", name, format_time(ci.author_time, ci.author_tz, show_raw_time), ent->lno + 1 + cnt); - else { + } else { if (opt & OUTPUT_SHOW_SCORE) printf(" %*d %02d", max_score_digits, ent->score, @@ -1697,9 +1718,15 @@ static void emit_other(struct scoreboard *sb, struct blame_entry *ent, int opt) ent->s_lno + 1 + cnt); if (!(opt & OUTPUT_NO_AUTHOR)) { - int pad = longest_author - utf8_strwidth(ci.author); + const char *name; + int pad; + if (opt & OUTPUT_SHOW_EMAIL) + name = ci.author_mail; + else + name = ci.author; + pad = longest_author - utf8_strwidth(name); printf(" (%s%*s %10s", - ci.author, pad, "", + name, pad, "", format_time(ci.author_time, ci.author_tz, show_raw_time)); @@ -1741,7 +1768,7 @@ static void output(struct scoreboard *sb, int option) for (ent = sb->ent; ent; ent = ent->next) { if (option & OUTPUT_PORCELAIN) - emit_porcelain(sb, ent); + emit_porcelain(sb, ent, option); else { emit_other(sb, ent, option); } @@ -1837,7 +1864,10 @@ static void find_alignment(struct scoreboard *sb, int *option) if (!(suspect->commit->object.flags & METAINFO_SHOWN)) { suspect->commit->object.flags |= METAINFO_SHOWN; get_commit_info(suspect->commit, &ci, 1); - num = utf8_strwidth(ci.author); + if (*option & OUTPUT_SHOW_EMAIL) + num = utf8_strwidth(ci.author_mail); + else + num = utf8_strwidth(ci.author); if (longest_author < num) longest_author = num; } @@ -2083,7 +2113,7 @@ static struct commit *fake_working_tree_commit(struct diff_options *opt, switch (st.st_mode & S_IFMT) { case S_IFREG: if (DIFF_OPT_TST(opt, ALLOW_TEXTCONV) && - textconv_object(read_from, null_sha1, &buf.buf, &buf_len)) + textconv_object(read_from, mode, null_sha1, &buf.buf, &buf_len)) buf.len = buf_len; else if (strbuf_read_file(&buf, read_from, st.st_size) != st.st_size) die_errno("cannot open or read '%s'", read_from); @@ -2282,16 +2312,19 @@ int cmd_blame(int argc, const char **argv, const char *prefix) OPT_BIT('f', "show-name", &output_option, "Show original filename (Default: auto)", OUTPUT_SHOW_NAME), OPT_BIT('n', "show-number", &output_option, "Show original linenumber (Default: off)", OUTPUT_SHOW_NUMBER), OPT_BIT('p', "porcelain", &output_option, "Show in a format designed for machine consumption", OUTPUT_PORCELAIN), + OPT_BIT(0, "line-porcelain", &output_option, "Show porcelain format with per-line commit information", OUTPUT_PORCELAIN|OUTPUT_LINE_PORCELAIN), OPT_BIT('c', NULL, &output_option, "Use the same output mode as git-annotate (Default: off)", OUTPUT_ANNOTATE_COMPAT), OPT_BIT('t', NULL, &output_option, "Show raw timestamp (Default: off)", OUTPUT_RAW_TIMESTAMP), OPT_BIT('l', NULL, &output_option, "Show long commit SHA1 (Default: off)", OUTPUT_LONG_OBJECT_NAME), OPT_BIT('s', NULL, &output_option, "Suppress author name and timestamp (Default: off)", OUTPUT_NO_AUTHOR), + OPT_BIT('e', "show-email", &output_option, "Show author email instead of name (Default: off)", OUTPUT_SHOW_EMAIL), OPT_BIT('w', NULL, &xdl_opts, "Ignore whitespace differences", XDF_IGNORE_WHITESPACE), OPT_STRING('S', NULL, &revs_file, "file", "Use revisions from <file> instead of calling git-rev-list"), OPT_STRING(0, "contents", &contents_from, "file", "Use <file>'s contents as the final image"), { OPTION_CALLBACK, 'C', NULL, &opt, "score", "Find line copies within and across files", PARSE_OPT_OPTARG, blame_copy_callback }, { OPTION_CALLBACK, 'M', NULL, &opt, "score", "Find line movements within and across files", PARSE_OPT_OPTARG, blame_move_callback }, OPT_CALLBACK('L', NULL, &bottomtop, "n,m", "Process only line range n,m, counting from 1", blame_bottomtop_callback), + OPT__ABBREV(&abbrev), OPT_END() }; @@ -2306,8 +2339,8 @@ int cmd_blame(int argc, const char **argv, const char *prefix) save_commit_buffer = 0; dashdash_pos = 0; - parse_options_start(&ctx, argc, argv, prefix, PARSE_OPT_KEEP_DASHDASH | - PARSE_OPT_KEEP_ARGV0); + parse_options_start(&ctx, argc, argv, prefix, options, + PARSE_OPT_KEEP_DASHDASH | PARSE_OPT_KEEP_ARGV0); for (;;) { switch (parse_options_step(&ctx, options, blame_opt_usage)) { case PARSE_OPT_HELP: @@ -2327,6 +2360,11 @@ int cmd_blame(int argc, const char **argv, const char *prefix) parse_done: argc = parse_options_end(&ctx); + if (abbrev == -1) + abbrev = default_abbrev; + /* one more abbrev length is needed for the boundary commit */ + abbrev++; + if (revs_file && read_ancestry(revs_file)) die_errno("reading graft file '%s' failed", revs_file); @@ -2376,11 +2414,11 @@ parse_done: * * The remaining are: * - * (1) if dashdash_pos != 0, its either + * (1) if dashdash_pos != 0, it is either * "blame [revisions] -- <path>" or * "blame -- <path> <rev>" * - * (2) otherwise, its one of the two: + * (2) otherwise, it is one of the two: * "blame [revisions] <path>" * "blame <path> <rev>" * @@ -2463,11 +2501,11 @@ parse_done: } else { o = get_origin(&sb, sb.final, path); - if (fill_blob_sha1(o)) + if (fill_blob_sha1_and_mode(o)) die("no such path %s in %s", path, final_commit_name); if (DIFF_OPT_TST(&sb.revs->diffopt, ALLOW_TEXTCONV) && - textconv_object(path, o->blob_sha1, (char **) &sb.final_buf, + textconv_object(path, o->mode, o->blob_sha1, (char **) &sb.final_buf, &sb.final_buf_size)) ; else diff --git a/builtin/branch.c b/builtin/branch.c index 87976f0921..aa705a0fb0 100644 --- a/builtin/branch.c +++ b/builtin/branch.c @@ -19,7 +19,7 @@ static const char * const builtin_branch_usage[] = { "git branch [options] [-r | -a] [--merged | --no-merged]", "git branch [options] [-l] [-f] <branchname> [<start-point>]", - "git branch [options] [-r] (-d | -D) <branchname>", + "git branch [options] [-r] (-d | -D) <branchname>...", "git branch [options] (-m | -M) [<oldbranch>] <newbranch>", NULL }; @@ -71,7 +71,7 @@ static int parse_branch_color_slot(const char *var, int ofs) static int git_branch_config(const char *var, const char *value, void *cb) { if (!strcmp(var, "color.branch")) { - branch_use_color = git_config_colorbool(var, value, -1); + branch_use_color = git_config_colorbool(var, value); return 0; } if (!prefixcmp(var, "color.branch.")) { @@ -88,7 +88,7 @@ static int git_branch_config(const char *var, const char *value, void *cb) static const char *branch_get_color(enum color_branch ix) { - if (branch_use_color > 0) + if (want_color(branch_use_color)) return branch_colors[ix]; return ""; } @@ -133,12 +133,12 @@ static int branch_merged(int kind, const char *name, if ((head_rev != reference_rev) && in_merge_bases(rev, &head_rev, 1) != merged) { if (merged) - warning("deleting branch '%s' that has been merged to\n" - " '%s', but it is not yet merged to HEAD.", + warning(_("deleting branch '%s' that has been merged to\n" + " '%s', but not yet merged to HEAD."), name, reference_name); else - warning("not deleting branch '%s' that is not yet merged to\n" - " '%s', even though it is merged to HEAD.", + warning(_("not deleting branch '%s' that is not yet merged to\n" + " '%s', even though it is merged to HEAD."), name, reference_name); } return merged; @@ -157,7 +157,8 @@ static int delete_branches(int argc, const char **argv, int force, int kinds) switch (kinds) { case REF_REMOTE_BRANCH: fmt = "refs/remotes/%s"; - remote = "remote "; + /* TRANSLATORS: This is "remote " in "remote branch '%s' not found" */ + remote = _("remote "); force = 1; break; case REF_LOCAL_BRANCH: @@ -165,19 +166,19 @@ static int delete_branches(int argc, const char **argv, int force, int kinds) remote = ""; break; default: - die("cannot use -a with -d"); + die(_("cannot use -a with -d")); } if (!force) { head_rev = lookup_commit_reference(head_sha1); if (!head_rev) - die("Couldn't look up commit object for HEAD"); + die(_("Couldn't look up commit object for HEAD")); } for (i = 0; i < argc; i++, strbuf_release(&bname)) { strbuf_branchname(&bname, argv[i]); if (kinds == REF_LOCAL_BRANCH && !strcmp(head, bname.buf)) { - error("Cannot delete the branch '%s' " - "which you are currently on.", bname.buf); + error(_("Cannot delete the branch '%s' " + "which you are currently on."), bname.buf); ret = 1; continue; } @@ -186,7 +187,7 @@ static int delete_branches(int argc, const char **argv, int force, int kinds) name = xstrdup(mkpath(fmt, bname.buf)); if (!resolve_ref(name, sha1, 1, NULL)) { - error("%sbranch '%s' not found.", + error(_("%sbranch '%s' not found."), remote, bname.buf); ret = 1; continue; @@ -194,31 +195,31 @@ static int delete_branches(int argc, const char **argv, int force, int kinds) rev = lookup_commit_reference(sha1); if (!rev) { - error("Couldn't look up commit object for '%s'", name); + error(_("Couldn't look up commit object for '%s'"), name); ret = 1; continue; } if (!force && !branch_merged(kinds, bname.buf, rev, head_rev)) { - error("The branch '%s' is not fully merged.\n" + error(_("The branch '%s' is not fully merged.\n" "If you are sure you want to delete it, " - "run 'git branch -D %s'.", bname.buf, bname.buf); + "run 'git branch -D %s'."), bname.buf, bname.buf); ret = 1; continue; } if (delete_ref(name, sha1, 0)) { - error("Error deleting %sbranch '%s'", remote, + error(_("Error deleting %sbranch '%s'"), remote, bname.buf); ret = 1; } else { struct strbuf buf = STRBUF_INIT; - printf("Deleted %sbranch %s (was %s).\n", remote, + printf(_("Deleted %sbranch %s (was %s).\n"), remote, bname.buf, find_unique_abbrev(sha1, DEFAULT_ABBREV)); strbuf_addf(&buf, "branch.%s", bname.buf); if (git_config_rename_section(buf.buf, NULL) < 0) - warning("Update of config-file failed"); + warning(_("Update of config-file failed")); strbuf_release(&buf); } } @@ -300,7 +301,7 @@ static int append_ref(const char *refname, const unsigned char *sha1, int flags, if (ref_list->verbose || ref_list->with_commit || merge_filter != NO_FILTER) { commit = lookup_commit_reference_gently(sha1, 1); if (!commit) { - cb->ret = error("branch '%s' does not point at a commit", refname); + cb->ret = error(_("branch '%s' does not point at a commit"), refname); return 0; } @@ -313,12 +314,7 @@ static int append_ref(const char *refname, const unsigned char *sha1, int flags, (struct object *)commit, refname); } - /* Resize buffer */ - if (ref_list->index >= ref_list->alloc) { - ref_list->alloc = alloc_nr(ref_list->alloc); - ref_list->list = xrealloc(ref_list->list, - ref_list->alloc * sizeof(struct ref_item)); - } + ALLOC_GROW(ref_list->list, ref_list->index + 1, ref_list->alloc); /* Record the new item */ newitem = &(ref_list->list[ref_list->index++]); @@ -377,11 +373,11 @@ static void fill_tracking_info(struct strbuf *stat, const char *branch_name, strbuf_addf(stat, "%s: ", shorten_unambiguous_ref(branch->merge[0]->dst, 0)); if (!ours) - strbuf_addf(stat, "behind %d] ", theirs); + strbuf_addf(stat, _("behind %d] "), theirs); else if (!theirs) - strbuf_addf(stat, "ahead %d] ", ours); + strbuf_addf(stat, _("ahead %d] "), ours); else - strbuf_addf(stat, "ahead %d, behind %d] ", ours, theirs); + strbuf_addf(stat, _("ahead %d, behind %d] "), ours, theirs); } static int matches_merge_filter(struct commit *commit) @@ -395,6 +391,28 @@ static int matches_merge_filter(struct commit *commit) return (is_merged == (merge_filter == SHOW_MERGED)); } +static void add_verbose_info(struct strbuf *out, struct ref_item *item, + int verbose, int abbrev) +{ + struct strbuf subject = STRBUF_INIT, stat = STRBUF_INIT; + const char *sub = " **** invalid ref ****"; + struct commit *commit = item->commit; + + if (commit && !parse_commit(commit)) { + pp_commit_easy(CMIT_FMT_ONELINE, commit, &subject); + sub = subject.buf; + } + + if (item->kind == REF_LOCAL_BRANCH) + fill_tracking_info(&stat, item->name, verbose > 1); + + strbuf_addf(out, " %s %s%s", + find_unique_abbrev(item->commit->object.sha1, abbrev), + stat.buf, sub); + strbuf_release(&stat); + strbuf_release(&subject); +} + static void print_ref_item(struct ref_item *item, int maxwidth, int verbose, int abbrev, int current, char *prefix) { @@ -435,27 +453,9 @@ static void print_ref_item(struct ref_item *item, int maxwidth, int verbose, if (item->dest) strbuf_addf(&out, " -> %s", item->dest); - else if (verbose) { - struct strbuf subject = STRBUF_INIT, stat = STRBUF_INIT; - const char *sub = " **** invalid ref ****"; - - commit = item->commit; - if (commit && !parse_commit(commit)) { - struct pretty_print_context ctx = {0}; - pretty_print_commit(CMIT_FMT_ONELINE, commit, - &subject, &ctx); - sub = subject.buf; - } - - if (item->kind == REF_LOCAL_BRANCH) - fill_tracking_info(&stat, item->name, verbose > 1); - - strbuf_addf(&out, " %s %s%s", - find_unique_abbrev(item->commit->object.sha1, abbrev), - stat.buf, sub); - strbuf_release(&stat); - strbuf_release(&subject); - } + else if (verbose) + /* " f7c0c00 [ahead 58, behind 197] vcs-svn: drop obj_pool.h" */ + add_verbose_info(&out, item, verbose, abbrev); printf("%s\n", out.buf); strbuf_release(&name); strbuf_release(&out); @@ -480,7 +480,7 @@ static void show_detached(struct ref_list *ref_list) if (head_commit && is_descendant_of(head_commit, ref_list->with_commit)) { struct ref_item item; - item.name = xstrdup("(no branch)"); + item.name = xstrdup(_("(no branch)")); item.len = strlen(item.name); item.kind = REF_LOCAL_BRANCH; item.dest = NULL; @@ -540,7 +540,7 @@ static int print_ref_list(int kinds, int detached, int verbose, int abbrev, stru free_ref_list(&ref_list); if (cb.ret) - error("some refs could not be read"); + error(_("some refs could not be read")); return cb.ret; } @@ -553,7 +553,7 @@ static void rename_branch(const char *oldname, const char *newname, int force) int recovery = 0; if (!oldname) - die("cannot rename the current branch while not on any."); + die(_("cannot rename the current branch while not on any.")); if (strbuf_check_branch_ref(&oldref, oldname)) { /* @@ -563,35 +563,31 @@ static void rename_branch(const char *oldname, const char *newname, int force) if (resolve_ref(oldref.buf, sha1, 1, NULL)) recovery = 1; else - die("Invalid branch name: '%s'", oldname); + die(_("Invalid branch name: '%s'"), oldname); } - if (strbuf_check_branch_ref(&newref, newname)) - die("Invalid branch name: '%s'", newname); - - if (resolve_ref(newref.buf, sha1, 1, NULL) && !force) - die("A branch named '%s' already exists.", newref.buf + 11); + validate_new_branchname(newname, &newref, force); strbuf_addf(&logmsg, "Branch: renamed %s to %s", oldref.buf, newref.buf); if (rename_ref(oldref.buf, newref.buf, logmsg.buf)) - die("Branch rename failed"); + die(_("Branch rename failed")); strbuf_release(&logmsg); if (recovery) - warning("Renamed a misnamed branch '%s' away", oldref.buf + 11); + warning(_("Renamed a misnamed branch '%s' away"), oldref.buf + 11); /* no need to pass logmsg here as HEAD didn't really move */ if (!strcmp(oldname, head) && create_symref("HEAD", newref.buf, NULL)) - die("Branch renamed to %s, but HEAD is not updated!", newname); + die(_("Branch renamed to %s, but HEAD is not updated!"), newname); strbuf_addf(&oldsection, "branch.%s", oldref.buf + 11); strbuf_release(&oldref); strbuf_addf(&newsection, "branch.%s", newref.buf + 11); strbuf_release(&newref); if (git_config_rename_section(oldsection.buf, newsection.buf) < 0) - die("Branch is renamed, but update of config-file failed"); + die(_("Branch is renamed, but update of config-file failed")); strbuf_release(&oldsection); strbuf_release(&newsection); } @@ -606,14 +602,14 @@ static int opt_parse_merge_filter(const struct option *opt, const char *arg, int if (!arg) arg = "HEAD"; if (get_sha1(arg, merge_filter_ref)) - die("malformed object name %s", arg); + die(_("malformed object name %s"), arg); return 0; } int cmd_branch(int argc, const char **argv, const char *prefix) { int delete = 0, rename = 0, force_create = 0; - int verbose = 0, abbrev = DEFAULT_ABBREV, detached = 0; + int verbose = 0, abbrev = -1, detached = 0; int reflog = 0; enum branch_track track; int kinds = REF_LOCAL_BRANCH; @@ -621,7 +617,8 @@ int cmd_branch(int argc, const char **argv, const char *prefix) struct option options[] = { OPT_GROUP("Generic options"), - OPT__VERBOSE(&verbose), + OPT__VERBOSE(&verbose, + "show hash and subject, give twice for upstream branch"), OPT_SET_INT('t', "track", &track, "set up tracking mode (see git-pull(1))", BRANCH_TRACK_EXPLICIT), OPT_SET_INT( 0, "set-upstream", &track, "change upstream info", @@ -651,7 +648,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix) OPT_BIT('m', NULL, &rename, "move/rename a branch and its reflog", 1), OPT_BIT('M', NULL, &rename, "move/rename a branch, even if target exists", 2), OPT_BOOLEAN('l', NULL, &reflog, "create the branch's reflog"), - OPT_BOOLEAN('f', "force", &force_create, "force creation (when already exists)"), + OPT__FORCE(&force_create, "force creation (when already exists)"), { OPTION_CALLBACK, 0, "no-merged", &merge_filter_ref, "commit", "print only not merged branches", @@ -667,22 +664,22 @@ int cmd_branch(int argc, const char **argv, const char *prefix) OPT_END(), }; - git_config(git_branch_config, NULL); + if (argc == 2 && !strcmp(argv[1], "-h")) + usage_with_options(builtin_branch_usage, options); - if (branch_use_color == -1) - branch_use_color = git_use_color_default; + git_config(git_branch_config, NULL); track = git_branch_track; head = resolve_ref("HEAD", head_sha1, 0, NULL); if (!head) - die("Failed to resolve HEAD as a valid ref."); + die(_("Failed to resolve HEAD as a valid ref.")); head = xstrdup(head); if (!strcmp(head, "HEAD")) { detached = 1; } else { if (prefixcmp(head, "refs/heads/")) - die("HEAD not found below refs/heads!"); + die(_("HEAD not found below refs/heads!")); head += 11; } hashcpy(merge_filter_ref, head_sha1); @@ -692,6 +689,9 @@ int cmd_branch(int argc, const char **argv, const char *prefix) if (!!delete + !!rename + !!force_create > 1) usage_with_options(builtin_branch_usage, options); + if (abbrev == -1) + abbrev = DEFAULT_ABBREV; + if (delete) return delete_branches(argc, argv, delete > 1, kinds); else if (argc == 0) @@ -702,7 +702,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix) rename_branch(argv[0], argv[1], rename > 1); else if (argc <= 2) { if (kinds != REF_LOCAL_BRANCH) - die("-a and -r options to 'git branch' do not make sense with a branch name"); + die(_("-a and -r options to 'git branch' do not make sense with a branch name")); create_branch(head, argv[0], (argc == 2) ? argv[1] : head, force_create, reflog, track); } else diff --git a/builtin/bundle.c b/builtin/bundle.c index 2006cc5cd5..81046a9cb8 100644 --- a/builtin/bundle.c +++ b/builtin/bundle.c @@ -12,13 +12,12 @@ static const char builtin_bundle_usage[] = "git bundle create <file> <git-rev-list args>\n" " or: git bundle verify <file>\n" - " or: git bundle list-heads <file> [refname...]\n" - " or: git bundle unbundle <file> [refname...]"; + " or: git bundle list-heads <file> [<refname>...]\n" + " or: git bundle unbundle <file> [<refname>...]"; int cmd_bundle(int argc, const char **argv, const char *prefix) { struct bundle_header header; - int nongit; const char *cmd, *bundle_file; int bundle_fd = -1; char buffer[PATH_MAX]; @@ -31,7 +30,6 @@ int cmd_bundle(int argc, const char **argv, const char *prefix) argc -= 2; argv += 2; - prefix = setup_git_directory_gently(&nongit); if (prefix && bundle_file[0] != '/') { snprintf(buffer, sizeof(buffer), "%s/%s", prefix, bundle_file); bundle_file = buffer; @@ -46,7 +44,7 @@ int cmd_bundle(int argc, const char **argv, const char *prefix) close(bundle_fd); if (verify_bundle(&header, 1)) return 1; - fprintf(stderr, "%s is okay\n", bundle_file); + fprintf(stderr, _("%s is okay\n"), bundle_file); return 0; } if (!strcmp(cmd, "list-heads")) { @@ -54,12 +52,12 @@ int cmd_bundle(int argc, const char **argv, const char *prefix) return !!list_bundle_refs(&header, argc, argv); } if (!strcmp(cmd, "create")) { - if (nongit) - die("Need a repository to create a bundle."); + if (!startup_info->have_repository) + die(_("Need a repository to create a bundle.")); return !!create_bundle(&header, bundle_file, argc, argv); } else if (!strcmp(cmd, "unbundle")) { - if (nongit) - die("Need a repository to unbundle."); + if (!startup_info->have_repository) + die(_("Need a repository to unbundle.")); return !!unbundle(&header, bundle_fd) || list_bundle_refs(&header, argc, argv); } else diff --git a/builtin/cat-file.c b/builtin/cat-file.c index 76ec3fec92..07bd984084 100644 --- a/builtin/cat-file.c +++ b/builtin/cat-file.c @@ -143,7 +143,7 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name) die("git cat-file --textconv %s: <object> must be <sha1:path>", obj_name); - if (!textconv_object(obj_context.path, sha1, &buf, &size)) + if (!textconv_object(obj_context.path, obj_context.mode, sha1, &buf, &size)) die("git cat-file --textconv: unable to run textconv on %s", obj_name); break; @@ -187,6 +187,8 @@ static int batch_one_object(const char *obj_name, int print_contents) if (type <= 0) { printf("%s missing\n", obj_name); fflush(stdout); + if (print_contents == BATCH) + free(contents); return 0; } diff --git a/builtin/check-attr.c b/builtin/check-attr.c index 3016d29caa..708988a0e1 100644 --- a/builtin/check-attr.c +++ b/builtin/check-attr.c @@ -4,28 +4,28 @@ #include "quote.h" #include "parse-options.h" +static int all_attrs; static int stdin_paths; static const char * const check_attr_usage[] = { -"git check-attr attr... [--] pathname...", -"git check-attr --stdin attr... < <list-of-paths>", +"git check-attr [-a | --all | attr...] [--] pathname...", +"git check-attr --stdin [-a | --all | attr...] < <list-of-paths>", NULL }; static int null_term_line; static const struct option check_attr_options[] = { + OPT_BOOLEAN('a', "all", &all_attrs, "report all attributes set on file"), OPT_BOOLEAN(0 , "stdin", &stdin_paths, "read file names from stdin"), OPT_BOOLEAN('z', NULL, &null_term_line, "input paths are terminated by a null character"), OPT_END() }; -static void check_attr(int cnt, struct git_attr_check *check, - const char** name, const char *file) +static void output_attr(int cnt, struct git_attr_check *check, + const char *file) { int j; - if (git_checkattr(file, cnt, check)) - die("git_checkattr died"); for (j = 0; j < cnt; j++) { const char *value = check[j].value; @@ -37,12 +37,30 @@ static void check_attr(int cnt, struct git_attr_check *check, value = "unspecified"; quote_c_style(file, NULL, stdout, 0); - printf(": %s: %s\n", name[j], value); + printf(": %s: %s\n", git_attr_name(check[j].attr), value); } } -static void check_attr_stdin_paths(int cnt, struct git_attr_check *check, - const char** name) +static void check_attr(const char *prefix, int cnt, + struct git_attr_check *check, const char *file) +{ + char *full_path = + prefix_path(prefix, prefix ? strlen(prefix) : 0, file); + if (check != NULL) { + if (git_check_attr(full_path, cnt, check)) + die("git_check_attr died"); + output_attr(cnt, check, file); + } else { + if (git_all_attrs(full_path, &cnt, &check)) + die("git_all_attrs died"); + output_attr(cnt, check, file); + free(check); + } + free(full_path); +} + +static void check_attr_stdin_paths(const char *prefix, int cnt, + struct git_attr_check *check) { struct strbuf buf, nbuf; int line_termination = null_term_line ? 0 : '\n'; @@ -56,23 +74,26 @@ static void check_attr_stdin_paths(int cnt, struct git_attr_check *check, die("line is badly quoted"); strbuf_swap(&buf, &nbuf); } - check_attr(cnt, check, name, buf.buf); + check_attr(prefix, cnt, check, buf.buf); maybe_flush_or_die(stdout, "attribute to stdout"); } strbuf_release(&buf); strbuf_release(&nbuf); } +static NORETURN void error_with_usage(const char *msg) +{ + error("%s", msg); + usage_with_options(check_attr_usage, check_attr_options); +} + int cmd_check_attr(int argc, const char **argv, const char *prefix) { struct git_attr_check *check; - int cnt, i, doubledash; - const char *errstr = NULL; + int cnt, i, doubledash, filei; argc = parse_options(argc, argv, prefix, check_attr_options, check_attr_usage, PARSE_OPT_KEEP_DASHDASH); - if (!argc) - usage_with_options(check_attr_usage, check_attr_options); if (read_cache() < 0) { die("invalid cache"); @@ -84,39 +105,63 @@ int cmd_check_attr(int argc, const char **argv, const char *prefix) doubledash = i; } - /* If there is no double dash, we handle only one attribute */ - if (doubledash < 0) { - cnt = 1; - doubledash = 0; - } else + /* Process --all and/or attribute arguments: */ + if (all_attrs) { + if (doubledash >= 1) + error_with_usage("Attributes and --all both specified"); + + cnt = 0; + filei = doubledash + 1; + } else if (doubledash == 0) { + error_with_usage("No attribute specified"); + } else if (doubledash < 0) { + if (!argc) + error_with_usage("No attribute specified"); + + if (stdin_paths) { + /* Treat all arguments as attribute names. */ + cnt = argc; + filei = argc; + } else { + /* Treat exactly one argument as an attribute name. */ + cnt = 1; + filei = 1; + } + } else { cnt = doubledash; - doubledash++; - - if (cnt <= 0) - errstr = "No attribute specified"; - else if (stdin_paths && doubledash < argc) - errstr = "Can't specify files with --stdin"; - if (errstr) { - error("%s", errstr); - usage_with_options(check_attr_usage, check_attr_options); + filei = doubledash + 1; } - check = xcalloc(cnt, sizeof(*check)); - for (i = 0; i < cnt; i++) { - const char *name; - struct git_attr *a; - name = argv[i]; - a = git_attr(name); - if (!a) - return error("%s: not a valid attribute name", name); - check[i].attr = a; + /* Check file argument(s): */ + if (stdin_paths) { + if (filei < argc) + error_with_usage("Can't specify files with --stdin"); + } else { + if (filei >= argc) + error_with_usage("No file specified"); + } + + if (all_attrs) { + check = NULL; + } else { + check = xcalloc(cnt, sizeof(*check)); + for (i = 0; i < cnt; i++) { + const char *name; + struct git_attr *a; + name = argv[i]; + a = git_attr(name); + if (!a) + return error("%s: not a valid attribute name", + name); + check[i].attr = a; + } } if (stdin_paths) - check_attr_stdin_paths(cnt, check, argv); + check_attr_stdin_paths(prefix, cnt, check); else { - for (i = doubledash; i < argc; i++) - check_attr(cnt, check, argv, argv[i]); + for (i = filei; i < argc; i++) + check_attr(prefix, cnt, check, argv[i]); maybe_flush_or_die(stdout, "attribute to stdout"); } return 0; diff --git a/builtin/check-ref-format.c b/builtin/check-ref-format.c index b106c65d80..ae3f28115a 100644 --- a/builtin/check-ref-format.c +++ b/builtin/check-ref-format.c @@ -33,28 +33,38 @@ static void collapse_slashes(char *dst, const char *src) *dst = '\0'; } +static int check_ref_format_branch(const char *arg) +{ + struct strbuf sb = STRBUF_INIT; + int nongit; + + setup_git_directory_gently(&nongit); + if (strbuf_check_branch_ref(&sb, arg)) + die("'%s' is not a valid branch name", arg); + printf("%s\n", sb.buf + 11); + return 0; +} + +static int check_ref_format_print(const char *arg) +{ + char *refname = xmalloc(strlen(arg) + 1); + + if (check_ref_format(arg)) + return 1; + collapse_slashes(refname, arg); + printf("%s\n", refname); + return 0; +} + int cmd_check_ref_format(int argc, const char **argv, const char *prefix) { if (argc == 2 && !strcmp(argv[1], "-h")) usage(builtin_check_ref_format_usage); - if (argc == 3 && !strcmp(argv[1], "--branch")) { - struct strbuf sb = STRBUF_INIT; - - if (strbuf_check_branch_ref(&sb, argv[2])) - die("'%s' is not a valid branch name", argv[2]); - printf("%s\n", sb.buf + 11); - exit(0); - } - if (argc == 3 && !strcmp(argv[1], "--print")) { - char *refname = xmalloc(strlen(argv[2]) + 1); - - if (check_ref_format(argv[2])) - exit(1); - collapse_slashes(refname, argv[2]); - printf("%s\n", refname); - exit(0); - } + if (argc == 3 && !strcmp(argv[1], "--branch")) + return check_ref_format_branch(argv[2]); + if (argc == 3 && !strcmp(argv[1], "--print")) + return check_ref_format_print(argv[2]); if (argc != 2) usage(builtin_check_ref_format_usage); return !!check_ref_format(argv[1]); diff --git a/builtin/checkout-index.c b/builtin/checkout-index.c index a7a5ee10f3..c16d82b7de 100644 --- a/builtin/checkout-index.c +++ b/builtin/checkout-index.c @@ -3,38 +3,6 @@ * * Copyright (C) 2005 Linus Torvalds * - * Careful: order of argument flags does matter. For example, - * - * git checkout-index -a -f file.c - * - * Will first check out all files listed in the cache (but not - * overwrite any old ones), and then force-checkout "file.c" a - * second time (ie that one _will_ overwrite any old contents - * with the same filename). - * - * Also, just doing "git checkout-index" does nothing. You probably - * meant "git checkout-index -a". And if you want to force it, you - * want "git checkout-index -f -a". - * - * Intuitiveness is not the goal here. Repeatability is. The - * reason for the "no arguments means no work" thing is that - * from scripts you are supposed to be able to do things like - * - * find . -name '*.h' -print0 | xargs -0 git checkout-index -f -- - * - * or: - * - * find . -name '*.h' -print0 | git checkout-index -f -z --stdin - * - * which will force all existing *.h files to be replaced with - * their cached copies. If an empty command line implied "all", - * then this would force-refresh everything in the cache, which - * was not the point. - * - * Oh, and the "--" is just a good idea when you know the rest - * will be filenames. Just so that you wouldn't have a filename - * of "-a" causing problems (not possible in the above example, - * but get used to it in scripting!). */ #include "builtin.h" #include "cache.h" @@ -155,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>...", + "git checkout-index [options] [--] [<file>...]", NULL }; @@ -217,9 +185,9 @@ int cmd_checkout_index(int argc, const char **argv, const char *prefix) struct option builtin_checkout_index_options[] = { OPT_BOOLEAN('a', "all", &all, "checks out all files in the index"), - OPT_BOOLEAN('f', "force", &force, - "forces overwrite of existing files"), - OPT__QUIET(&quiet), + OPT__FORCE(&force, "forces overwrite of existing files"), + OPT__QUIET(&quiet, + "no warning for existing files and files not in index"), OPT_BOOLEAN('n', "no-create", ¬_new, "don't checkout new files"), { OPTION_CALLBACK, 'u', "index", &newfd, NULL, @@ -241,6 +209,9 @@ int cmd_checkout_index(int argc, const char **argv, const char *prefix) OPT_END() }; + if (argc == 2 && !strcmp(argv[1], "-h")) + usage_with_options(builtin_checkout_index_usage, + builtin_checkout_index_options); git_config(git_default_config, NULL); state.base_dir = ""; prefix_length = prefix ? strlen(prefix) : 0; diff --git a/builtin/checkout.c b/builtin/checkout.c index 1994be92c6..3bb652591b 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -18,6 +18,7 @@ #include "xdiff-interface.h" #include "ll-merge.h" #include "resolve-undo.h" +#include "submodule.h" static const char * const checkout_usage[] = { "git checkout [options] <branch>", @@ -29,13 +30,19 @@ struct checkout_opts { int quiet; int merge; int force; + int force_detach; int writeout_stage; int writeout_error; + /* not set by parse_options */ + int branch_exists; + const char *new_branch; + const char *new_branch_force; const char *new_orphan_branch; int new_branch_log; enum branch_track track; + struct diff_options diff_options; }; static int post_checkout_hook(struct commit *old, struct commit *new, @@ -72,7 +79,10 @@ static int update_some(const unsigned char *sha1, const char *base, int baselen, static int read_tree_some(struct tree *tree, const char **pathspec) { - read_tree_recursive(tree, "", 0, 0, pathspec, update_some, NULL); + struct pathspec ps; + init_pathspec(&ps, pathspec); + read_tree_recursive(tree, "", 0, 0, &ps, update_some, NULL); + free_pathspec(&ps); /* update the index with the given tree's info * for all args, expanding wildcards, and exit @@ -97,9 +107,10 @@ static int check_stage(int stage, struct cache_entry *ce, int pos) return 0; pos++; } - return error("path '%s' does not have %s version", - ce->name, - (stage == 2) ? "our" : "their"); + if (stage == 2) + return error(_("path '%s' does not have our version"), ce->name); + else + return error(_("path '%s' does not have their version"), ce->name); } static int check_all_stages(struct cache_entry *ce, int pos) @@ -110,7 +121,7 @@ static int check_all_stages(struct cache_entry *ce, int pos) ce_stage(active_cache[pos+1]) != 2 || strcmp(active_cache[pos+2]->name, ce->name) || ce_stage(active_cache[pos+2]) != 3) - return error("path '%s' does not have all three versions", + return error(_("path '%s' does not have all three versions"), ce->name); return 0; } @@ -124,9 +135,10 @@ static int checkout_stage(int stage, struct cache_entry *ce, int pos, return checkout_entry(active_cache[pos], state, NULL); pos++; } - return error("path '%s' does not have %s version", - ce->name, - (stage == 2) ? "our" : "their"); + if (stage == 2) + return error(_("path '%s' does not have our version"), ce->name); + else + return error(_("path '%s' does not have their version"), ce->name); } static int checkout_merged(int pos, struct checkout *state) @@ -144,20 +156,24 @@ static int checkout_merged(int pos, struct checkout *state) ce_stage(active_cache[pos+1]) != 2 || strcmp(active_cache[pos+2]->name, path) || ce_stage(active_cache[pos+2]) != 3) - return error("path '%s' does not have all 3 versions", path); + return error(_("path '%s' does not have all 3 versions"), path); read_mmblob(&ancestor, active_cache[pos]->sha1); read_mmblob(&ours, active_cache[pos+1]->sha1); read_mmblob(&theirs, active_cache[pos+2]->sha1); + /* + * NEEDSWORK: re-create conflicts from merges with + * merge.renormalize set, too + */ status = ll_merge(&result_buf, path, &ancestor, "base", - &ours, "ours", &theirs, "theirs", 0); + &ours, "ours", &theirs, "theirs", NULL); free(ancestor.ptr); free(ours.ptr); free(theirs.ptr); if (status < 0 || !result_buf.ptr) { free(result_buf.ptr); - return error("path '%s': cannot merge", path); + return error(_("path '%s': cannot merge"), path); } /* @@ -174,18 +190,18 @@ static int checkout_merged(int pos, struct checkout *state) */ if (write_sha1_file(result_buf.ptr, result_buf.size, blob_type, sha1)) - die("Unable to add merge result for '%s'", path); + die(_("Unable to add merge result for '%s'"), path); ce = make_cache_entry(create_ce_mode(active_cache[pos+1]->ce_mode), sha1, path, 2, 0); if (!ce) - die("make_cache_entry failed for path '%s'", path); + die(_("make_cache_entry failed for path '%s'"), path); status = checkout_entry(ce, state, NULL); return status; } static int checkout_paths(struct tree *source_tree, const char **pathspec, - struct checkout_opts *opts) + const char *prefix, struct checkout_opts *opts) { int pos; struct checkout state; @@ -201,7 +217,7 @@ static int checkout_paths(struct tree *source_tree, const char **pathspec, newfd = hold_locked_index(lock_file, 1); if (read_cache_preload(pathspec) < 0) - return error("corrupt index file"); + return error(_("corrupt index file")); if (source_tree) read_tree_some(source_tree, pathspec); @@ -215,7 +231,7 @@ static int checkout_paths(struct tree *source_tree, const char **pathspec, match_pathspec(pathspec, ce->name, ce_namelen(ce), 0, ps_matched); } - if (report_path_error(ps_matched, pathspec, 0)) + if (report_path_error(ps_matched, pathspec, prefix)) return 1; /* "checkout -m path" to recreate conflicted state */ @@ -229,14 +245,14 @@ static int checkout_paths(struct tree *source_tree, const char **pathspec, if (!ce_stage(ce)) continue; if (opts->force) { - warning("path '%s' is unmerged", ce->name); + warning(_("path '%s' is unmerged"), ce->name); } else if (stage) { errs |= check_stage(stage, ce, pos); } else if (opts->merge) { errs |= check_all_stages(ce, pos); } else { errs = 1; - error("path '%s' is unmerged", ce->name); + error(_("path '%s' is unmerged"), ce->name); } pos = skip_same_name(ce, pos) - 1; } @@ -265,7 +281,7 @@ static int checkout_paths(struct tree *source_tree, const char **pathspec, if (write_cache(newfd, active_cache, active_nr) || commit_locked_index(lock_file)) - die("unable to write new index file"); + die(_("unable to write new index file")); resolve_ref("HEAD", rev, 0, &flag); head = lookup_commit_reference_gently(rev, 1); @@ -274,25 +290,24 @@ static int checkout_paths(struct tree *source_tree, const char **pathspec, return errs; } -static void show_local_changes(struct object *head) +static void show_local_changes(struct object *head, struct diff_options *opts) { struct rev_info rev; /* I think we want full paths, even if we're in a subdirectory. */ init_revisions(&rev, NULL); - rev.abbrev = 0; + rev.diffopt.flags = opts->flags; rev.diffopt.output_format |= DIFF_FORMAT_NAME_STATUS; if (diff_setup_done(&rev.diffopt) < 0) - die("diff_setup_done failed"); + die(_("diff_setup_done failed")); add_pending_object(&rev, head, NULL); run_diff_index(&rev, 0); } -static void describe_detached_head(char *msg, struct commit *commit) +static void describe_detached_head(const char *msg, struct commit *commit) { struct strbuf sb = STRBUF_INIT; - struct pretty_print_context ctx = {0}; parse_commit(commit); - pretty_print_commit(CMIT_FMT_ONELINE, commit, &sb, &ctx); + pp_commit_easy(CMIT_FMT_ONELINE, commit, &sb); fprintf(stderr, "%s %s... %s\n", msg, find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV), sb.buf); strbuf_release(&sb); @@ -356,7 +371,7 @@ static int merge_working_tree(struct checkout_opts *opts, int newfd = hold_locked_index(lock_file, 1); if (read_cache_preload(NULL) < 0) - return error("corrupt index file"); + return error(_("corrupt index file")); resolve_undo_clear(); if (opts->force) { @@ -373,12 +388,12 @@ static int merge_working_tree(struct checkout_opts *opts, topts.src_index = &the_index; topts.dst_index = &the_index; - topts.msgs.not_uptodate_file = "You have local changes to '%s'; cannot switch branches."; + setup_unpack_trees_porcelain(&topts, "checkout"); refresh_cache(REFRESH_QUIET); if (unmerged_cache()) { - error("you need to resolve your current index first"); + error(_("you need to resolve your current index first")); return 1; } @@ -394,7 +409,7 @@ static int merge_working_tree(struct checkout_opts *opts, topts.dir->exclude_per_dir = ".gitignore"; tree = parse_tree_indirect(old->commit ? old->commit->object.sha1 : - (unsigned char *)EMPTY_TREE_SHA1_BIN); + EMPTY_TREE_SHA1_BIN); init_tree_desc(&trees[0], tree->buffer, tree->size); tree = parse_tree_indirect(new->commit->object.sha1); init_tree_desc(&trees[1], tree->buffer, tree->size); @@ -433,6 +448,13 @@ static int merge_working_tree(struct checkout_opts *opts, */ add_files_to_cache(NULL, NULL, 0); + /* + * NEEDSWORK: carrying over local changes + * when branches have different end-of-line + * normalization (or clean+smudge rules) is + * a pain; plumb in an option to set + * o.renormalize? + */ init_merge_options(&o); o.verbosity = 0; work = write_tree_from_memory(&o); @@ -453,10 +475,10 @@ static int merge_working_tree(struct checkout_opts *opts, if (write_cache(newfd, active_cache, active_nr) || commit_locked_index(lock_file)) - die("unable to write new index file"); + die(_("unable to write new index file")); if (!opts->force && !opts->quiet) - show_local_changes(&new->commit->object); + show_local_changes(&new->commit->object, &opts->diff_options); return 0; } @@ -502,7 +524,7 @@ static void update_refs_for_switch(struct checkout_opts *opts, temp = log_all_ref_updates; log_all_ref_updates = 1; if (log_ref_setup(ref_name, log_file, sizeof(log_file))) { - fprintf(stderr, "Can not do reflog for '%s'\n", + fprintf(stderr, _("Can not do reflog for '%s'\n"), opts->new_orphan_branch); log_all_ref_updates = temp; return; @@ -511,7 +533,8 @@ static void update_refs_for_switch(struct checkout_opts *opts, } } else - create_branch(old->name, opts->new_branch, new->name, 0, + create_branch(old->name, opts->new_branch, new->name, + opts->new_branch_force ? 1 : 0, opts->new_branch_log, opts->track); new->name = opts->new_branch; setup_branch_path(new); @@ -523,16 +546,31 @@ static void update_refs_for_switch(struct checkout_opts *opts, strbuf_addf(&msg, "checkout: moving from %s to %s", old_desc ? old_desc : "(invalid)", new->name); - if (new->path) { + if (!strcmp(new->name, "HEAD") && !new->path && !opts->force_detach) { + /* Nothing to do. */ + } else if (opts->force_detach || !new->path) { /* No longer on any branch. */ + update_ref(msg.buf, "HEAD", new->commit->object.sha1, NULL, + REF_NODEREF, DIE_ON_ERR); + if (!opts->quiet) { + if (old->path && advice_detached_head) + detach_advice(old->path, new->name); + describe_detached_head(_("HEAD is now at"), new->commit); + } + } else if (new->path) { /* Switch branches. */ create_symref("HEAD", new->path, msg.buf); if (!opts->quiet) { - if (old->path && !strcmp(new->path, old->path)) - fprintf(stderr, "Already on '%s'\n", + if (old->path && !strcmp(new->path, old->path)) { + fprintf(stderr, _("Already on '%s'\n"), new->name); - else - fprintf(stderr, "Switched to%s branch '%s'\n", - opts->new_branch ? " a new" : "", + } else if (opts->new_branch) { + if (opts->branch_exists) + fprintf(stderr, _("Switched to and reset branch '%s'\n"), new->name); + else + fprintf(stderr, _("Switched to a new branch '%s'\n"), new->name); + } else { + fprintf(stderr, _("Switched to branch '%s'\n"), new->name); + } } if (old->path && old->name) { char log_file[PATH_MAX], ref_file[PATH_MAX]; @@ -542,21 +580,135 @@ static void update_refs_for_switch(struct checkout_opts *opts, if (!file_exists(ref_file) && file_exists(log_file)) remove_path(log_file); } - } else if (strcmp(new->name, "HEAD")) { - update_ref(msg.buf, "HEAD", new->commit->object.sha1, NULL, - REF_NODEREF, DIE_ON_ERR); - if (!opts->quiet) { - if (old->path && advice_detached_head) - detach_advice(old->path, new->name); - describe_detached_head("HEAD is now at", new->commit); - } } remove_branch_state(); strbuf_release(&msg); - if (!opts->quiet && (new->path || !strcmp(new->name, "HEAD"))) + if (!opts->quiet && + (new->path || (!opts->force_detach && !strcmp(new->name, "HEAD")))) report_tracking(new); } +struct rev_list_args { + int argc; + int alloc; + const char **argv; +}; + +static void add_one_rev_list_arg(struct rev_list_args *args, const char *s) +{ + ALLOC_GROW(args->argv, args->argc + 1, args->alloc); + args->argv[args->argc++] = s; +} + +static int add_one_ref_to_rev_list_arg(const char *refname, + const unsigned char *sha1, + int flags, + void *cb_data) +{ + add_one_rev_list_arg(cb_data, refname); + return 0; +} + +static int clear_commit_marks_from_one_ref(const char *refname, + const unsigned char *sha1, + int flags, + void *cb_data) +{ + struct commit *commit = lookup_commit_reference_gently(sha1, 1); + if (commit) + clear_commit_marks(commit, -1); + return 0; +} + +static void describe_one_orphan(struct strbuf *sb, struct commit *commit) +{ + parse_commit(commit); + strbuf_addstr(sb, " "); + strbuf_addstr(sb, + find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV)); + strbuf_addch(sb, ' '); + pp_commit_easy(CMIT_FMT_ONELINE, commit, sb); + strbuf_addch(sb, '\n'); +} + +#define ORPHAN_CUTOFF 4 +static void suggest_reattach(struct commit *commit, struct rev_info *revs) +{ + struct commit *c, *last = NULL; + struct strbuf sb = STRBUF_INIT; + int lost = 0; + while ((c = get_revision(revs)) != NULL) { + if (lost < ORPHAN_CUTOFF) + describe_one_orphan(&sb, c); + last = c; + lost++; + } + if (ORPHAN_CUTOFF < lost) { + int more = lost - ORPHAN_CUTOFF; + if (more == 1) + describe_one_orphan(&sb, last); + else + strbuf_addf(&sb, _(" ... and %d more.\n"), more); + } + + fprintf(stderr, + Q_( + /* The singular version */ + "Warning: you are leaving %d commit behind, " + "not connected to\n" + "any of your branches:\n\n" + "%s\n", + /* The plural version */ + "Warning: you are leaving %d commits behind, " + "not connected to\n" + "any of your branches:\n\n" + "%s\n", + /* Give ngettext() the count */ + lost), + lost, + sb.buf); + strbuf_release(&sb); + + if (advice_detached_head) + fprintf(stderr, + _( + "If you want to keep them by creating a new branch, " + "this may be a good time\nto do so with:\n\n" + " git branch new_branch_name %s\n\n"), + sha1_to_hex(commit->object.sha1)); +} + +/* + * We are about to leave commit that was at the tip of a detached + * HEAD. If it is not reachable from any ref, this is the last chance + * for the user to do so without resorting to reflog. + */ +static void orphaned_commit_warning(struct commit *commit) +{ + struct rev_list_args args = { 0, 0, NULL }; + struct rev_info revs; + + add_one_rev_list_arg(&args, "(internal)"); + add_one_rev_list_arg(&args, sha1_to_hex(commit->object.sha1)); + add_one_rev_list_arg(&args, "--not"); + for_each_ref(add_one_ref_to_rev_list_arg, &args); + add_one_rev_list_arg(&args, "--"); + add_one_rev_list_arg(&args, NULL); + + init_revisions(&revs, NULL); + if (setup_revisions(args.argc - 1, args.argv, &revs, NULL) != 1) + die(_("internal error: only -- alone should have been left")); + if (prepare_revision_walk(&revs)) + die(_("internal error in revision walk")); + if (!(commit->object.flags & UNINTERESTING)) + suggest_reattach(commit, &revs); + else + describe_detached_head(_("Previous HEAD position was"), commit); + + clear_commit_marks(commit, -1); + for_each_ref(clear_commit_marks_from_one_ref, NULL); +} + static int switch_branches(struct checkout_opts *opts, struct branch_info *new) { int ret = 0; @@ -564,10 +716,12 @@ static int switch_branches(struct checkout_opts *opts, struct branch_info *new) unsigned char rev[20]; int flag; memset(&old, 0, sizeof(old)); - old.path = resolve_ref("HEAD", rev, 0, &flag); + old.path = xstrdup(resolve_ref("HEAD", rev, 0, &flag)); old.commit = lookup_commit_reference_gently(rev, 1); - if (!(flag & REF_ISSYMREF)) + if (!(flag & REF_ISSYMREF)) { + free((char *)old.path); old.path = NULL; + } if (old.path && !prefixcmp(old.path, "refs/heads/")) old.name = old.path + strlen("refs/heads/"); @@ -576,7 +730,7 @@ static int switch_branches(struct checkout_opts *opts, struct branch_info *new) new->name = "HEAD"; new->commit = old.commit; if (!new->commit) - die("You are on a branch yet to be born"); + die(_("You are on a branch yet to be born")); parse_commit(new->commit); } @@ -584,23 +738,28 @@ static int switch_branches(struct checkout_opts *opts, struct branch_info *new) if (ret) return ret; - /* - * If we were on a detached HEAD, but have now moved to - * a new commit, we want to mention the old commit once more - * to remind the user that it might be lost. - */ if (!opts->quiet && !old.path && old.commit && new->commit != old.commit) - describe_detached_head("Previous HEAD position was", old.commit); + orphaned_commit_warning(old.commit); update_refs_for_switch(opts, &old, new); ret = post_checkout_hook(old.commit, new->commit, 1); + free((char *)old.path); return ret || opts->writeout_error; } static int git_checkout_config(const char *var, const char *value, void *cb) { - return git_xmerge_config(var, value, cb); + if (!strcmp(var, "diff.ignoresubmodules")) { + struct checkout_opts *opts = cb; + handle_ignore_submodules_arg(&opts->diff_options, value); + return 0; + } + + if (!prefixcmp(var, "submodule.")) + return parse_submodule_config_option(var, value); + + return git_xmerge_config(var, value, NULL); } static int interactive_checkout(const char *revision, const char **pathspec, @@ -645,29 +804,145 @@ static const char *unique_tracking_name(const char *name) return NULL; } +static int parse_branchname_arg(int argc, const char **argv, + int dwim_new_local_branch_ok, + struct branch_info *new, + struct tree **source_tree, + unsigned char rev[20], + const char **new_branch) +{ + int argcount = 0; + unsigned char branch_rev[20]; + const char *arg; + int has_dash_dash; + + /* + * case 1: git checkout <ref> -- [<paths>] + * + * <ref> must be a valid tree, everything after the '--' must be + * a path. + * + * case 2: git checkout -- [<paths>] + * + * everything after the '--' must be paths. + * + * case 3: git checkout <something> [<paths>] + * + * With no paths, if <something> is a commit, that is to + * switch to the branch or detach HEAD at it. As a special case, + * if <something> is A...B (missing A or B means HEAD but you can + * omit at most one side), and if there is a unique merge base + * between A and B, A...B names that merge base. + * + * With no paths, if <something> is _not_ a commit, no -t nor -b + * was given, and there is a tracking branch whose name is + * <something> in one and only one remote, then this is a short-hand + * to fork local <something> from that remote-tracking branch. + * + * Otherwise <something> shall not be ambiguous. + * - If it's *only* a reference, treat it like case (1). + * - If it's only a path, treat it like case (2). + * - else: fail. + * + */ + if (!argc) + return 0; + + if (!strcmp(argv[0], "--")) /* case (2) */ + return 1; + + arg = argv[0]; + has_dash_dash = (argc > 1) && !strcmp(argv[1], "--"); + + if (!strcmp(arg, "-")) + arg = "@{-1}"; + + if (get_sha1_mb(arg, rev)) { + if (has_dash_dash) /* case (1) */ + die(_("invalid reference: %s"), arg); + if (dwim_new_local_branch_ok && + !check_filename(NULL, arg) && + argc == 1) { + const char *remote = unique_tracking_name(arg); + if (!remote || get_sha1(remote, rev)) + return argcount; + *new_branch = arg; + arg = remote; + /* DWIMmed to create local branch */ + } else { + return argcount; + } + } + + /* we can't end up being in (2) anymore, eat the argument */ + argcount++; + argv++; + argc--; + + new->name = arg; + setup_branch_path(new); + + if (check_ref_format(new->path) == CHECK_REF_FORMAT_OK && + resolve_ref(new->path, branch_rev, 1, NULL)) + hashcpy(rev, branch_rev); + else + new->path = NULL; /* not an existing branch */ + + new->commit = lookup_commit_reference_gently(rev, 1); + if (!new->commit) { + /* not a commit */ + *source_tree = parse_tree_indirect(rev); + } else { + parse_commit(new->commit); + *source_tree = new->commit->tree; + } + + if (!*source_tree) /* case (1): want a tree */ + die(_("reference is not a tree: %s"), arg); + if (!has_dash_dash) {/* case (3 -> 1) */ + /* + * Do not complain the most common case + * git checkout branch + * even if there happen to be a file called 'branch'; + * it would be extremely annoying. + */ + if (argc) + verify_non_filename(NULL, arg); + } else { + argcount++; + argv++; + argc--; + } + + return argcount; +} + int cmd_checkout(int argc, const char **argv, const char *prefix) { struct checkout_opts opts; unsigned char rev[20]; - const char *arg; struct branch_info new; struct tree *source_tree = NULL; char *conflict_style = NULL; int patch_mode = 0; int dwim_new_local_branch = 1; struct option options[] = { - OPT__QUIET(&opts.quiet), - OPT_STRING('b', NULL, &opts.new_branch, "new branch", "branch"), - OPT_BOOLEAN('l', NULL, &opts.new_branch_log, "log for new branch"), - OPT_SET_INT('t', "track", &opts.track, "track", + OPT__QUIET(&opts.quiet, "suppress progress reporting"), + OPT_STRING('b', NULL, &opts.new_branch, "branch", + "create and checkout a new branch"), + OPT_STRING('B', NULL, &opts.new_branch_force, "branch", + "create/reset and checkout a branch"), + OPT_BOOLEAN('l', NULL, &opts.new_branch_log, "create reflog for new branch"), + OPT_BOOLEAN(0, "detach", &opts.force_detach, "detach the HEAD at named commit"), + OPT_SET_INT('t', "track", &opts.track, "set upstream info for new branch", BRANCH_TRACK_EXPLICIT), OPT_STRING(0, "orphan", &opts.new_orphan_branch, "new branch", "new unparented branch"), - OPT_SET_INT('2', "ours", &opts.writeout_stage, "stage", + OPT_SET_INT('2', "ours", &opts.writeout_stage, "checkout our version for unmerged files", 2), - OPT_SET_INT('3', "theirs", &opts.writeout_stage, "stage", + OPT_SET_INT('3', "theirs", &opts.writeout_stage, "checkout their version for unmerged files", 3), - OPT_BOOLEAN('f', "force", &opts.force, "force"), - OPT_BOOLEAN('m', "merge", &opts.merge, "merge"), + OPT__FORCE(&opts.force, "force checkout (throw away local modifications)"), + OPT_BOOLEAN('m', "merge", &opts.merge, "perform a 3-way merge with the new branch"), OPT_STRING(0, "conflict", &conflict_style, "style", "conflict style (merge or diff3)"), OPT_BOOLEAN('p', "patch", &patch_mode, "select hunks interactively"), @@ -676,42 +951,56 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) PARSE_OPT_NOARG | PARSE_OPT_HIDDEN }, OPT_END(), }; - int has_dash_dash; memset(&opts, 0, sizeof(opts)); memset(&new, 0, sizeof(new)); - git_config(git_checkout_config, NULL); + gitmodules_config(); + git_config(git_checkout_config, &opts); opts.track = BRANCH_TRACK_UNSPECIFIED; 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")); + + /* copy -B over to -b, so that we can just check the latter */ + if (opts.new_branch_force) + opts.new_branch = opts.new_branch_force; + if (patch_mode && (opts.track > 0 || opts.new_branch - || opts.new_branch_log || opts.merge || opts.force)) - die ("--patch is incompatible with all other options"); + || opts.new_branch_log || opts.merge || opts.force + || opts.force_detach)) + die (_("--patch is incompatible with all other options")); + + if (opts.force_detach && (opts.new_branch || opts.new_orphan_branch)) + die(_("--detach cannot be used with -b/-B/--orphan")); + if (opts.force_detach && 0 < opts.track) + die(_("--detach cannot be used with -t")); /* --track without -b should DWIM */ if (0 < opts.track && !opts.new_branch) { const char *argv0 = argv[0]; if (!argc || !strcmp(argv0, "--")) - die ("--track needs a branch name"); + die (_("--track needs a branch name")); if (!prefixcmp(argv0, "refs/")) argv0 += 5; if (!prefixcmp(argv0, "remotes/")) argv0 += 8; argv0 = strchr(argv0, '/'); if (!argv0 || !argv0[1]) - die ("Missing branch name; try -b"); + die (_("Missing branch name; try -b")); opts.new_branch = argv0 + 1; } if (opts.new_orphan_branch) { if (opts.new_branch) - die("--orphan and -b are mutually exclusive"); + die(_("--orphan and -b|-B are mutually exclusive")); if (opts.track > 0) - die("--orphan cannot be used with -t"); + die(_("--orphan cannot be used with -t")); opts.new_branch = opts.new_orphan_branch; } @@ -721,108 +1010,33 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) } if (opts.force && opts.merge) - die("git checkout: -f and -m are incompatible"); + die(_("git checkout: -f and -m are incompatible")); /* - * case 1: git checkout <ref> -- [<paths>] + * Extract branch name from command line arguments, so + * all that is left is pathspecs. * - * <ref> must be a valid tree, everything after the '--' must be - * a path. - * - * case 2: git checkout -- [<paths>] - * - * everything after the '--' must be paths. - * - * case 3: git checkout <something> [<paths>] + * Handle * - * With no paths, if <something> is a commit, that is to - * switch to the branch or detach HEAD at it. As a special case, - * if <something> is A...B (missing A or B means HEAD but you can - * omit at most one side), and if there is a unique merge base - * between A and B, A...B names that merge base. - * - * With no paths, if <something> is _not_ a commit, no -t nor -b - * was given, and there is a tracking branch whose name is - * <something> in one and only one remote, then this is a short-hand - * to fork local <something> from that remote tracking branch. - * - * Otherwise <something> shall not be ambiguous. - * - If it's *only* a reference, treat it like case (1). - * - If it's only a path, treat it like case (2). - * - else: fail. + * 1) git checkout <tree> -- [<paths>] + * 2) git checkout -- [<paths>] + * 3) git checkout <something> [<paths>] * + * including "last branch" syntax and DWIM-ery for names of + * remote branches, erroring out for invalid or ambiguous cases. */ if (argc) { - if (!strcmp(argv[0], "--")) { /* case (2) */ - argv++; - argc--; - goto no_reference; - } - - arg = argv[0]; - has_dash_dash = (argc > 1) && !strcmp(argv[1], "--"); - - if (!strcmp(arg, "-")) - arg = "@{-1}"; - - if (get_sha1_mb(arg, rev)) { - if (has_dash_dash) /* case (1) */ - die("invalid reference: %s", arg); - if (!patch_mode && - dwim_new_local_branch && - opts.track == BRANCH_TRACK_UNSPECIFIED && - !opts.new_branch && - !check_filename(NULL, arg) && - argc == 1) { - const char *remote = unique_tracking_name(arg); - if (!remote || get_sha1(remote, rev)) - goto no_reference; - opts.new_branch = arg; - arg = remote; - /* DWIMmed to create local branch */ - } - else - goto no_reference; - } - - /* we can't end up being in (2) anymore, eat the argument */ - argv++; - argc--; - - new.name = arg; - if ((new.commit = lookup_commit_reference_gently(rev, 1))) { - setup_branch_path(&new); - - if ((check_ref_format(new.path) == CHECK_REF_FORMAT_OK) && - resolve_ref(new.path, rev, 1, NULL)) - ; - else - new.path = NULL; - parse_commit(new.commit); - source_tree = new.commit->tree; - } else - source_tree = parse_tree_indirect(rev); - - if (!source_tree) /* case (1): want a tree */ - die("reference is not a tree: %s", arg); - if (!has_dash_dash) {/* case (3 -> 1) */ - /* - * Do not complain the most common case - * git checkout branch - * even if there happen to be a file called 'branch'; - * it would be extremely annoying. - */ - if (argc) - verify_non_filename(NULL, arg); - } - else { - argv++; - argc--; - } + int dwim_ok = + !patch_mode && + dwim_new_local_branch && + opts.track == BRANCH_TRACK_UNSPECIFIED && + !opts.new_branch; + int n = parse_branchname_arg(argc, argv, dwim_ok, + &new, &source_tree, rev, &opts.new_branch); + argv += n; + argc -= n; } -no_reference: - if (opts.track == BRANCH_TRACK_UNSPECIFIED) opts.track = git_branch_track; @@ -830,7 +1044,7 @@ no_reference: const char **pathspec = get_pathspec(prefix, argv); if (!pathspec) - die("invalid path specification"); + die(_("invalid path specification")); if (patch_mode) return interactive_checkout(new.name, pathspec, &opts); @@ -838,16 +1052,19 @@ no_reference: /* Checkout paths */ if (opts.new_branch) { if (argc == 1) { - die("git checkout: updating paths is incompatible with switching branches.\nDid you intend to checkout '%s' which can not be resolved as commit?", argv[0]); + die(_("git checkout: updating paths is incompatible with switching branches.\nDid you intend to checkout '%s' which can not be resolved as commit?"), argv[0]); } else { - die("git checkout: updating paths is incompatible with switching branches."); + die(_("git checkout: updating paths is incompatible with switching branches.")); } } + if (opts.force_detach) + die(_("git checkout: --detach does not take a path argument")); + if (1 < !!opts.writeout_stage + !!opts.force + !!opts.merge) - die("git checkout: --ours/--theirs, --force and --merge are incompatible when\nchecking out of the index."); + die(_("git checkout: --ours/--theirs, --force and --merge are incompatible when\nchecking out of the index.")); - return checkout_paths(source_tree, pathspec, &opts); + return checkout_paths(source_tree, pathspec, prefix, &opts); } if (patch_mode) @@ -855,19 +1072,17 @@ no_reference: if (opts.new_branch) { struct strbuf buf = STRBUF_INIT; - if (strbuf_check_branch_ref(&buf, opts.new_branch)) - die("git checkout: we do not like '%s' as a branch name.", - opts.new_branch); - if (!get_sha1(buf.buf, rev)) - die("git checkout: branch %s already exists", opts.new_branch); + + opts.branch_exists = validate_new_branchname(opts.new_branch, &buf, !!opts.new_branch_force); + strbuf_release(&buf); } if (new.name && !new.commit) { - die("Cannot switch branch to a non-commit."); + die(_("Cannot switch branch to a non-commit.")); } if (opts.writeout_stage) - die("--ours/--theirs is incompatible with switching branches."); + die(_("--ours/--theirs is incompatible with switching branches.")); return switch_branches(&opts, &new); } diff --git a/builtin/clean.c b/builtin/clean.c index fac64e6cd3..0c7b3d0f4c 100644 --- a/builtin/clean.c +++ b/builtin/clean.c @@ -10,12 +10,13 @@ #include "cache.h" #include "dir.h" #include "parse-options.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] [-x | -X] [--] <paths>...", + "git clean [-d] [-f] [-n] [-q] [-e <pattern>] [-x | -X] [--] <paths>...", NULL }; @@ -26,24 +27,34 @@ static int git_clean_config(const char *var, const char *value, void *cb) return git_default_config(var, value, cb); } +static int exclude_cb(const struct option *opt, const char *arg, int unset) +{ + struct string_list *exclude_list = opt->value; + string_list_append(exclude_list, arg); + return 0; +} + int cmd_clean(int argc, const char **argv, const char *prefix) { int i; int show_only = 0, remove_directories = 0, quiet = 0, ignored = 0; - int ignored_only = 0, baselen = 0, config_set = 0, errors = 0; + int ignored_only = 0, config_set = 0, errors = 0; int rm_flags = REMOVE_DIR_KEEP_NESTED_GIT; struct strbuf directory = STRBUF_INIT; struct dir_struct dir; static const char **pathspec; struct strbuf buf = STRBUF_INIT; + struct string_list exclude_list = STRING_LIST_INIT_NODUP; const char *qname; char *seen = NULL; struct option options[] = { - OPT__QUIET(&quiet), - OPT__DRY_RUN(&show_only), - OPT_BOOLEAN('f', "force", &force, "force"), + OPT__QUIET(&quiet, "do not print names of files removed"), + OPT__DRY_RUN(&show_only, "dry run"), + OPT__FORCE(&force, "force"), OPT_BOOLEAN('d', NULL, &remove_directories, "remove whole directories"), + { OPTION_CALLBACK, 'e', "exclude", &exclude_list, "pattern", + "add <pattern> to ignore rules", PARSE_OPT_NONEG, exclude_cb }, OPT_BOOLEAN('x', NULL, &ignored, "remove ignored files, too"), OPT_BOOLEAN('X', NULL, &ignored_only, "remove only ignored files"), @@ -64,11 +75,16 @@ int cmd_clean(int argc, const char **argv, const char *prefix) dir.flags |= DIR_SHOW_IGNORED; if (ignored && ignored_only) - die("-x and -X cannot be used together"); - - if (!show_only && !force) - die("clean.requireForce %s to true and neither -n nor -f given; " - "refusing to clean", config_set ? "set" : "defaults"); + die(_("-x and -X cannot be used together")); + + if (!show_only && !force) { + if (config_set) + die(_("clean.requireForce set to true and neither -n nor -f given; " + "refusing to clean")); + else + die(_("clean.requireForce defaults to true and neither -n nor -f given; " + "refusing to clean")); + } if (force > 1) rm_flags = 0; @@ -76,11 +92,15 @@ int cmd_clean(int argc, const char **argv, const char *prefix) dir.flags |= DIR_SHOW_OTHER_DIRECTORIES; if (read_cache() < 0) - die("index file corrupt"); + die(_("index file corrupt")); if (!ignored) setup_standard_excludes(&dir); + for (i = 0; i < exclude_list.nr; i++) + add_exclude(exclude_list.items[i].string, "", 0, + &dir.exclude_list[EXC_CMDL]); + pathspec = get_pathspec(prefix, argv); fill_directory(&dir, pathspec); @@ -124,7 +144,7 @@ int cmd_clean(int argc, const char **argv, const char *prefix) if (pathspec) { memset(seen, 0, argc > 0 ? argc : 1); matches = match_pathspec(pathspec, ent->name, len, - baselen, seen); + 0, seen); } if (S_ISDIR(st.st_mode)) { @@ -132,20 +152,20 @@ int cmd_clean(int argc, const char **argv, const char *prefix) qname = quote_path_relative(directory.buf, directory.len, &buf, prefix); if (show_only && (remove_directories || (matches == MATCHED_EXACTLY))) { - printf("Would remove %s\n", qname); + printf(_("Would remove %s\n"), qname); } else if (remove_directories || (matches == MATCHED_EXACTLY)) { if (!quiet) - printf("Removing %s\n", qname); + printf(_("Removing %s\n"), qname); if (remove_dir_recursively(&directory, rm_flags) != 0) { - warning("failed to remove '%s'", qname); + warning(_("failed to remove %s"), qname); errors++; } } else if (show_only) { - printf("Would not remove %s\n", qname); + printf(_("Would not remove %s\n"), qname); } else { - printf("Not removing %s\n", qname); + printf(_("Not removing %s\n"), qname); } strbuf_reset(&directory); } else { @@ -153,13 +173,13 @@ int cmd_clean(int argc, const char **argv, const char *prefix) continue; qname = quote_path_relative(ent->name, -1, &buf, prefix); if (show_only) { - printf("Would remove %s\n", qname); + printf(_("Would remove %s\n"), qname); continue; } else if (!quiet) { - printf("Removing %s\n", qname); + printf(_("Removing %s\n"), qname); } if (unlink(ent->name) != 0) { - warning("failed to remove '%s'", qname); + warning(_("failed to remove %s"), qname); errors++; } } @@ -167,5 +187,6 @@ int cmd_clean(int argc, const char **argv, const char *prefix) free(seen); strbuf_release(&directory); + string_list_clear(&exclude_list, 0); return (errors != 0); } diff --git a/builtin/clone.c b/builtin/clone.c index efb1e6faa5..488f48e9a5 100644 --- a/builtin/clone.c +++ b/builtin/clone.c @@ -8,7 +8,7 @@ * Clone a repository into a different directory that does not yet exist. */ -#include "cache.h" +#include "builtin.h" #include "parse-options.h" #include "fetch-pack.h" #include "refs.h" @@ -39,12 +39,24 @@ static const char * const builtin_clone_usage[] = { static int option_no_checkout, option_bare, option_mirror; static int option_local, option_no_hardlinks, option_shared, option_recursive; -static char *option_template, *option_reference, *option_depth; +static char *option_template, *option_depth; static char *option_origin = NULL; static char *option_branch = NULL; +static const char *real_git_dir; static char *option_upload_pack = "git-upload-pack"; static int option_verbosity; static int option_progress; +static struct string_list option_config; +static struct string_list option_reference; + +static int opt_parse_reference(const struct option *opt, const char *arg, int unset) +{ + struct string_list *option_reference = opt->value; + if (!arg) + return -1; + string_list_append(option_reference, arg); + return 0; +} static struct option builtin_clone_options[] = { OPT__VERBOSITY(&option_verbosity), @@ -66,10 +78,12 @@ static struct option builtin_clone_options[] = { "setup as shared repository"), OPT_BOOLEAN(0, "recursive", &option_recursive, "initialize submodules in the clone"), - OPT_STRING(0, "template", &option_template, "path", - "path the template repository"), - OPT_STRING(0, "reference", &option_reference, "repo", - "reference repository"), + OPT_BOOLEAN(0, "recurse-submodules", &option_recursive, + "initialize submodules in the clone"), + OPT_STRING(0, "template", &option_template, "template-directory", + "directory from which templates will be used"), + OPT_CALLBACK(0 , "reference", &option_reference, "repo", + "reference repository", &opt_parse_reference), OPT_STRING('o', "origin", &option_origin, "branch", "use <branch> instead of 'origin' to track upstream"), OPT_STRING('b', "branch", &option_branch, "branch", @@ -78,7 +92,10 @@ static struct option builtin_clone_options[] = { "path to git-upload-pack on the remote"), OPT_STRING(0, "depth", &option_depth, "depth", "create a shallow clone of that depth"), - + OPT_STRING(0, "separate-git-dir", &real_git_dir, "gitdir", + "separate git dir from working tree"), + OPT_STRING_LIST('c', "config", &option_config, "key=value", + "set config inside the new repository"), OPT_END() }; @@ -96,9 +113,26 @@ static char *get_repo_path(const char *repo, int *is_bundle) for (i = 0; i < ARRAY_SIZE(suffix); i++) { const char *path; path = mkpath("%s%s", repo, suffix[i]); - if (is_directory(path)) { + if (stat(path, &st)) + continue; + if (S_ISDIR(st.st_mode)) { *is_bundle = 0; - return xstrdup(make_nonrelative_path(path)); + return xstrdup(absolute_path(path)); + } else if (S_ISREG(st.st_mode) && st.st_size > 8) { + /* Is it a "gitfile"? */ + char signature[8]; + int len, fd = open(path, O_RDONLY); + if (fd < 0) + continue; + len = read_in_full(fd, signature, 8); + close(fd); + if (len != 8 || strncmp(signature, "gitdir: ", 8)) + continue; + path = read_gitfile(path); + if (path) { + *is_bundle = 0; + return xstrdup(absolute_path(path)); + } } } @@ -107,7 +141,7 @@ static char *get_repo_path(const char *repo, int *is_bundle) path = mkpath("%s%s", repo, bundle_suffix[i]); if (!stat(path, &st) && S_ISREG(st.st_mode)) { *is_bundle = 1; - return xstrdup(make_nonrelative_path(path)); + return xstrdup(absolute_path(path)); } } @@ -192,39 +226,80 @@ static void strip_trailing_slashes(char *dir) *end = '\0'; } -static void setup_reference(const char *repo) +static int add_one_reference(struct string_list_item *item, void *cb_data) { - const char *ref_git; - char *ref_git_copy; - + char *ref_git; + struct strbuf alternate = STRBUF_INIT; struct remote *remote; struct transport *transport; const struct ref *extra; - ref_git = make_absolute_path(option_reference); - - if (is_directory(mkpath("%s/.git/objects", ref_git))) - ref_git = mkpath("%s/.git", ref_git); - else if (!is_directory(mkpath("%s/objects", ref_git))) - die("reference repository '%s' is not a local directory.", - option_reference); - - ref_git_copy = xstrdup(ref_git); - - add_to_alternates_file(ref_git_copy); - - remote = remote_get(ref_git_copy); - transport = transport_get(remote, ref_git_copy); + /* Beware: real_path() and mkpath() return static buffer */ + ref_git = xstrdup(real_path(item->string)); + if (is_directory(mkpath("%s/.git/objects", ref_git))) { + char *ref_git_git = xstrdup(mkpath("%s/.git", ref_git)); + free(ref_git); + ref_git = ref_git_git; + } else if (!is_directory(mkpath("%s/objects", ref_git))) + die(_("reference repository '%s' is not a local directory."), + item->string); + + strbuf_addf(&alternate, "%s/objects", ref_git); + add_to_alternates_file(alternate.buf); + strbuf_release(&alternate); + + remote = remote_get(ref_git); + transport = transport_get(remote, ref_git); for (extra = transport_get_remote_refs(transport); extra; extra = extra->next) add_extra_ref(extra->name, extra->old_sha1, 0); transport_disconnect(transport); + free(ref_git); + return 0; +} + +static void setup_reference(void) +{ + for_each_string_list(&option_reference, add_one_reference, NULL); +} - free(ref_git_copy); +static void copy_alternates(struct strbuf *src, struct strbuf *dst, + const char *src_repo) +{ + /* + * Read from the source objects/info/alternates file + * and copy the entries to corresponding file in the + * destination repository with add_to_alternates_file(). + * Both src and dst have "$path/objects/info/alternates". + * + * Instead of copying bit-for-bit from the original, + * we need to append to existing one so that the already + * created entry via "clone -s" is not lost, and also + * to turn entries with paths relative to the original + * absolute, so that they can be used in the new repository. + */ + FILE *in = fopen(src->buf, "r"); + struct strbuf line = STRBUF_INIT; + + while (strbuf_getline(&line, in, '\n') != EOF) { + char *abs_path, abs_buf[PATH_MAX]; + if (!line.len || line.buf[0] == '#') + continue; + if (is_absolute_path(line.buf)) { + add_to_alternates_file(line.buf); + continue; + } + abs_path = mkpath("%s/objects/%s", src_repo, line.buf); + normalize_path_copy(abs_buf, abs_path); + add_to_alternates_file(abs_buf); + } + strbuf_release(&line); + fclose(in); } -static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest) +static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest, + const char *src_repo, int src_baselen) { struct dirent *de; struct stat buf; @@ -233,15 +308,15 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest) dir = opendir(src->buf); if (!dir) - die_errno("failed to open '%s'", src->buf); + die_errno(_("failed to open '%s'"), src->buf); if (mkdir(dest->buf, 0777)) { if (errno != EEXIST) - die_errno("failed to create directory '%s'", dest->buf); + die_errno(_("failed to create directory '%s'"), dest->buf); else if (stat(dest->buf, &buf)) - die_errno("failed to stat '%s'", dest->buf); + die_errno(_("failed to stat '%s'"), dest->buf); else if (!S_ISDIR(buf.st_mode)) - die("%s exists and is not a directory", dest->buf); + die(_("%s exists and is not a directory"), dest->buf); } strbuf_addch(src, '/'); @@ -255,26 +330,33 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest) strbuf_setlen(dest, dest_len); strbuf_addstr(dest, de->d_name); if (stat(src->buf, &buf)) { - warning ("failed to stat %s\n", src->buf); + warning (_("failed to stat %s\n"), src->buf); continue; } if (S_ISDIR(buf.st_mode)) { if (de->d_name[0] != '.') - copy_or_link_directory(src, dest); + copy_or_link_directory(src, dest, + src_repo, src_baselen); + continue; + } + + /* Files that cannot be copied bit-for-bit... */ + if (!strcmp(src->buf + src_baselen, "/info/alternates")) { + copy_alternates(src, dest, src_repo); continue; } if (unlink(dest->buf) && errno != ENOENT) - die_errno("failed to unlink '%s'", dest->buf); + die_errno(_("failed to unlink '%s'"), dest->buf); if (!option_no_hardlinks) { if (!link(src->buf, dest->buf)) continue; if (option_local) - die_errno("failed to create link '%s'", dest->buf); + die_errno(_("failed to create link '%s'"), dest->buf); option_no_hardlinks = 1; } if (copy_file_with_time(dest->buf, src->buf, 0666)) - die_errno("failed to copy file to '%s'", dest->buf); + die_errno(_("failed to copy file to '%s'"), dest->buf); } closedir(dir); } @@ -283,17 +365,20 @@ static const struct ref *clone_local(const char *src_repo, const char *dest_repo) { const struct ref *ret; - struct strbuf src = STRBUF_INIT; - struct strbuf dest = STRBUF_INIT; struct remote *remote; struct transport *transport; - if (option_shared) - add_to_alternates_file(src_repo); - else { + if (option_shared) { + struct strbuf alt = STRBUF_INIT; + strbuf_addf(&alt, "%s/objects", src_repo); + add_to_alternates_file(alt.buf); + strbuf_release(&alt); + } else { + struct strbuf src = STRBUF_INIT; + struct strbuf dest = STRBUF_INIT; strbuf_addf(&src, "%s/objects", src_repo); strbuf_addf(&dest, "%s/objects", dest_repo); - copy_or_link_directory(&src, &dest); + copy_or_link_directory(&src, &dest, src_repo, src.len); strbuf_release(&src); strbuf_release(&dest); } @@ -303,7 +388,7 @@ static const struct ref *clone_local(const char *src_repo, ret = transport_get_remote_refs(transport); transport_disconnect(transport); if (0 <= option_verbosity) - printf("done.\n"); + printf(_("done.\n")); return ret; } @@ -338,8 +423,9 @@ static void remove_junk_on_signal(int signo) static struct ref *wanted_peer_refs(const struct ref *refs, struct refspec *refspec) { - struct ref *local_refs = NULL; - struct ref **tail = &local_refs; + struct ref *head = copy_ref(find_ref_by_name(refs, "HEAD")); + struct ref *local_refs = head; + struct ref **tail = head ? &head->next : &local_refs; get_fetch_map(refs, refspec, &tail, 0); if (!option_mirror) @@ -352,16 +438,35 @@ static void write_remote_refs(const struct ref *local_refs) { const struct ref *r; - for (r = local_refs; r; r = r->next) + for (r = local_refs; r; r = r->next) { + if (!r->peer_ref) + continue; add_extra_ref(r->peer_ref->name, r->old_sha1, 0); + } pack_refs(PACK_REFS_ALL); clear_extra_refs(); } +static int write_one_config(const char *key, const char *value, void *data) +{ + return git_config_set_multivar(key, value ? value : "true", "^$", 0); +} + +static void write_config(struct string_list *config) +{ + int i; + + for (i = 0; i < config->nr; i++) { + if (git_config_parse_parameter(config->items[i].string, + write_one_config, NULL) < 0) + die("unable to write parameters to config file"); + } +} + int cmd_clone(int argc, const char **argv, const char *prefix) { - int is_bundle = 0; + int is_bundle = 0, is_local; struct stat buf; const char *repo_name, *repo, *work_tree, *git_dir; char *path, *dir; @@ -381,15 +486,16 @@ int cmd_clone(int argc, const char **argv, const char *prefix) junk_pid = getpid(); + packet_trace_identity("clone"); argc = parse_options(argc, argv, prefix, builtin_clone_options, builtin_clone_usage, 0); if (argc > 2) - usage_msg_opt("Too many arguments.", + usage_msg_opt(_("Too many arguments."), builtin_clone_usage, builtin_clone_options); if (argc == 0) - usage_msg_opt("You must specify a repository to clone.", + usage_msg_opt(_("You must specify a repository to clone."), builtin_clone_usage, builtin_clone_options); if (option_mirror) @@ -397,7 +503,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix) if (option_bare) { if (option_origin) - die("--bare and --origin %s options are incompatible.", + die(_("--bare and --origin %s options are incompatible."), option_origin); option_no_checkout = 1; } @@ -409,11 +515,14 @@ int cmd_clone(int argc, const char **argv, const char *prefix) path = get_repo_path(repo_name, &is_bundle); if (path) - repo = xstrdup(make_nonrelative_path(repo_name)); + repo = xstrdup(absolute_path(repo_name)); else if (!strchr(repo_name, ':')) - repo = xstrdup(make_absolute_path(repo_name)); + die(_("repository '%s' does not exist"), repo_name); else repo = repo_name; + is_local = path && !is_bundle; + if (is_local && option_depth) + warning(_("--depth is ignored in local clones; use file:// instead.")); if (argc == 2) dir = xstrdup(argv[1]); @@ -423,8 +532,8 @@ int cmd_clone(int argc, const char **argv, const char *prefix) dest_exists = !stat(dir, &buf); if (dest_exists && !is_empty_dir(dir)) - die("destination path '%s' already exists and is not " - "an empty directory.", dir); + die(_("destination path '%s' already exists and is not " + "an empty directory."), dir); strbuf_addf(&reflog_msg, "clone: from %s", repo); @@ -433,7 +542,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix) else { work_tree = getenv("GIT_WORK_TREE"); if (work_tree && !stat(work_tree, &buf)) - die("working tree '%s' already exists.", work_tree); + die(_("working tree '%s' already exists."), work_tree); } if (option_bare || work_tree) @@ -446,10 +555,10 @@ int cmd_clone(int argc, const char **argv, const char *prefix) if (!option_bare) { junk_work_tree = work_tree; if (safe_create_leading_directories_const(work_tree) < 0) - die_errno("could not create leading directories of '%s'", + die_errno(_("could not create leading directories of '%s'"), work_tree); if (!dest_exists && mkdir(work_tree, 0755)) - die_errno("could not create work tree dir '%s'.", + die_errno(_("could not create work tree dir '%s'."), work_tree); set_git_work_tree(work_tree); } @@ -460,13 +569,20 @@ int cmd_clone(int argc, const char **argv, const char *prefix) setenv(CONFIG_ENVIRONMENT, mkpath("%s/config", git_dir), 1); if (safe_create_leading_directories_const(git_dir) < 0) - die("could not create leading directories of '%s'", git_dir); - set_git_dir(make_absolute_path(git_dir)); + die(_("could not create leading directories of '%s'"), git_dir); - if (0 <= option_verbosity) - printf("Cloning into %s%s...\n", - option_bare ? "bare repository " : "", dir); + set_git_dir_init(git_dir, real_git_dir, 0); + if (real_git_dir) + git_dir = real_git_dir; + + if (0 <= option_verbosity) { + if (option_bare) + printf(_("Cloning into bare repository %s...\n"), dir); + else + printf(_("Cloning into %s...\n"), dir); + } init_db(option_template, INIT_DB_QUIET); + write_config(&option_config); /* * At this point, the config exists, so we do not need the @@ -506,15 +622,15 @@ int cmd_clone(int argc, const char **argv, const char *prefix) git_config_set(key.buf, repo); strbuf_reset(&key); - if (option_reference) - setup_reference(git_dir); + if (option_reference.nr) + setup_reference(); fetch_pattern = value.buf; refspec = parse_fetch_refspec(1, &fetch_pattern); strbuf_reset(&value); - if (path && !is_bundle) { + if (is_local) { refs = clone_local(path, git_dir); mapped_refs = wanted_peer_refs(refs, refspec); } else { @@ -522,7 +638,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix) transport = transport_get(remote, remote->url[0]); if (!transport->get_refs_list || !transport->fetch) - die("Don't know how to clone %s", transport->url); + die(_("Don't know how to clone %s"), transport->url); transport_set_option(transport, TRANS_OPT_KEEP, "yes"); @@ -561,8 +677,8 @@ int cmd_clone(int argc, const char **argv, const char *prefix) strbuf_release(&head); if (!our_head_points_at) { - warning("Remote branch %s not found in " - "upstream %s, using HEAD instead", + warning(_("Remote branch %s not found in " + "upstream %s, using HEAD instead"), option_branch, option_origin); our_head_points_at = remote_head_points_at; } @@ -571,7 +687,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix) our_head_points_at = remote_head_points_at; } else { - warning("You appear to have cloned an empty repository."); + warning(_("You appear to have cloned an empty repository.")); our_head_points_at = NULL; remote_head_points_at = NULL; remote_head = NULL; @@ -613,8 +729,8 @@ int cmd_clone(int argc, const char **argv, const char *prefix) } else { /* Nothing to checkout out */ if (!option_no_checkout) - warning("remote HEAD refers to nonexistent ref, " - "unable to checkout.\n"); + warning(_("remote HEAD refers to nonexistent ref, " + "unable to checkout.\n")); option_no_checkout = 1; } @@ -650,7 +766,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix) if (write_cache(fd, active_cache, active_nr) || commit_locked_index(lock_file)) - die("unable to write new index file"); + die(_("unable to write new index file")); err |= run_hook(NULL, "post-checkout", sha1_to_hex(null_sha1), sha1_to_hex(our_head_points_at->old_sha1), "1", diff --git a/builtin/commit-tree.c b/builtin/commit-tree.c index 87f0591c2f..d083795e26 100644 --- a/builtin/commit-tree.c +++ b/builtin/commit-tree.c @@ -9,7 +9,7 @@ #include "builtin.h" #include "utf8.h" -static const char commit_tree_usage[] = "git commit-tree <sha1> [-p <sha1>]* < changelog"; +static const char commit_tree_usage[] = "git commit-tree <sha1> [(-p <sha1>)...] < changelog"; static void new_parent(struct commit *parent, struct commit_list **parents_p) { @@ -56,10 +56,12 @@ int cmd_commit_tree(int argc, const char **argv, const char *prefix) if (strbuf_read(&buffer, 0, 0) < 0) die_errno("git commit-tree: failed to read"); - if (!commit_tree(buffer.buf, tree_sha1, parents, commit_sha1, NULL)) { - printf("%s\n", sha1_to_hex(commit_sha1)); - return 0; - } - else + if (commit_tree(buffer.buf, tree_sha1, parents, commit_sha1, NULL)) { + strbuf_release(&buffer); return 1; + } + + printf("%s\n", sha1_to_hex(commit_sha1)); + strbuf_release(&buffer); + return 0; } diff --git a/builtin/commit.c b/builtin/commit.c index a78dbd83bf..cbc9613ec6 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -25,6 +25,7 @@ #include "rerere.h" #include "unpack-trees.h" #include "quote.h" +#include "submodule.h" static const char * const builtin_commit_usage[] = { "git commit [options] [--] <filepattern>...", @@ -37,25 +38,31 @@ static const char * const builtin_status_usage[] = { }; static const char implicit_ident_advice[] = -"Your name and email address were configured automatically based\n" +N_("Your name and email address were configured automatically based\n" "on your username and hostname. Please check that they are accurate.\n" "You can suppress this message by setting them explicitly:\n" "\n" " git config --global user.name \"Your Name\"\n" " git config --global user.email you@example.com\n" "\n" -"If the identity used for this commit is wrong, you can fix it with:\n" +"After doing this, you may fix the identity used for this commit with:\n" "\n" -" git commit --amend --author='Your Name <you@example.com>'\n"; +" git commit --amend --reset-author\n"); static const char empty_amend_advice[] = -"You asked to amend the most recent commit, but doing so would make\n" +N_("You asked to amend the most recent commit, but doing so would make\n" "it empty. You can repeat your command with --allow-empty, or you can\n" -"remove the commit entirely with \"git reset HEAD^\".\n"; +"remove the commit entirely with \"git reset HEAD^\".\n"); -static unsigned char head_sha1[20]; +static const char empty_cherry_pick_advice[] = +N_("The previous cherry-pick is now empty, possibly due to conflict resolution.\n" +"If you wish to commit it anyway, use:\n" +"\n" +" git commit --allow-empty\n" +"\n" +"Otherwise, please use 'git reset'\n"); -static char *use_message_buffer; +static const char *use_message_buffer; static const char commit_editmsg[] = "COMMIT_EDITMSG"; static struct lock_file index_lock; /* real index */ static struct lock_file false_lock; /* used only for partial commits */ @@ -67,9 +74,14 @@ static enum { static const char *logfile, *force_author; static const char *template_file; +/* + * The _message variables are commit names from which to take + * the commit message and/or authorship. + */ +static const char *author_message, *author_message_buffer; static char *edit_message, *use_message; -static char *author_name, *author_email, *author_date; -static int all, edit_flag, also, interactive, only, amend, signoff; +static char *fixup_message, *squash_message; +static int all, edit_flag, also, interactive, patch_interactive, only, amend, signoff; static int quiet, verbose, no_verify, allow_empty, dry_run, renew_authorship; static int no_post_rewrite, allow_empty_message; static char *untracked_files_arg, *force_date, *ignore_submodule_arg; @@ -87,7 +99,8 @@ static enum { } cleanup_mode; static char *cleanup_arg; -static int use_editor = 1, initial_commit, in_merge, include_status = 1; +static enum commit_whence whence; +static int use_editor = 1, include_status = 1; static int show_ignored_in_status; static const char *only_include_assumed; static struct strbuf message; @@ -113,16 +126,18 @@ static int opt_parse_m(const struct option *opt, const char *arg, int unset) } static struct option builtin_commit_options[] = { - OPT__QUIET(&quiet), - OPT__VERBOSE(&verbose), + 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 log from file"), - OPT_STRING(0, "author", &force_author, "AUTHOR", "override author for commit"), - OPT_STRING(0, "date", &force_date, "DATE", "override date for commit"), - OPT_CALLBACK('m', "message", &message, "MESSAGE", "specify commit message", opt_parse_m), - OPT_STRING('c', "reedit-message", &edit_message, "COMMIT", "reuse and edit message from specified commit"), - OPT_STRING('C', "reuse-message", &use_message, "COMMIT", "reuse message from specified commit"), + OPT_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"), @@ -135,6 +150,7 @@ static struct option builtin_commit_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"), @@ -142,7 +158,7 @@ static struct option builtin_commit_options[] = { STATUS_FORMAT_SHORT), OPT_BOOLEAN(0, "branch", &status_show_branch, "show branch information"), OPT_SET_INT(0, "porcelain", &status_format, - "show porcelain output format", STATUS_FORMAT_PORCELAIN), + "machine-readable output", STATUS_FORMAT_PORCELAIN), OPT_BOOLEAN('z', "null", &null_termination, "terminate entries with NUL"), OPT_BOOLEAN(0, "amend", &amend, "amend previous commit"), @@ -160,6 +176,36 @@ static struct option builtin_commit_options[] = { OPT_END() }; +static void determine_whence(struct wt_status *s) +{ + if (file_exists(git_path("MERGE_HEAD"))) + whence = FROM_MERGE; + else if (file_exists(git_path("CHERRY_PICK_HEAD"))) + whence = FROM_CHERRY_PICK; + else + whence = FROM_COMMIT; + if (s) + s->whence = whence; +} + +static const char *whence_s(void) +{ + char *s = ""; + + switch (whence) { + case FROM_COMMIT: + break; + case FROM_MERGE: + s = "merge"; + break; + case FROM_CHERRY_PICK: + s = "cherry-pick"; + break; + } + + return s; +} + static void rollback_index_files(void) { switch (commit_style) { @@ -208,8 +254,10 @@ static int list_paths(struct string_list *list, const char *with_tree, ; m = xcalloc(1, i); - if (with_tree) - overlay_tree_on_cache(with_tree, prefix); + if (with_tree) { + const char *max_prefix = pathspec_prefix(prefix, pattern); + overlay_tree_on_cache(with_tree, max_prefix); + } for (i = 0; i < active_nr; i++) { struct cache_entry *ce = active_cache[i]; @@ -224,7 +272,7 @@ static int list_paths(struct string_list *list, const char *with_tree, item->util = item; /* better a valid pointer than a fake one */ } - return report_path_error(m, pattern, prefix ? strlen(prefix) : 0); + return report_path_error(m, pattern, prefix); } static void add_remove_files(struct string_list *list) @@ -240,19 +288,19 @@ static void add_remove_files(struct string_list *list) if (!lstat(p->string, &st)) { if (add_to_cache(p->string, &st, 0)) - die("updating files failed"); + die(_("updating files failed")); } else remove_file_from_cache(p->string); } } -static void create_base_index(void) +static void create_base_index(const struct commit *current_head) { struct tree *tree; struct unpack_trees_options opts; struct tree_desc t; - if (initial_commit) { + if (!current_head) { discard_cache(); return; } @@ -265,9 +313,9 @@ static void create_base_index(void) opts.dst_index = &the_index; opts.fn = oneway_merge; - tree = parse_tree_indirect(head_sha1); + tree = parse_tree_indirect(current_head->object.sha1); if (!tree) - die("failed to unpack HEAD tree object"); + die(_("failed to unpack HEAD tree object")); parse_tree(tree); init_tree_desc(&t, tree->buffer, tree->size); if (unpack_trees(1, &t, &opts)) @@ -284,29 +332,50 @@ static void refresh_cache_or_die(int refresh_flags) die_resolve_conflict("commit"); } -static char *prepare_index(int argc, const char **argv, const char *prefix, int is_status) +static char *prepare_index(int argc, const char **argv, const char *prefix, + const struct commit *current_head, int is_status) { int fd; struct string_list partial; const char **pathspec = NULL; + char *old_index_env = NULL; int refresh_flags = REFRESH_QUIET; if (is_status) refresh_flags |= REFRESH_UNMERGED; - if (interactive) { - if (interactive_add(argc, argv, prefix) != 0) - die("interactive add failed"); - if (read_cache_preload(NULL) < 0) - die("index file corrupt"); - commit_style = COMMIT_AS_IS; - return get_index_file(); - } if (*argv) pathspec = get_pathspec(prefix, argv); if (read_cache_preload(pathspec) < 0) - die("index file corrupt"); + die(_("index file corrupt")); + + if (interactive) { + fd = hold_locked_index(&index_lock, 1); + + refresh_cache_or_die(refresh_flags); + + if (write_cache(fd, active_cache, active_nr) || + close_lock_file(&index_lock)) + die(_("unable to create temporary index")); + + old_index_env = getenv(INDEX_ENVIRONMENT); + setenv(INDEX_ENVIRONMENT, index_lock.filename, 1); + + if (interactive_add(argc, argv, prefix, patch_interactive) != 0) + die(_("interactive add failed")); + + if (old_index_env && *old_index_env) + setenv(INDEX_ENVIRONMENT, old_index_env, 1); + else + unsetenv(INDEX_ENVIRONMENT); + + discard_cache(); + read_cache_from(index_lock.filename); + + commit_style = COMMIT_NORMAL; + return index_lock.filename; + } /* * Non partial, non as-is commit. @@ -326,7 +395,7 @@ static char *prepare_index(int argc, const char **argv, const char *prefix, int refresh_cache_or_die(refresh_flags); if (write_cache(fd, active_cache, active_nr) || close_lock_file(&index_lock)) - die("unable to write new_index file"); + die(_("unable to write new_index file")); commit_style = COMMIT_NORMAL; return index_lock.filename; } @@ -346,7 +415,7 @@ static char *prepare_index(int argc, const char **argv, const char *prefix, int if (active_cache_changed) { if (write_cache(fd, active_cache, active_nr) || commit_locked_index(&index_lock)) - die("unable to write new_index file"); + die(_("unable to write new_index file")); } else { rollback_lock_file(&index_lock); } @@ -375,37 +444,37 @@ static char *prepare_index(int argc, const char **argv, const char *prefix, int */ commit_style = COMMIT_PARTIAL; - if (in_merge) - die("cannot do a partial commit during a merge."); + if (whence != FROM_COMMIT) + die(_("cannot do a partial commit during a %s."), whence_s()); memset(&partial, 0, sizeof(partial)); partial.strdup_strings = 1; - if (list_paths(&partial, initial_commit ? NULL : "HEAD", prefix, pathspec)) + if (list_paths(&partial, !current_head ? NULL : "HEAD", prefix, pathspec)) exit(1); discard_cache(); if (read_cache() < 0) - die("cannot read the index"); + die(_("cannot read the index")); fd = hold_locked_index(&index_lock, 1); add_remove_files(&partial); refresh_cache(REFRESH_QUIET); if (write_cache(fd, active_cache, active_nr) || close_lock_file(&index_lock)) - die("unable to write new_index file"); + die(_("unable to write new_index file")); fd = hold_lock_file_for_update(&false_lock, git_path("next-index-%"PRIuMAX, (uintmax_t) getpid()), LOCK_DIE_ON_ERROR); - create_base_index(); + create_base_index(current_head); add_remove_files(&partial); refresh_cache(REFRESH_QUIET); if (write_cache(fd, active_cache, active_nr) || close_lock_file(&false_lock)) - die("unable to write temporary index file"); + die(_("unable to write temporary index file")); discard_cache(); read_cache_from(false_lock.filename); @@ -448,17 +517,14 @@ static int run_status(FILE *fp, const char *index_file, const char *prefix, int return s->commitable; } -static int is_a_merge(const unsigned char *sha1) +static int is_a_merge(const struct commit *current_head) { - struct commit *commit = lookup_commit(sha1); - if (!commit || parse_commit(commit)) - die("could not parse HEAD commit"); - return !!(commit->parents && commit->parents->next); + return !!(current_head->parents && current_head->parents->next); } static const char sign_off_header[] = "Signed-off-by: "; -static void determine_author_info(void) +static void determine_author_info(struct strbuf *author_ident) { char *name, *email, *date; @@ -466,18 +532,18 @@ static void determine_author_info(void) email = getenv("GIT_AUTHOR_EMAIL"); date = getenv("GIT_AUTHOR_DATE"); - if (use_message && !renew_authorship) { + if (author_message) { const char *a, *lb, *rb, *eol; - a = strstr(use_message_buffer, "\nauthor "); + a = strstr(author_message_buffer, "\nauthor "); if (!a) - die("invalid commit: %s", use_message); + die(_("invalid commit: %s"), author_message); lb = strchrnul(a + strlen("\nauthor "), '<'); rb = strchrnul(lb, '>'); eol = strchrnul(rb, '\n'); if (!*lb || !*rb || !*eol) - die("invalid commit: %s", use_message); + die(_("invalid commit: %s"), author_message); if (lb == a + strlen("\nauthor ")) /* \nauthor <foo@example.com> */ @@ -495,17 +561,15 @@ static void determine_author_info(void) const char *rb = strchr(force_author, '>'); if (!lb || !rb) - die("malformed --author parameter"); + die(_("malformed --author parameter")); name = xstrndup(force_author, lb - force_author); email = xstrndup(lb + 2, rb - (lb + 2)); } if (force_date) date = force_date; - - author_name = name; - author_email = email; - author_date = date; + strbuf_addstr(author_ident, fmt_ident(name, email, date, + IDENT_ERROR_ON_NO_NAME)); } static int ends_rfc2822_footer(struct strbuf *sb) @@ -549,68 +613,124 @@ static int ends_rfc2822_footer(struct strbuf *sb) return 1; } +static char *cut_ident_timestamp_part(char *string) +{ + char *ket = strrchr(string, '>'); + if (!ket || ket[1] != ' ') + die(_("Malformed ident string: '%s'"), string); + *++ket = '\0'; + return ket; +} + static int prepare_to_commit(const char *index_file, const char *prefix, - struct wt_status *s) + struct commit *current_head, + struct wt_status *s, + struct strbuf *author_ident) { struct stat statbuf; + struct strbuf committer_ident = STRBUF_INIT; int commitable, saved_color_setting; struct strbuf sb = STRBUF_INIT; char *buffer; - FILE *fp; const char *hook_arg1 = NULL; const char *hook_arg2 = NULL; int ident_shown = 0; + int clean_message_contents = (cleanup_mode != CLEANUP_NONE); if (!no_verify && run_hook(index_file, "pre-commit", NULL)) return 0; + if (squash_message) { + /* + * Insert the proper subject line before other commit + * message options add their content. + */ + if (use_message && !strcmp(use_message, squash_message)) + strbuf_addstr(&sb, "squash! "); + else { + struct pretty_print_context ctx = {0}; + struct commit *c; + c = lookup_commit_reference_by_name(squash_message); + if (!c) + die(_("could not lookup commit %s"), squash_message); + ctx.output_encoding = get_commit_output_encoding(); + format_commit_message(c, "squash! %s\n\n", &sb, + &ctx); + } + } + if (message.len) { strbuf_addbuf(&sb, &message); hook_arg1 = "message"; } else if (logfile && !strcmp(logfile, "-")) { if (isatty(0)) - fprintf(stderr, "(reading log message from standard input)\n"); + fprintf(stderr, _("(reading log message from standard input)\n")); if (strbuf_read(&sb, 0, 0) < 0) - die_errno("could not read log from standard input"); + die_errno(_("could not read log from standard input")); hook_arg1 = "message"; } else if (logfile) { if (strbuf_read_file(&sb, logfile, 0) < 0) - die_errno("could not read log file '%s'", + die_errno(_("could not read log file '%s'"), logfile); hook_arg1 = "message"; } else if (use_message) { buffer = strstr(use_message_buffer, "\n\n"); if (!buffer || buffer[2] == '\0') - die("commit has empty message"); + die(_("commit has empty message")); strbuf_add(&sb, buffer + 2, strlen(buffer + 2)); hook_arg1 = "commit"; hook_arg2 = use_message; + } else if (fixup_message) { + struct pretty_print_context ctx = {0}; + struct commit *commit; + commit = lookup_commit_reference_by_name(fixup_message); + if (!commit) + die(_("could not lookup commit %s"), fixup_message); + ctx.output_encoding = get_commit_output_encoding(); + format_commit_message(commit, "fixup! %s\n\n", + &sb, &ctx); + hook_arg1 = "message"; } else if (!stat(git_path("MERGE_MSG"), &statbuf)) { if (strbuf_read_file(&sb, git_path("MERGE_MSG"), 0) < 0) - die_errno("could not read MERGE_MSG"); + die_errno(_("could not read MERGE_MSG")); hook_arg1 = "merge"; } else if (!stat(git_path("SQUASH_MSG"), &statbuf)) { if (strbuf_read_file(&sb, git_path("SQUASH_MSG"), 0) < 0) - die_errno("could not read SQUASH_MSG"); + die_errno(_("could not read SQUASH_MSG")); hook_arg1 = "squash"; - } else if (template_file && !stat(template_file, &statbuf)) { + } else if (template_file) { if (strbuf_read_file(&sb, template_file, 0) < 0) - die_errno("could not read '%s'", template_file); + die_errno(_("could not read '%s'"), template_file); hook_arg1 = "template"; + clean_message_contents = 0; } /* - * This final case does not modify the template message, - * it just sets the argument to the prepare-commit-msg hook. + * The remaining cases don't modify the template message, but + * just set the argument(s) to the prepare-commit-msg hook. */ - else if (in_merge) + else if (whence == FROM_MERGE) hook_arg1 = "merge"; + else if (whence == FROM_CHERRY_PICK) { + hook_arg1 = "commit"; + hook_arg2 = "CHERRY_PICK_HEAD"; + } - fp = fopen(git_path(commit_editmsg), "w"); - if (fp == NULL) - die_errno("could not open '%s'", git_path(commit_editmsg)); + if (squash_message) { + /* + * If squash_commit was used for the commit subject, + * then we're possibly hijacking other commit log options. + * Reset the hook args to tell the real story. + */ + hook_arg1 = "message"; + hook_arg2 = ""; + } - if (cleanup_mode != CLEANUP_NONE) + s->fp = fopen(git_path(commit_editmsg), "w"); + if (s->fp == NULL) + die_errno(_("could not open '%s'"), git_path(commit_editmsg)); + + if (clean_message_contents) stripspace(&sb, 0); if (signoff) { @@ -631,77 +751,81 @@ static int prepare_to_commit(const char *index_file, const char *prefix, strbuf_release(&sob); } - if (fwrite(sb.buf, 1, sb.len, fp) < sb.len) - die_errno("could not write commit template"); + if (fwrite(sb.buf, 1, sb.len, s->fp) < sb.len) + die_errno(_("could not write commit template")); strbuf_release(&sb); - determine_author_info(); + /* This checks and barfs if author is badly specified */ + determine_author_info(author_ident); /* This checks if committer ident is explicitly given */ - git_committer_info(0); + strbuf_addstr(&committer_ident, git_committer_info(0)); if (use_editor && include_status) { - char *author_ident; - const char *committer_ident; - - if (in_merge) - fprintf(fp, - "#\n" - "# It looks like you may be committing a MERGE.\n" - "# If this is not correct, please remove the file\n" - "# %s\n" - "# and try again.\n" - "#\n", - git_path("MERGE_HEAD")); - - fprintf(fp, - "\n" - "# Please enter the commit message for your changes."); + char *ai_tmp, *ci_tmp; + if (whence != FROM_COMMIT) + status_printf_ln(s, GIT_COLOR_NORMAL, + _("\n" + "It looks like you may be committing a %s.\n" + "If this is not correct, please remove the file\n" + " %s\n" + "and try again.\n" + ""), + whence_s(), + git_path(whence == FROM_MERGE + ? "MERGE_HEAD" + : "CHERRY_PICK_HEAD")); + + fprintf(s->fp, "\n"); + status_printf(s, GIT_COLOR_NORMAL, + _("Please enter the commit message for your changes.")); if (cleanup_mode == CLEANUP_ALL) - fprintf(fp, - " Lines starting\n" - "# with '#' will be ignored, and an empty" - " message aborts the commit.\n"); + status_printf_more(s, GIT_COLOR_NORMAL, + _(" Lines starting\n" + "with '#' will be ignored, and an empty" + " message aborts the commit.\n")); else /* CLEANUP_SPACE, that is. */ - fprintf(fp, - " Lines starting\n" - "# with '#' will be kept; you may remove them" + status_printf_more(s, GIT_COLOR_NORMAL, + _(" Lines starting\n" + "with '#' will be kept; you may remove them" " yourself if you want to.\n" - "# An empty message aborts the commit.\n"); + "An empty message aborts the commit.\n")); if (only_include_assumed) - fprintf(fp, "# %s\n", only_include_assumed); - - author_ident = xstrdup(fmt_name(author_name, author_email)); - committer_ident = fmt_name(getenv("GIT_COMMITTER_NAME"), - getenv("GIT_COMMITTER_EMAIL")); - if (strcmp(author_ident, committer_ident)) - fprintf(fp, - "%s" - "# Author: %s\n", - ident_shown++ ? "" : "#\n", - author_ident); - free(author_ident); + status_printf_ln(s, GIT_COLOR_NORMAL, + "%s", only_include_assumed); + + ai_tmp = cut_ident_timestamp_part(author_ident->buf); + ci_tmp = cut_ident_timestamp_part(committer_ident.buf); + if (strcmp(author_ident->buf, committer_ident.buf)) + status_printf_ln(s, GIT_COLOR_NORMAL, + _("%s" + "Author: %s"), + ident_shown++ ? "" : "\n", + author_ident->buf); if (!user_ident_sufficiently_given()) - fprintf(fp, - "%s" - "# Committer: %s\n", - ident_shown++ ? "" : "#\n", - committer_ident); + status_printf_ln(s, GIT_COLOR_NORMAL, + _("%s" + "Committer: %s"), + ident_shown++ ? "" : "\n", + committer_ident.buf); if (ident_shown) - fprintf(fp, "#\n"); + status_printf_ln(s, GIT_COLOR_NORMAL, ""); saved_color_setting = s->use_color; s->use_color = 0; - commitable = run_status(fp, index_file, prefix, 1, s); + commitable = run_status(s->fp, index_file, prefix, 1, s); s->use_color = saved_color_setting; + + *ai_tmp = ' '; + *ci_tmp = ' '; } else { unsigned char sha1[20]; const char *parent = "HEAD"; if (!active_nr && read_cache() < 0) - die("Cannot read index"); + die(_("Cannot read index")); if (amend) parent = "HEAD^1"; @@ -711,14 +835,22 @@ static int prepare_to_commit(const char *index_file, const char *prefix, else commitable = index_differs_from(parent, 0); } + strbuf_release(&committer_ident); - fclose(fp); + fclose(s->fp); - if (!commitable && !in_merge && !allow_empty && - !(amend && is_a_merge(head_sha1))) { + /* + * Reject an attempt to record a non-merge empty commit without + * explicit --allow-empty. In the cherry-pick case, it may be + * empty due to conflict resolution, which the user should okay. + */ + if (!commitable && whence != FROM_MERGE && !allow_empty && + !(amend && is_a_merge(current_head))) { run_status(stdout, index_file, prefix, 0, s); if (amend) - fputs(empty_amend_advice, stderr); + fputs(_(empty_amend_advice), stderr); + else if (whence == FROM_CHERRY_PICK) + fputs(_(empty_cherry_pick_advice), stderr); return 0; } @@ -733,7 +865,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix, active_cache_tree = cache_tree(); if (cache_tree_update(active_cache_tree, active_cache, active_nr, 0, 0) < 0) { - error("Error building trees"); + error(_("Error building trees")); return 0; } @@ -748,7 +880,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix, snprintf(index, sizeof(index), "GIT_INDEX_FILE=%s", index_file); if (launch_editor(git_path(commit_editmsg), NULL, env)) { fprintf(stderr, - "Please supply the message using either -m or -F option.\n"); + _("Please supply the message using either -m or -F option.\n")); exit(1); } } @@ -828,7 +960,7 @@ static const char *find_author_by_nickname(const char *name) format_commit_message(commit, "%an <%ae>", &buf, &ctx); return strbuf_detach(&buf, NULL); } - die("No existing author found with '%s'", name); + die(_("No existing author found with '%s'"), name); } @@ -843,12 +975,35 @@ static void handle_untracked_files_arg(struct wt_status *s) else if (!strcmp(untracked_files_arg, "all")) s->show_untracked_files = SHOW_ALL_UNTRACKED_FILES; else - die("Invalid untracked files mode '%s'", untracked_files_arg); + die(_("Invalid untracked files mode '%s'"), untracked_files_arg); +} + +static const char *read_commit_message(const char *name) +{ + const char *out_enc, *out; + struct commit *commit; + + commit = lookup_commit_reference_by_name(name); + if (!commit) + die(_("could not lookup commit %s"), name); + out_enc = get_commit_output_encoding(); + out = logmsg_reencode(commit, out_enc); + + /* + * If we failed to reencode the buffer, just copy it + * byte for byte so the user can try to fix it up. + * This also handles the case where input and output + * encodings are identical. + */ + if (out == NULL) + out = xstrdup(commit->buffer); + return out; } static int parse_and_validate_options(int argc, const char *argv[], const char * const usage[], const char *prefix, + struct commit *current_head, struct wt_status *s) { int f = 0; @@ -860,86 +1015,63 @@ static int parse_and_validate_options(int argc, const char *argv[], force_author = find_author_by_nickname(force_author); if (force_author && renew_authorship) - die("Using both --reset-author and --author does not make sense"); + die(_("Using both --reset-author and --author does not make sense")); - if (logfile || message.len || use_message) + if (logfile || message.len || use_message || fixup_message) use_editor = 0; if (edit_flag) use_editor = 1; if (!use_editor) setenv("GIT_EDITOR", ":", 1); - if (get_sha1("HEAD", head_sha1)) - initial_commit = 1; - /* Sanity check options */ - if (amend && initial_commit) - die("You have nothing to amend."); - if (amend && in_merge) - die("You are in the middle of a merge -- cannot amend."); - + if (amend && !current_head) + die(_("You have nothing to amend.")); + if (amend && whence != FROM_COMMIT) + die(_("You are in the middle of a %s -- cannot amend."), whence_s()); + if (fixup_message && squash_message) + die(_("Options --squash and --fixup cannot be used together")); if (use_message) f++; if (edit_message) f++; + if (fixup_message) + f++; if (logfile) f++; if (f > 1) - die("Only one of -c/-C/-F can be used."); + die(_("Only one of -c/-C/-F/--fixup can be used.")); if (message.len && f > 0) - die("Option -m cannot be combined with -c/-C/-F."); + die((_("Option -m cannot be combined with -c/-C/-F/--fixup."))); if (edit_message) use_message = edit_message; - if (amend && !use_message) + if (amend && !use_message && !fixup_message) use_message = "HEAD"; - if (!use_message && renew_authorship) - die("--reset-author can be used only with -C, -c or --amend."); + if (!use_message && whence != FROM_CHERRY_PICK && renew_authorship) + die(_("--reset-author can be used only with -C, -c or --amend.")); if (use_message) { - unsigned char sha1[20]; - static char utf8[] = "UTF-8"; - const char *out_enc; - char *enc, *end; - struct commit *commit; - - if (get_sha1(use_message, sha1)) - die("could not lookup commit %s", use_message); - commit = lookup_commit_reference(sha1); - if (!commit || parse_commit(commit)) - die("could not parse commit %s", use_message); - - enc = strstr(commit->buffer, "\nencoding"); - if (enc) { - end = strchr(enc + 10, '\n'); - enc = xstrndup(enc + 10, end - (enc + 10)); - } else { - enc = utf8; + use_message_buffer = read_commit_message(use_message); + if (!renew_authorship) { + author_message = use_message; + author_message_buffer = use_message_buffer; } - out_enc = git_commit_encoding ? git_commit_encoding : utf8; - - if (strcmp(out_enc, enc)) - use_message_buffer = - reencode_string(commit->buffer, out_enc, enc); - - /* - * If we failed to reencode the buffer, just copy it - * byte for byte so the user can try to fix it up. - * This also handles the case where input and output - * encodings are identical. - */ - if (use_message_buffer == NULL) - use_message_buffer = xstrdup(commit->buffer); - if (enc != utf8) - free(enc); + } + if (whence == FROM_CHERRY_PICK && !renew_authorship) { + author_message = "CHERRY_PICK_HEAD"; + author_message_buffer = read_commit_message(author_message); } + if (patch_interactive) + interactive = 1; + if (!!also + !!only + !!all + !!interactive > 1) - die("Only one of --include/--only/--all/--interactive can be used."); + die(_("Only one of --include/--only/--all/--interactive/--patch can be used.")); if (argc == 0 && (also || (only && !amend))) - die("No paths with --include/--only does not make sense."); + die(_("No paths with --include/--only does not make sense.")); if (argc == 0 && only && amend) - only_include_assumed = "Clever... amending the last one with dirty index."; + only_include_assumed = _("Clever... amending the last one with dirty index."); if (argc > 0 && !also && !only) - only_include_assumed = "Explicit paths specified without -i nor -o; assuming --only paths..."; + only_include_assumed = _("Explicit paths specified without -i nor -o; assuming --only paths..."); if (!cleanup_arg || !strcmp(cleanup_arg, "default")) cleanup_mode = use_editor ? CLEANUP_ALL : CLEANUP_SPACE; else if (!strcmp(cleanup_arg, "verbatim")) @@ -949,14 +1081,12 @@ static int parse_and_validate_options(int argc, const char *argv[], else if (!strcmp(cleanup_arg, "strip")) cleanup_mode = CLEANUP_ALL; else - die("Invalid cleanup mode %s", cleanup_arg); + die(_("Invalid cleanup mode %s"), cleanup_arg); handle_untracked_files_arg(s); if (all && argc > 0) - die("Paths with -a does not make sense."); - else if (interactive && argc > 0) - die("Paths with --interactive does not make sense."); + die(_("Paths with -a does not make sense.")); if (null_termination && status_format == STATUS_FORMAT_LONG) status_format = STATUS_FORMAT_PORCELAIN; @@ -967,12 +1097,12 @@ static int parse_and_validate_options(int argc, const char *argv[], } static int dry_run_commit(int argc, const char **argv, const char *prefix, - struct wt_status *s) + const struct commit *current_head, struct wt_status *s) { int commitable; const char *index_file; - index_file = prepare_index(argc, argv, prefix, 1); + index_file = prepare_index(argc, argv, prefix, current_head, 1); commitable = run_status(stdout, index_file, prefix, 0, s); rollback_index_files(); @@ -983,6 +1113,8 @@ static int parse_status_slot(const char *var, int offset) { if (!strcasecmp(var+offset, "header")) return WT_STATUS_HEADER; + if (!strcasecmp(var+offset, "branch")) + return WT_STATUS_ONBRANCH; if (!strcasecmp(var+offset, "updated") || !strcasecmp(var+offset, "added")) return WT_STATUS_UPDATED; @@ -1009,7 +1141,7 @@ static int git_status_config(const char *k, const char *v, void *cb) return 0; } if (!strcmp(k, "status.color") || !strcmp(k, "color.status")) { - s->use_color = git_config_colorbool(k, v, -1); + s->use_color = git_config_colorbool(k, v); return 0; } if (!prefixcmp(k, "status.color.") || !prefixcmp(k, "color.status.")) { @@ -1035,7 +1167,7 @@ static int git_status_config(const char *k, const char *v, void *cb) else if (!strcmp(v, "all")) s->show_untracked_files = SHOW_ALL_UNTRACKED_FILES; else - return error("Invalid untracked files mode '%s'", v); + return error(_("Invalid untracked files mode '%s'"), v); return 0; } return git_diff_ui_config(k, v, NULL); @@ -1047,13 +1179,13 @@ 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), + OPT__VERBOSE(&verbose, "be verbose"), OPT_SET_INT('s', "short", &status_format, "show status concisely", STATUS_FORMAT_SHORT), OPT_BOOLEAN('b', "branch", &status_show_branch, "show branch information"), OPT_SET_INT(0, "porcelain", &status_format, - "show porcelain output format", + "machine-readable output", STATUS_FORMAT_PORCELAIN), OPT_BOOLEAN('z', "null", &null_termination, "terminate entries with NUL"), @@ -1069,15 +1201,20 @@ int cmd_status(int argc, const char **argv, const char *prefix) OPT_END(), }; - if (null_termination && status_format == STATUS_FORMAT_LONG) - status_format = STATUS_FORMAT_PORCELAIN; + if (argc == 2 && !strcmp(argv[1], "-h")) + usage_with_options(builtin_status_usage, builtin_status_options); wt_status_prepare(&s); + gitmodules_config(); git_config(git_status_config, &s); - in_merge = file_exists(git_path("MERGE_HEAD")); + determine_whence(&s); argc = parse_options(argc, argv, prefix, builtin_status_options, builtin_status_usage, 0); + + if (null_termination && status_format == STATUS_FORMAT_LONG) + status_format = STATUS_FORMAT_PORCELAIN; + handle_untracked_files_arg(&s); if (show_ignored_in_status) s.show_ignored_files = 1; @@ -1088,25 +1225,15 @@ int cmd_status(int argc, const char **argv, const char *prefix) refresh_index(&the_index, REFRESH_QUIET|REFRESH_UNMERGED, s.pathspec, NULL, NULL); fd = hold_locked_index(&index_lock, 0); - if (0 <= fd) { - if (active_cache_changed && - !write_cache(fd, active_cache, active_nr)) - commit_locked_index(&index_lock); - else - rollback_lock_file(&index_lock); - } + if (0 <= fd) + update_index_if_able(&the_index, &index_lock); s.is_initial = get_sha1(s.reference, sha1) ? 1 : 0; - s.in_merge = in_merge; s.ignore_submodule_arg = ignore_submodule_arg; wt_status_collect(&s); if (s.relative_paths) s.prefix = prefix; - if (s.use_color == -1) - s.use_color = git_use_color_default; - if (diff_use_color_default == -1) - diff_use_color_default = git_use_color_default; switch (status_format) { case STATUS_FORMAT_SHORT: @@ -1124,7 +1251,8 @@ int cmd_status(int argc, const char **argv, const char *prefix) return 0; } -static void print_summary(const char *prefix, const unsigned char *sha1) +static void print_summary(const char *prefix, const unsigned char *sha1, + int initial_commit) { struct rev_info rev; struct commit *commit; @@ -1137,9 +1265,9 @@ static void print_summary(const char *prefix, const unsigned char *sha1) commit = lookup_commit(sha1); if (!commit) - die("couldn't look up newly created commit"); + die(_("couldn't look up newly created commit")); if (!commit || parse_commit(commit)) - die("could not parse newly created commit"); + die(_("could not parse newly created commit")); strbuf_addstr(&format, "format:%h] %s"); @@ -1154,7 +1282,7 @@ static void print_summary(const char *prefix, const unsigned char *sha1) strbuf_addbuf_percentquote(&format, &committer_ident); if (advice_implicit_identity) { strbuf_addch(&format, '\n'); - strbuf_addstr(&format, implicit_ident_advice); + strbuf_addstr(&format, _(implicit_ident_advice)); } } strbuf_release(&author_ident); @@ -1163,7 +1291,6 @@ static void print_summary(const char *prefix, const unsigned char *sha1) init_revisions(&rev, prefix); setup_revisions(0, NULL, &rev, NULL); - rev.abbrev = 0; rev.diff = 1; rev.diffopt.output_format = DIFF_FORMAT_SHORTSTAT | DIFF_FORMAT_SUMMARY; @@ -1173,7 +1300,6 @@ static void print_summary(const char *prefix, const unsigned char *sha1) get_commit_format(format.buf, &rev); rev.always_show_header = 0; rev.diffopt.detect_rename = 1; - rev.diffopt.rename_limit = 100; rev.diffopt.break_opt = 0; diff_setup_done(&rev.diffopt); @@ -1181,9 +1307,9 @@ static void print_summary(const char *prefix, const unsigned char *sha1) !prefixcmp(head, "refs/heads/") ? head + 11 : !strcmp(head, "HEAD") ? - "detached HEAD" : + _("detached HEAD") : head, - initial_commit ? " (root-commit)" : ""); + initial_commit ? _(" (root-commit)") : ""); if (!log_tree_commit(&rev, commit)) { rev.always_show_header = 1; @@ -1245,77 +1371,79 @@ static int run_rewrite_hook(const unsigned char *oldsha1, int cmd_commit(int argc, const char **argv, const char *prefix) { struct strbuf sb = STRBUF_INIT; + struct strbuf author_ident = STRBUF_INIT; const char *index_file, *reflog_msg; char *nl, *p; - unsigned char commit_sha1[20]; + unsigned char sha1[20]; struct ref_lock *ref_lock; struct commit_list *parents = NULL, **pptr = &parents; struct stat statbuf; int allow_fast_forward = 1; struct wt_status s; + struct commit *current_head = NULL; + + if (argc == 2 && !strcmp(argv[1], "-h")) + usage_with_options(builtin_commit_usage, builtin_commit_options); wt_status_prepare(&s); git_config(git_commit_config, &s); - in_merge = file_exists(git_path("MERGE_HEAD")); - s.in_merge = in_merge; + determine_whence(&s); - if (s.use_color == -1) - s.use_color = git_use_color_default; - argc = parse_and_validate_options(argc, argv, builtin_commit_usage, - prefix, &s); - if (dry_run) { - if (diff_use_color_default == -1) - diff_use_color_default = git_use_color_default; - return dry_run_commit(argc, argv, prefix, &s); + if (get_sha1("HEAD", sha1)) + current_head = NULL; + else { + current_head = lookup_commit(sha1); + if (!current_head || parse_commit(current_head)) + die(_("could not parse HEAD commit")); } - index_file = prepare_index(argc, argv, prefix, 0); + argc = parse_and_validate_options(argc, argv, builtin_commit_usage, + prefix, current_head, &s); + if (dry_run) + return dry_run_commit(argc, argv, prefix, current_head, &s); + index_file = prepare_index(argc, argv, prefix, current_head, 0); /* Set up everything for writing the commit object. This includes running hooks, writing the trees, and interacting with the user. */ - if (!prepare_to_commit(index_file, prefix, &s)) { + if (!prepare_to_commit(index_file, prefix, + current_head, &s, &author_ident)) { rollback_index_files(); return 1; } /* Determine parents */ reflog_msg = getenv("GIT_REFLOG_ACTION"); - if (initial_commit) { + if (!current_head) { if (!reflog_msg) reflog_msg = "commit (initial)"; } else if (amend) { struct commit_list *c; - struct commit *commit; if (!reflog_msg) reflog_msg = "commit (amend)"; - commit = lookup_commit(head_sha1); - if (!commit || parse_commit(commit)) - die("could not parse HEAD commit"); - - for (c = commit->parents; c; c = c->next) + for (c = current_head->parents; c; c = c->next) pptr = &commit_list_insert(c->item, pptr)->next; - } else if (in_merge) { + } else if (whence == FROM_MERGE) { struct strbuf m = STRBUF_INIT; FILE *fp; if (!reflog_msg) reflog_msg = "commit (merge)"; - pptr = &commit_list_insert(lookup_commit(head_sha1), pptr)->next; + pptr = &commit_list_insert(current_head, pptr)->next; fp = fopen(git_path("MERGE_HEAD"), "r"); if (fp == NULL) - die_errno("could not open '%s' for reading", + die_errno(_("could not open '%s' for reading"), git_path("MERGE_HEAD")); while (strbuf_getline(&m, fp, '\n') != EOF) { unsigned char sha1[20]; if (get_sha1_hex(m.buf, sha1) < 0) - die("Corrupt MERGE_HEAD file (%s)", m.buf); + die(_("Corrupt MERGE_HEAD file (%s)"), m.buf); pptr = &commit_list_insert(lookup_commit(sha1), pptr)->next; } fclose(fp); strbuf_release(&m); if (!stat(git_path("MERGE_MODE"), &statbuf)) { if (strbuf_read_file(&sb, git_path("MERGE_MODE"), 0) < 0) - die_errno("could not read MERGE_MODE"); + die_errno(_("could not read MERGE_MODE")); if (!strcmp(sb.buf, "no-ff")) allow_fast_forward = 0; } @@ -1323,8 +1451,10 @@ int cmd_commit(int argc, const char **argv, const char *prefix) parents = reduce_heads(parents); } else { if (!reflog_msg) - reflog_msg = "commit"; - pptr = &commit_list_insert(lookup_commit(head_sha1), pptr)->next; + reflog_msg = (whence == FROM_CHERRY_PICK) + ? "commit (cherry-pick)" + : "commit"; + pptr = &commit_list_insert(current_head, pptr)->next; } /* Finally, get the commit message */ @@ -1332,7 +1462,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix) if (strbuf_read_file(&sb, git_path(commit_editmsg), 0) < 0) { int saved_errno = errno; rollback_index_files(); - die("could not read commit message: %s", strerror(saved_errno)); + die(_("could not read commit message: %s"), strerror(saved_errno)); } /* Truncate the message just before the diff, if any. */ @@ -1346,19 +1476,21 @@ int cmd_commit(int argc, const char **argv, const char *prefix) stripspace(&sb, cleanup_mode == CLEANUP_ALL); if (message_is_empty(&sb) && !allow_empty_message) { rollback_index_files(); - fprintf(stderr, "Aborting commit due to empty commit message.\n"); + fprintf(stderr, _("Aborting commit due to empty commit message.\n")); exit(1); } - if (commit_tree(sb.buf, active_cache_tree->sha1, parents, commit_sha1, - fmt_ident(author_name, author_email, author_date, - IDENT_ERROR_ON_NO_NAME))) { + if (commit_tree(sb.buf, active_cache_tree->sha1, parents, sha1, + author_ident.buf)) { rollback_index_files(); - die("failed to write commit object"); + die(_("failed to write commit object")); } + strbuf_release(&author_ident); ref_lock = lock_any_ref_for_update("HEAD", - initial_commit ? NULL : head_sha1, + !current_head + ? NULL + : current_head->object.sha1, 0); nl = strchr(sb.buf, '\n'); @@ -1371,22 +1503,23 @@ int cmd_commit(int argc, const char **argv, const char *prefix) if (!ref_lock) { rollback_index_files(); - die("cannot lock HEAD ref"); + die(_("cannot lock HEAD ref")); } - if (write_ref_sha1(ref_lock, commit_sha1, sb.buf) < 0) { + if (write_ref_sha1(ref_lock, sha1, sb.buf) < 0) { rollback_index_files(); - die("cannot update HEAD ref"); + die(_("cannot update HEAD ref")); } + unlink(git_path("CHERRY_PICK_HEAD")); unlink(git_path("MERGE_HEAD")); unlink(git_path("MERGE_MSG")); unlink(git_path("MERGE_MODE")); unlink(git_path("SQUASH_MSG")); if (commit_index_files()) - die ("Repository has been updated, but unable to write\n" + die (_("Repository has been updated, but unable to write\n" "new_index file. Check that disk is not full or quota is\n" - "not exceeded, and then \"git reset HEAD\" to recover."); + "not exceeded, and then \"git reset HEAD\" to recover.")); rerere(0); run_hook(get_index_file(), "post-commit", NULL); @@ -1394,13 +1527,14 @@ int cmd_commit(int argc, const char **argv, const char *prefix) struct notes_rewrite_cfg *cfg; cfg = init_copy_notes_for_rewrite("amend"); if (cfg) { - copy_note_for_rewrite(cfg, head_sha1, commit_sha1); + /* we are amending, so current_head is not NULL */ + copy_note_for_rewrite(cfg, current_head->object.sha1, sha1); finish_copy_notes_for_rewrite(cfg); } - run_rewrite_hook(head_sha1, commit_sha1); + run_rewrite_hook(current_head->object.sha1, sha1); } if (!quiet) - print_summary(prefix, commit_sha1); + print_summary(prefix, sha1, !current_head); return 0; } diff --git a/builtin/config.c b/builtin/config.c index f3d1660d02..0b4ecac855 100644 --- a/builtin/config.c +++ b/builtin/config.c @@ -20,7 +20,7 @@ static char delim = '='; static char key_delim = ' '; static char term = '\n'; -static int use_global_config, use_system_config; +static int use_global_config, use_system_config, use_local_config; static const char *given_config_file; static int actions, types; static const char *get_color_slot, *get_colorbool_slot; @@ -51,7 +51,8 @@ static struct option builtin_config_options[] = { OPT_GROUP("Config file location"), OPT_BOOLEAN(0, "global", &use_global_config, "use global config file"), OPT_BOOLEAN(0, "system", &use_system_config, "use system config file"), - OPT_STRING('f', "file", &given_config_file, "FILE", "use given config file"), + OPT_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), @@ -152,7 +153,6 @@ static int show_config(const char *key_, const char *value_, void *cb) static int get_value(const char *key_, const char *regex_) { int ret = -1; - char *tl; char *global = NULL, *repo_config = NULL; const char *system_wide = NULL, *local; @@ -160,24 +160,38 @@ static int get_value(const char *key_, const char *regex_) if (!local) { const char *home = getenv("HOME"); local = repo_config = git_pathdup("config"); - if (git_config_global() && home) + if (home) global = xstrdup(mkpath("%s/.gitconfig", home)); if (git_config_system()) system_wide = git_etc_gitconfig(); } - key = xstrdup(key_); - for (tl=key+strlen(key)-1; tl >= key && *tl != '.'; --tl) - *tl = tolower(*tl); - for (tl=key; *tl && *tl != '.'; ++tl) - *tl = tolower(*tl); - if (use_key_regexp) { + char *tl; + + /* + * NEEDSWORK: this naive pattern lowercasing obviously does not + * work for more complex patterns like "^[^.]*Foo.*bar". + * Perhaps we should deprecate this altogether someday. + */ + + key = xstrdup(key_); + for (tl = key + strlen(key) - 1; + tl >= key && *tl != '.'; + tl--) + *tl = tolower(*tl); + for (tl = key; *tl && *tl != '.'; tl++) + *tl = tolower(*tl); + key_regexp = (regex_t*)xmalloc(sizeof(regex_t)); if (regcomp(key_regexp, key, REG_EXTENDED)) { fprintf(stderr, "Invalid key pattern: %s\n", key_); + free(key); goto free_strings; } + } else { + if (git_config_parse_key(key_, &key, NULL)) + goto free_strings; } if (regex_) { @@ -289,24 +303,18 @@ static void get_color(const char *def_color) fputs(parsed_color, stdout); } -static int stdout_is_tty; static int get_colorbool_found; static int get_diff_color_found; +static int get_color_ui_found; static int git_get_colorbool_config(const char *var, const char *value, void *cb) { - if (!strcmp(var, get_colorbool_slot)) { - get_colorbool_found = - git_config_colorbool(var, value, stdout_is_tty); - } - if (!strcmp(var, "diff.color")) { - get_diff_color_found = - git_config_colorbool(var, value, stdout_is_tty); - } - if (!strcmp(var, "color.ui")) { - git_use_color_default = git_config_colorbool(var, value, stdout_is_tty); - return 0; - } + if (!strcmp(var, get_colorbool_slot)) + get_colorbool_found = git_config_colorbool(var, value); + else if (!strcmp(var, "diff.color")) + get_diff_color_found = git_config_colorbool(var, value); + else if (!strcmp(var, "color.ui")) + get_color_ui_found = git_config_colorbool(var, value); return 0; } @@ -320,9 +328,11 @@ static int get_colorbool(int print) if (!strcmp(get_colorbool_slot, "color.diff")) get_colorbool_found = get_diff_color_found; if (get_colorbool_found < 0) - get_colorbool_found = git_use_color_default; + get_colorbool_found = get_color_ui_found; } + get_colorbool_found = want_color(get_colorbool_found); + if (print) { printf("%s\n", get_colorbool_found ? "true" : "false"); return 0; @@ -330,11 +340,10 @@ static int get_colorbool(int print) return get_colorbool_found ? 0 : 1; } -int cmd_config(int argc, const char **argv, const char *unused_prefix) +int cmd_config(int argc, const char **argv, const char *prefix) { - int nongit; + int nongit = !startup_info->have_repository; char *value; - const char *prefix = setup_git_directory_gently(&nongit); config_exclusive_filename = getenv(CONFIG_ENVIRONMENT); @@ -342,7 +351,7 @@ int cmd_config(int argc, const char **argv, const char *unused_prefix) builtin_config_usage, PARSE_OPT_STOP_AT_NON_OPTION); - if (use_global_config + use_system_config + !!given_config_file > 1) { + if (use_global_config + use_system_config + use_local_config + !!given_config_file > 1) { error("only one config file at a time."); usage_with_options(builtin_config_usage, builtin_config_options); } @@ -358,6 +367,8 @@ int cmd_config(int argc, const char **argv, const char *unused_prefix) } else if (use_system_config) config_exclusive_filename = git_etc_gitconfig(); + else if (use_local_config) + config_exclusive_filename = git_pathdup("config"); else if (given_config_file) { if (!is_absolute_path(given_config_file) && prefix) config_exclusive_filename = prefix_filename(prefix, @@ -421,9 +432,14 @@ int cmd_config(int argc, const char **argv, const char *unused_prefix) NULL, NULL); } else if (actions == ACTION_SET) { + int ret; check_argc(argc, 2, 2); value = normalize_value(argv[0], argv[1]); - return git_config_set(argv[0], value); + ret = git_config_set(argv[0], value); + if (ret == CONFIG_NOTHING_SET) + error("cannot overwrite multiple values with a single value\n" + " Use a regexp, --add or --set-all to change %s.", argv[0]); + return ret; } else if (actions == ACTION_SET_ALL) { check_argc(argc, 2, 3); @@ -490,11 +506,15 @@ int cmd_config(int argc, const char **argv, const char *unused_prefix) } else if (actions == ACTION_GET_COLORBOOL) { if (argc == 1) - stdout_is_tty = git_config_bool("command line", argv[0]); - else if (argc == 0) - stdout_is_tty = isatty(1); + color_stdout_is_tty = git_config_bool("command line", argv[0]); return get_colorbool(argc != 0); } return 0; } + +int cmd_repo_config(int argc, const char **argv, const char *prefix) +{ + fprintf(stderr, "WARNING: git repo-config is deprecated in favor of git config.\n"); + return cmd_config(argc, argv, prefix); +} diff --git a/builtin/count-objects.c b/builtin/count-objects.c index 2bdd8ebde1..c37cb98c31 100644 --- a/builtin/count-objects.c +++ b/builtin/count-objects.c @@ -79,7 +79,7 @@ int cmd_count_objects(int argc, const char **argv, const char *prefix) unsigned long loose = 0, packed = 0, packed_loose = 0, garbage = 0; off_t loose_size = 0; struct option opts[] = { - OPT__VERBOSE(&verbose), + OPT__VERBOSE(&verbose, "be verbose"), OPT_END(), }; diff --git a/builtin/describe.c b/builtin/describe.c index 43caff2ffe..9f63067f50 100644 --- a/builtin/describe.c +++ b/builtin/describe.c @@ -6,6 +6,7 @@ #include "exec_cmd.h" #include "parse-options.h" #include "diff.h" +#include "hash.h" #define SEEN (1u<<0) #define MAX_TAGS (FLAG_BITS - 1) @@ -20,9 +21,10 @@ static int debug; /* Display lots of verbose info */ static int all; /* Any valid ref can be used */ static int tags; /* Allow lightweight tags */ static int longformat; -static int abbrev = DEFAULT_ABBREV; +static int abbrev = -1; /* unspecified */ static int max_candidates = 10; -static int found_names; +static struct hash_table names; +static int have_util; static const char *pattern; static int always; static const char *dirty; @@ -34,16 +36,44 @@ static const char *diff_index_args[] = { struct commit_name { + struct commit_name *next; + unsigned char peeled[20]; struct tag *tag; unsigned prio:2; /* annotated tag = 2, tag = 1, head = 0 */ unsigned name_checked:1; unsigned char sha1[20]; - char path[FLEX_ARRAY]; /* more */ + const char *path; }; static const char *prio_names[] = { "head", "lightweight", "annotated", }; +static inline unsigned int hash_sha1(const unsigned char *sha1) +{ + unsigned int hash; + memcpy(&hash, sha1, sizeof(hash)); + return hash; +} + +static inline struct commit_name *find_commit_name(const unsigned char *peeled) +{ + struct commit_name *n = lookup_hash(hash_sha1(peeled), &names); + while (n && !!hashcmp(peeled, n->peeled)) + n = n->next; + return n; +} + +static int set_util(void *chain, void *data) +{ + struct commit_name *n; + for (n = chain; n; n = n->next) { + struct commit *c = lookup_commit_reference_gently(n->peeled, 1); + if (c) + c->util = n; + } + return 0; +} + static int replace_name(struct commit_name *e, int prio, const unsigned char *sha1, @@ -78,31 +108,36 @@ static int replace_name(struct commit_name *e, } static void add_to_known_names(const char *path, - struct commit *commit, + const unsigned char *peeled, int prio, const unsigned char *sha1) { - struct commit_name *e = commit->util; + struct commit_name *e = find_commit_name(peeled); struct tag *tag = NULL; if (replace_name(e, prio, sha1, &tag)) { - size_t len = strlen(path)+1; - free(e); - e = xmalloc(sizeof(struct commit_name) + len); + if (!e) { + void **pos; + e = xmalloc(sizeof(struct commit_name)); + hashcpy(e->peeled, peeled); + pos = insert_hash(hash_sha1(peeled), e, &names); + if (pos) { + e->next = *pos; + *pos = e; + } else { + e->next = NULL; + } + } e->tag = tag; e->prio = prio; e->name_checked = 0; hashcpy(e->sha1, sha1); - memcpy(e->path, path, len); - commit->util = e; + e->path = path; } - found_names = 1; } static int get_name(const char *path, const unsigned char *sha1, int flag, void *cb_data) { int might_be_tag = !prefixcmp(path, "refs/tags/"); - struct commit *commit; - struct object *object; unsigned char peeled[20]; int is_tag, prio; @@ -110,16 +145,10 @@ static int get_name(const char *path, const unsigned char *sha1, int flag, void return 0; if (!peel_ref(path, peeled) && !is_null_sha1(peeled)) { - commit = lookup_commit_reference_gently(peeled, 1); - if (!commit) - return 0; - is_tag = !!hashcmp(sha1, commit->object.sha1); + is_tag = !!hashcmp(sha1, peeled); } else { - commit = lookup_commit_reference_gently(sha1, 1); - object = parse_object(sha1); - if (!commit || !object) - return 0; - is_tag = object->type == OBJ_TAG; + hashcpy(peeled, sha1); + is_tag = 0; } /* If --all, then any refs are used. @@ -142,7 +171,7 @@ static int get_name(const char *path, const unsigned char *sha1, int flag, void if (!prio) return 0; } - add_to_known_names(all ? path + 5 : path + 10, commit, prio, sha1); + add_to_known_names(all ? path + 5 : path + 10, peeled, prio, sha1); return 0; } @@ -189,7 +218,7 @@ static unsigned long finish_depth_computation( struct commit *p = parents->item; parse_commit(p); if (!(p->object.flags & SEEN)) - insert_by_date(p, list); + commit_list_insert_by_date(p, list); p->object.flags |= c->object.flags; parents = parents->next; } @@ -202,13 +231,13 @@ static void display_name(struct commit_name *n) if (n->prio == 2 && !n->tag) { n->tag = lookup_tag(n->sha1); if (!n->tag || parse_tag(n->tag)) - die("annotated tag %s not available", n->path); + die(_("annotated tag %s not available"), n->path); } if (n->tag && !n->name_checked) { if (!n->tag->tag) - die("annotated tag %s has no embedded name", n->path); + die(_("annotated tag %s has no embedded name"), n->path); if (strcmp(n->tag->tag, all ? n->path + 5 : n->path)) - warning("tag '%s' is really '%s' here", n->tag->tag, n->path); + warning(_("tag '%s' is really '%s' here"), n->tag->tag, n->path); n->name_checked = 1; } @@ -235,12 +264,12 @@ static void describe(const char *arg, int last_one) unsigned int unannotated_cnt = 0; if (get_sha1(arg, sha1)) - die("Not a valid object name %s", arg); + die(_("Not a valid object name %s"), arg); cmit = lookup_commit_reference(sha1); if (!cmit) - die("%s is not a valid '%s' object", arg, commit_type); + die(_("%s is not a valid '%s' object"), arg, commit_type); - n = cmit->util; + n = find_commit_name(cmit->object.sha1); if (n && (tags || all || n->prio == 2)) { /* * Exact match to an existing ref. @@ -255,9 +284,14 @@ static void describe(const char *arg, int last_one) } if (!max_candidates) - die("no tag exactly matches '%s'", sha1_to_hex(cmit->object.sha1)); + die(_("no tag exactly matches '%s'"), sha1_to_hex(cmit->object.sha1)); if (debug) - fprintf(stderr, "searching to describe %s\n", arg); + fprintf(stderr, _("searching to describe %s\n"), arg); + + if (!have_util) { + for_each_hash(&names, set_util, NULL); + have_util = 1; + } list = NULL; cmit->object.flags = SEEN; @@ -292,7 +326,7 @@ static void describe(const char *arg, int last_one) } if (annotated_cnt && !list) { if (debug) - fprintf(stderr, "finished search at %s\n", + fprintf(stderr, _("finished search at %s\n"), sha1_to_hex(c->object.sha1)); break; } @@ -300,7 +334,7 @@ static void describe(const char *arg, int last_one) struct commit *p = parents->item; parse_commit(p); if (!(p->object.flags & SEEN)) - insert_by_date(p, &list); + commit_list_insert_by_date(p, &list); p->object.flags |= c->object.flags; parents = parents->next; } @@ -316,19 +350,19 @@ static void describe(const char *arg, int last_one) return; } if (unannotated_cnt) - die("No annotated tags can describe '%s'.\n" - "However, there were unannotated tags: try --tags.", + die(_("No annotated tags can describe '%s'.\n" + "However, there were unannotated tags: try --tags."), sha1_to_hex(sha1)); else - die("No tags can describe '%s'.\n" - "Try --always, or create some tags.", + die(_("No tags can describe '%s'.\n" + "Try --always, or create some tags."), sha1_to_hex(sha1)); } qsort(all_matches, match_cnt, sizeof(all_matches[0]), compare_pt); if (gave_up_on) { - insert_by_date(gave_up_on, &list); + commit_list_insert_by_date(gave_up_on, &list); seen_commits--; } seen_commits += finish_depth_computation(&list, &all_matches[0]); @@ -341,11 +375,11 @@ static void describe(const char *arg, int last_one) prio_names[t->name->prio], t->depth, t->name->path); } - fprintf(stderr, "traversed %lu commits\n", seen_commits); + fprintf(stderr, _("traversed %lu commits\n"), seen_commits); if (gave_up_on) { fprintf(stderr, - "more than %i tags found; listed %i most recent\n" - "gave up search at %s\n", + _("more than %i tags found; listed %i most recent\n" + "gave up search at %s\n"), max_candidates, max_candidates, sha1_to_hex(gave_up_on->object.sha1)); } @@ -386,7 +420,11 @@ int cmd_describe(int argc, const char **argv, const char *prefix) OPT_END(), }; + git_config(git_default_config, NULL); argc = parse_options(argc, argv, prefix, options, describe_usage, 0); + if (abbrev < 0) + abbrev = DEFAULT_ABBREV; + if (max_candidates < 0) max_candidates = 0; else if (max_candidates > MAX_TAGS) @@ -395,7 +433,7 @@ int cmd_describe(int argc, const char **argv, const char *prefix) save_commit_buffer = 0; if (longformat && abbrev == 0) - die("--long is incompatible with --abbrev=0"); + die(_("--long is incompatible with --abbrev=0")); if (contains) { const char **args = xmalloc((7 + argc) * sizeof(char *)); @@ -418,16 +456,30 @@ int cmd_describe(int argc, const char **argv, const char *prefix) return cmd_name_rev(i + argc, args, prefix); } - for_each_ref(get_name, NULL); - if (!found_names && !always) - die("No names found, cannot describe anything."); + init_hash(&names); + for_each_rawref(get_name, NULL); + if (!names.nr && !always) + die(_("No names found, cannot describe anything.")); if (argc == 0) { - if (dirty && !cmd_diff_index(ARRAY_SIZE(diff_index_args) - 1, diff_index_args, prefix)) - dirty = NULL; + if (dirty) { + static struct lock_file index_lock; + int fd; + + read_cache_preload(NULL); + refresh_index(&the_index, REFRESH_QUIET|REFRESH_UNMERGED, + NULL, NULL, NULL); + fd = hold_locked_index(&index_lock, 0); + if (0 <= fd) + update_index_if_able(&the_index, &index_lock); + + if (!cmd_diff_index(ARRAY_SIZE(diff_index_args) - 1, + diff_index_args, prefix)) + dirty = NULL; + } describe("HEAD", 1); } else if (dirty) { - die("--dirty is incompatible with committishes"); + die(_("--dirty is incompatible with committishes")); } else { while (argc-- > 0) { describe(*argv++, argc == 0); diff --git a/builtin/diff-files.c b/builtin/diff-files.c index 5b64011de8..46085f862f 100644 --- a/builtin/diff-files.c +++ b/builtin/diff-files.c @@ -8,6 +8,7 @@ #include "commit.h" #include "revision.h" #include "builtin.h" +#include "submodule.h" static const char diff_files_usage[] = "git diff-files [-q] [-0/-1/2/3 |-c|--cc] [<common diff options>] [<path>...]" @@ -20,6 +21,7 @@ int cmd_diff_files(int argc, const char **argv, const char *prefix) unsigned options = 0; init_revisions(&rev, prefix); + gitmodules_config(); git_config(git_diff_basic_config, NULL); /* no "diff" UI options */ rev.abbrev = 0; @@ -59,7 +61,7 @@ int cmd_diff_files(int argc, const char **argv, const char *prefix) (rev.diffopt.output_format & DIFF_FORMAT_PATCH)) rev.combine_merges = rev.dense_combined_merges = 1; - if (read_cache_preload(rev.diffopt.paths) < 0) { + if (read_cache_preload(rev.diffopt.pathspec.raw) < 0) { perror("read_cache_preload"); return -1; } diff --git a/builtin/diff-index.c b/builtin/diff-index.c index 04837494fe..2eb32bd9da 100644 --- a/builtin/diff-index.c +++ b/builtin/diff-index.c @@ -3,6 +3,7 @@ #include "commit.h" #include "revision.h" #include "builtin.h" +#include "submodule.h" static const char diff_cache_usage[] = "git diff-index [-m] [--cached] " @@ -17,6 +18,7 @@ int cmd_diff_index(int argc, const char **argv, const char *prefix) int result; init_revisions(&rev, prefix); + gitmodules_config(); git_config(git_diff_basic_config, NULL); /* no "diff" UI options */ rev.abbrev = 0; diff --git a/builtin/diff-tree.c b/builtin/diff-tree.c index 3c78bda566..be6417d166 100644 --- a/builtin/diff-tree.c +++ b/builtin/diff-tree.c @@ -3,6 +3,7 @@ #include "commit.h" #include "log-tree.h" #include "builtin.h" +#include "submodule.h" static struct rev_info log_tree_opt; @@ -112,6 +113,7 @@ int cmd_diff_tree(int argc, const char **argv, const char *prefix) int read_stdin = 0; init_revisions(opt, prefix); + gitmodules_config(); git_config(git_diff_basic_config, NULL); /* no "diff" UI options */ opt->abbrev = 0; opt->diff = 1; @@ -161,6 +163,9 @@ int cmd_diff_tree(int argc, const char **argv, const char *prefix) } if (read_stdin) { + int saved_nrl = 0; + int saved_dcctc = 0; + if (opt->diffopt.detect_rename) opt->diffopt.setup |= (DIFF_SETUP_USE_SIZE_CACHE | DIFF_SETUP_USE_CACHE); @@ -171,9 +176,16 @@ int cmd_diff_tree(int argc, const char **argv, const char *prefix) fputs(line, stdout); fflush(stdout); } - else + else { diff_tree_stdin(line); + if (saved_nrl < opt->diffopt.needed_rename_limit) + saved_nrl = opt->diffopt.needed_rename_limit; + if (opt->diffopt.degraded_cc_to_c) + saved_dcctc = 1; + } } + opt->diffopt.degraded_cc_to_c = saved_dcctc; + opt->diffopt.needed_rename_limit = saved_nrl; } return diff_result_code(&opt->diffopt, 0); diff --git a/builtin/diff.c b/builtin/diff.c index 89ae89cde1..1118689fb2 100644 --- a/builtin/diff.c +++ b/builtin/diff.c @@ -13,6 +13,7 @@ #include "revision.h" #include "log-tree.h" #include "builtin.h" +#include "submodule.h" struct blobinfo { unsigned char sha1[20]; @@ -21,7 +22,7 @@ struct blobinfo { }; static const char builtin_diff_usage[] = -"git diff <options> <rev>{0,2} -- <path>*"; +"git diff [<options>] [<commit> [<commit>]] [--] [<path>...]"; static void stuff_change(struct diff_options *opt, unsigned old_mode, unsigned new_mode, @@ -70,9 +71,9 @@ static int builtin_diff_b_f(struct rev_info *revs, usage(builtin_diff_usage); if (lstat(path, &st)) - die_errno("failed to stat '%s'", path); + die_errno(_("failed to stat '%s'"), path); if (!(S_ISREG(st.st_mode) || S_ISLNK(st.st_mode))) - die("'%s': not a regular file or symlink", path); + die(_("'%s': not a regular file or symlink"), path); diff_set_mnemonic_prefix(&revs->diffopt, "o/", "w/"); @@ -134,7 +135,7 @@ static int builtin_diff_index(struct rev_info *revs, revs->max_count != -1 || revs->min_age != -1 || revs->max_age != -1) usage(builtin_diff_usage); - if (read_cache_preload(revs->diffopt.paths) < 0) { + if (read_cache_preload(revs->diffopt.pathspec.raw) < 0) { perror("read_cache_preload"); return -1; } @@ -181,6 +182,7 @@ static int builtin_diff_combined(struct rev_info *revs, hashcpy((unsigned char *)(parent + i), ent[i].item->sha1); diff_tree_combined(parent[0], parent + 1, ents - 1, revs->dense_combined_merges, revs); + free(parent); return 0; } @@ -196,17 +198,11 @@ static void refresh_index_quietly(void) discard_cache(); read_cache(); refresh_cache(REFRESH_QUIET|REFRESH_UNMERGED); - - if (active_cache_changed && - !write_cache(fd, active_cache, active_nr)) - commit_locked_index(lock_file); - - rollback_lock_file(lock_file); + update_index_if_able(&the_index, lock_file); } static int builtin_diff_files(struct rev_info *revs, int argc, const char **argv) { - int result; unsigned int options = 0; while (1 < argc && argv[1][0] == '-') { @@ -221,7 +217,7 @@ static int builtin_diff_files(struct rev_info *revs, int argc, const char **argv else if (!strcmp(argv[1], "-h")) usage(builtin_diff_usage); else - return error("invalid option: %s", argv[1]); + return error(_("invalid option: %s"), argv[1]); argv++; argc--; } @@ -236,12 +232,11 @@ static int builtin_diff_files(struct rev_info *revs, int argc, const char **argv revs->combine_merges = revs->dense_combined_merges = 1; setup_work_tree(); - if (read_cache_preload(revs->diffopt.paths) < 0) { + if (read_cache_preload(revs->diffopt.pathspec.raw) < 0) { perror("read_cache_preload"); return -1; } - result = run_diff_files(revs, options); - return diff_result_code(&revs->diffopt, result); + return run_diff_files(revs, options); } int cmd_diff(int argc, const char **argv, const char *prefix) @@ -279,11 +274,9 @@ int cmd_diff(int argc, const char **argv, const char *prefix) */ prefix = setup_git_directory_gently(&nongit); + gitmodules_config(); git_config(git_diff_ui_config, NULL); - if (diff_use_color_default == -1) - diff_use_color_default = git_use_color_default; - init_revisions(&rev, prefix); /* If this is a no-index diff, just run it and exit there. */ @@ -297,12 +290,12 @@ int cmd_diff(int argc, const char **argv, const char *prefix) DIFF_OPT_SET(&rev.diffopt, ALLOW_TEXTCONV); if (nongit) - die("Not a git repository"); + die(_("Not a git repository")); argc = setup_revisions(argc, argv, &rev, NULL); if (!rev.diffopt.output_format) { rev.diffopt.output_format = DIFF_FORMAT_PATCH; if (diff_setup_done(&rev.diffopt) < 0) - die("diff_setup_done failed"); + die(_("diff_setup_done failed")); } DIFF_OPT_SET(&rev.diffopt, RECURSIVE); @@ -328,8 +321,11 @@ int cmd_diff(int argc, const char **argv, const char *prefix) else if (!strcmp(arg, "--cached") || !strcmp(arg, "--staged")) { add_head_to_pending(&rev); - if (!rev.pending.nr) - die("No HEAD commit to compare with (yet)"); + if (!rev.pending.nr) { + struct tree *tree; + tree = lookup_tree((const unsigned char*)EMPTY_TREE_SHA1_BIN); + add_pending_object(&rev, &tree->object, "HEAD"); + } break; } } @@ -344,12 +340,12 @@ int cmd_diff(int argc, const char **argv, const char *prefix) obj = parse_object(obj->sha1); obj = deref_tag(obj, NULL, 0); if (!obj) - die("invalid object '%s' given.", name); + die(_("invalid object '%s' given."), name); if (obj->type == OBJ_COMMIT) obj = &((struct commit *)obj)->tree->object; if (obj->type == OBJ_TREE) { if (ARRAY_SIZE(ent) <= ents) - die("more than %d trees given: '%s'", + die(_("more than %d trees given: '%s'"), (int) ARRAY_SIZE(ent), name); obj->flags |= flags; ent[ents].item = obj; @@ -359,7 +355,7 @@ int cmd_diff(int argc, const char **argv, const char *prefix) } if (obj->type == OBJ_BLOB) { if (2 <= blobs) - die("more than two blobs given: '%s'", name); + die(_("more than two blobs given: '%s'"), name); hashcpy(blob[blobs].sha1, obj->sha1); blob[blobs].name = name; blob[blobs].mode = list->mode; @@ -367,16 +363,12 @@ int cmd_diff(int argc, const char **argv, const char *prefix) continue; } - die("unhandled object '%s' given.", name); + die(_("unhandled object '%s' given."), name); } - if (rev.prune_data) { - const char **pathspec = rev.prune_data; - while (*pathspec) { - if (!path) - path = *pathspec; - paths++; - pathspec++; - } + if (rev.prune_data.nr) { + if (!path) + path = rev.prune_data.items[0].match; + paths += rev.prune_data.nr; } /* diff --git a/builtin/fast-export.c b/builtin/fast-export.c index 9fe25ff0b3..9836e6b7ca 100644 --- a/builtin/fast-export.c +++ b/builtin/fast-export.c @@ -16,6 +16,7 @@ #include "string-list.h" #include "utf8.h" #include "parse-options.h" +#include "quote.h" static const char *fast_export_usage[] = { "git fast-export [rev-list-opts]", @@ -26,7 +27,9 @@ static int progress; static enum { ABORT, VERBATIM, WARN, STRIP } signed_tag_mode = ABORT; static enum { ERROR, DROP, REWRITE } tag_of_filtered_mode = ABORT; static int fake_missing_tagger; +static int use_done_feature; static int no_data; +static int full_tree; static int parse_opt_signed_tag_mode(const struct option *opt, const char *arg, int unset) @@ -147,23 +150,74 @@ static void handle_object(const unsigned char *sha1) free(buf); } +static int depth_first(const void *a_, const void *b_) +{ + const struct diff_filepair *a = *((const struct diff_filepair **)a_); + const struct diff_filepair *b = *((const struct diff_filepair **)b_); + const char *name_a, *name_b; + int len_a, len_b, len; + int cmp; + + name_a = a->one ? a->one->path : a->two->path; + name_b = b->one ? b->one->path : b->two->path; + + len_a = strlen(name_a); + len_b = strlen(name_b); + len = (len_a < len_b) ? len_a : len_b; + + /* strcmp will sort 'd' before 'd/e', we want 'd/e' before 'd' */ + cmp = memcmp(name_a, name_b, len); + if (cmp) + return cmp; + cmp = len_b - len_a; + if (cmp) + return cmp; + /* + * Move 'R'ename entries last so that all references of the file + * appear in the output before it is renamed (e.g., when a file + * was copied and renamed in the same commit). + */ + return (a->status == 'R') - (b->status == 'R'); +} + +static void print_path(const char *path) +{ + int need_quote = quote_c_style(path, NULL, NULL, 0); + if (need_quote) + quote_c_style(path, NULL, stdout, 0); + else + printf("%s", path); +} + static void show_filemodify(struct diff_queue_struct *q, struct diff_options *options, void *data) { int i; + + /* + * Handle files below a directory first, in case they are all deleted + * and the directory changes to a file or symlink. + */ + qsort(q->queue, q->nr, sizeof(q->queue[0]), depth_first); + for (i = 0; i < q->nr; i++) { struct diff_filespec *ospec = q->queue[i]->one; struct diff_filespec *spec = q->queue[i]->two; switch (q->queue[i]->status) { case DIFF_STATUS_DELETED: - printf("D %s\n", spec->path); + printf("D "); + print_path(spec->path); + putchar('\n'); break; case DIFF_STATUS_COPIED: case DIFF_STATUS_RENAMED: - printf("%c \"%s\" \"%s\"\n", q->queue[i]->status, - ospec->path, spec->path); + printf("%c ", q->queue[i]->status); + print_path(ospec->path); + putchar(' '); + print_path(spec->path); + putchar('\n'); if (!hashcmp(ospec->sha1, spec->sha1) && ospec->mode == spec->mode) @@ -178,13 +232,15 @@ static void show_filemodify(struct diff_queue_struct *q, * output the SHA-1 verbatim. */ if (no_data || S_ISGITLINK(spec->mode)) - printf("M %06o %s %s\n", spec->mode, - sha1_to_hex(spec->sha1), spec->path); + printf("M %06o %s ", spec->mode, + sha1_to_hex(spec->sha1)); else { struct object *object = lookup_object(spec->sha1); - printf("M %06o :%d %s\n", spec->mode, - get_object_mark(object), spec->path); + printf("M %06o :%d ", spec->mode, + get_object_mark(object)); } + print_path(spec->path); + putchar('\n'); break; default: @@ -241,7 +297,8 @@ static void handle_commit(struct commit *commit, struct rev_info *rev) message += 2; if (commit->parents && - get_object_mark(&commit->parents->item->object) != 0) { + get_object_mark(&commit->parents->item->object) != 0 && + !full_tree) { parse_commit(commit->parents->item); diff_tree_sha1(commit->parents->item->tree->object.sha1, commit->tree->object.sha1, "", &rev->diffopt); @@ -281,6 +338,8 @@ static void handle_commit(struct commit *commit, struct rev_info *rev) i++; } + if (full_tree) + printf("deleteall\n"); log_tree_diff_flush(rev); rev->diffopt.output_format = saved_output_format; @@ -565,8 +624,8 @@ static void import_marks(char *input_file) int cmd_fast_export(int argc, const char **argv, const char *prefix) { struct rev_info revs; - struct object_array commits = { 0, 0, NULL }; - struct string_list extra_refs = { NULL, 0, 0, 0 }; + struct object_array commits = OBJECT_ARRAY_INIT; + struct string_list extra_refs = STRING_LIST_INIT_NODUP; struct commit *commit; char *export_filename = NULL, *import_filename = NULL; struct option options[] = { @@ -578,12 +637,16 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix) OPT_CALLBACK(0, "tag-of-filtered-object", &tag_of_filtered_mode, "mode", "select handling of tags that tag filtered objects", parse_opt_tag_of_filtered_mode), - OPT_STRING(0, "export-marks", &export_filename, "FILE", + OPT_STRING(0, "export-marks", &export_filename, "file", "Dump marks to this file"), - OPT_STRING(0, "import-marks", &import_filename, "FILE", + OPT_STRING(0, "import-marks", &import_filename, "file", "Import marks from this file"), OPT_BOOLEAN(0, "fake-missing-tagger", &fake_missing_tagger, "Fake a tagger when tags lack one"), + OPT_BOOLEAN(0, "full-tree", &full_tree, + "Output full tree for each commit"), + OPT_BOOLEAN(0, "use-done-feature", &use_done_feature, + "Use the done feature to terminate the stream"), { OPTION_NEGBIT, 0, "data", &no_data, NULL, "Skip output of blob data", PARSE_OPT_NOARG | PARSE_OPT_NEGHELP, NULL, 1 }, @@ -605,9 +668,15 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix) if (argc > 1) usage_with_options (fast_export_usage, options); + if (use_done_feature) + printf("feature done\n"); + if (import_filename) import_marks(import_filename); + if (import_filename && revs.prune_data.nr) + full_tree = 1; + get_tags_and_duplicates(&revs.pending, &extra_refs); if (prepare_revision_walk(&revs)) @@ -629,5 +698,8 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix) if (export_filename) export_marks(export_filename); + if (use_done_feature) + printf("done\n"); + return 0; } diff --git a/builtin/fetch-pack.c b/builtin/fetch-pack.c index dbd8b7bcc8..412bd327b5 100644 --- a/builtin/fetch-pack.c +++ b/builtin/fetch-pack.c @@ -1,4 +1,4 @@ -#include "cache.h" +#include "builtin.h" #include "refs.h" #include "pkt-line.h" #include "commit.h" @@ -9,11 +9,13 @@ #include "fetch-pack.h" #include "remote.h" #include "run-command.h" +#include "transport.h" static int transfer_unpack_limit = -1; static int fetch_unpack_limit = -1; static int unpack_limit = 100; static int prefer_ofs_delta = 1; +static int no_done = 0; static struct fetch_pack_args args = { /* .uploadpack = */ "git-upload-pack", }; @@ -47,7 +49,7 @@ static void rev_list_push(struct commit *commit, int mark) if (parse_commit(commit)) return; - insert_by_date(commit, &rev_list); + commit_list_insert_by_date(commit, &rev_list); if (!(commit->object.flags & COMMON)) non_common_revs++; @@ -183,6 +185,36 @@ static void consume_shallow_list(int fd) } } +struct write_shallow_data { + struct strbuf *out; + int use_pack_protocol; + int count; +}; + +static int write_one_shallow(const struct commit_graft *graft, void *cb_data) +{ + struct write_shallow_data *data = cb_data; + const char *hex = sha1_to_hex(graft->sha1); + data->count++; + if (data->use_pack_protocol) + packet_buf_write(data->out, "shallow %s", hex); + else { + strbuf_addstr(data->out, hex); + strbuf_addch(data->out, '\n'); + } + return 0; +} + +static int write_shallow_commits(struct strbuf *out, int use_pack_protocol) +{ + struct write_shallow_data data; + data.out = out; + data.use_pack_protocol = use_pack_protocol; + data.count = 0; + for_each_commit_graft(write_one_shallow, &data); + return data.count; +} + static enum ack_type get_ack(int fd, unsigned char *result_sha1) { static char line[1000]; @@ -217,14 +249,40 @@ static void send_request(int fd, struct strbuf *buf) safe_write(fd, buf->buf, buf->len); } +static void insert_one_alternate_ref(const struct ref *ref, void *unused) +{ + rev_list_insert_ref(NULL, ref->old_sha1, 0, NULL); +} + +static void insert_alternate_refs(void) +{ + for_each_alternate_ref(insert_one_alternate_ref, NULL); +} + +#define INITIAL_FLUSH 16 +#define PIPESAFE_FLUSH 32 +#define LARGE_FLUSH 1024 + +static int next_flush(int count) +{ + int flush_limit = args.stateless_rpc ? LARGE_FLUSH : PIPESAFE_FLUSH; + + if (count < flush_limit) + count <<= 1; + else + count += flush_limit; + return count; +} + static int find_common(int fd[2], unsigned char *result_sha1, struct ref *refs) { int fetching; - int count = 0, flushes = 0, retval; + int count = 0, flushes = 0, flush_at = INITIAL_FLUSH, retval; const unsigned char *sha1; unsigned in_vain = 0; int got_continue = 0; + int got_ready = 0; struct strbuf req_buf = STRBUF_INIT; size_t state_len = 0; @@ -235,6 +293,7 @@ static int find_common(int fd[2], unsigned char *result_sha1, marked = 1; for_each_ref(rev_list_insert_ref, NULL); + insert_alternate_refs(); fetching = 0; for ( ; refs ; refs = refs->next) { @@ -262,6 +321,7 @@ static int find_common(int fd[2], unsigned char *result_sha1, struct strbuf c = STRBUF_INIT; if (multi_ack == 2) strbuf_addstr(&c, " multi_ack_detailed"); if (multi_ack == 1) strbuf_addstr(&c, " multi_ack"); + if (no_done) strbuf_addstr(&c, " no-done"); if (use_sideband == 2) strbuf_addstr(&c, " side-band-64k"); if (use_sideband == 1) strbuf_addstr(&c, " side-band"); if (args.use_thin_pack) strbuf_addstr(&c, " thin-pack"); @@ -332,19 +392,20 @@ static int find_common(int fd[2], unsigned char *result_sha1, if (args.verbose) fprintf(stderr, "have %s\n", sha1_to_hex(sha1)); in_vain++; - if (!(31 & ++count)) { + if (flush_at <= ++count) { int ack; packet_buf_flush(&req_buf); send_request(fd[1], &req_buf); strbuf_setlen(&req_buf, state_len); flushes++; + flush_at = next_flush(count); /* * We keep one window "ahead" of the other side, and * will wait for an ACK only on the next one */ - if (!args.stateless_rpc && count == 32) + if (!args.stateless_rpc && count == INITIAL_FLUSH) continue; consume_shallow_list(fd[0]); @@ -364,6 +425,8 @@ static int find_common(int fd[2], unsigned char *result_sha1, case ACK_continue: { struct commit *commit = lookup_commit(result_sha1); + if (!commit) + die("invalid commit %s", sha1_to_hex(result_sha1)); if (args.stateless_rpc && ack == ACK_common && !(commit->object.flags & COMMON)) { @@ -379,6 +442,10 @@ static int find_common(int fd[2], unsigned char *result_sha1, retval = 0; in_vain = 0; got_continue = 1; + if (ack == ACK_ready) { + rev_list = NULL; + got_ready = 1; + } break; } } @@ -392,8 +459,10 @@ static int find_common(int fd[2], unsigned char *result_sha1, } } done: - packet_buf_write(&req_buf, "done\n"); - send_request(fd[1], &req_buf); + if (!got_ready || !no_done) { + packet_buf_write(&req_buf, "done\n"); + send_request(fd[1], &req_buf); + } if (args.verbose) fprintf(stderr, "done\n"); if (retval != 0) { @@ -435,8 +504,10 @@ static int mark_complete(const char *path, const unsigned char *sha1, int flag, } if (o && o->type == OBJ_COMMIT) { struct commit *commit = (struct commit *)o; - commit->object.flags |= COMPLETE; - insert_by_date(commit, &complete); + if (!(commit->object.flags & COMPLETE)) { + commit->object.flags |= COMPLETE; + commit_list_insert_by_date(commit, &complete); + } } return 0; } @@ -696,6 +767,12 @@ static struct ref *do_fetch_pack(int fd[2], 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) @@ -804,6 +881,8 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix) char **pack_lockfile_ptr = NULL; struct child_process *conn; + packet_trace_identity("fetch-pack"); + nr_heads = 0; heads = NULL; for (i = 1; i < argc; i++) { diff --git a/builtin/fetch.c b/builtin/fetch.c index 6eb1dfea09..93c99385a9 100644 --- a/builtin/fetch.c +++ b/builtin/fetch.c @@ -12,11 +12,12 @@ #include "parse-options.h" #include "sigchain.h" #include "transport.h" +#include "submodule.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 --multiple [<options>] [(<repository> | <group>)...]", "git fetch --all [<options>]", NULL }; @@ -28,12 +29,28 @@ enum { }; static int all, append, dry_run, force, keep, multiple, prune, update_head_ok, verbosity; -static int progress; +static int progress, recurse_submodules = RECURSE_SUBMODULES_DEFAULT; static int tags = TAGS_DEFAULT; static const char *depth; static const char *upload_pack; static struct strbuf default_rla = STRBUF_INIT; static struct transport *transport; +static const char *submodule_prefix = ""; +static const char *recurse_submodules_default; + +static int option_parse_recurse_submodules(const struct option *opt, + const char *arg, int unset) +{ + if (unset) { + recurse_submodules = RECURSE_SUBMODULES_OFF; + } else { + if (arg) + recurse_submodules = parse_fetch_recurse_submodules_arg(opt->long_name, arg); + else + recurse_submodules = RECURSE_SUBMODULES_ON; + } + return 0; +} static struct option builtin_fetch_options[] = { OPT__VERBOSITY(&verbosity), @@ -41,10 +58,9 @@ static struct option builtin_fetch_options[] = { "fetch from all remotes"), OPT_BOOLEAN('a', "append", &append, "append to .git/FETCH_HEAD instead of overwriting"), - OPT_STRING(0, "upload-pack", &upload_pack, "PATH", + OPT_STRING(0, "upload-pack", &upload_pack, "path", "path to upload pack on remote end"), - OPT_BOOLEAN('f', "force", &force, - "force overwrite of local branch"), + OPT__FORCE(&force, "force overwrite of local branch"), OPT_BOOLEAN('m', "multiple", &multiple, "fetch from multiple remotes"), OPT_SET_INT('t', "tags", &tags, @@ -52,15 +68,23 @@ static struct option builtin_fetch_options[] = { OPT_SET_INT('n', NULL, &tags, "do not fetch all tags (--no-tags)", TAGS_UNSET), OPT_BOOLEAN('p', "prune", &prune, - "prune tracking branches no longer on remote"), + "prune remote-tracking branches no longer on remote"), + { OPTION_CALLBACK, 0, "recurse-submodules", NULL, "on-demand", + "control recursive fetching of submodules", + PARSE_OPT_OPTARG, option_parse_recurse_submodules }, OPT_BOOLEAN(0, "dry-run", &dry_run, "dry run"), OPT_BOOLEAN('k', "keep", &keep, "keep downloaded pack"), OPT_BOOLEAN('u', "update-head-ok", &update_head_ok, "allow updating of HEAD ref"), OPT_BOOLEAN(0, "progress", &progress, "force progress reporting"), - OPT_STRING(0, "depth", &depth, "DEPTH", + OPT_STRING(0, "depth", &depth, "depth", "deepen history of shallow clone"), + { OPTION_STRING, 0, "submodule-prefix", &submodule_prefix, "dir", + "prepend this to submodule path output", PARSE_OPT_HIDDEN }, + { OPTION_STRING, 0, "recurse-submodules-default", + &recurse_submodules_default, NULL, + "default mode for recursion", PARSE_OPT_HIDDEN }, OPT_END() }; @@ -98,7 +122,7 @@ static void add_merge_config(struct ref **head, continue; /* - * Not fetched to a tracking branch? We need to fetch + * Not fetched to a remote-tracking branch? We need to fetch * it anyway to allow this branch's "branch.$name.merge" * to be honored by 'git pull', but we do not have to * fail if branch.$name.merge is misconfigured to point @@ -146,7 +170,10 @@ static struct ref *get_ref_map(struct transport *transport, struct remote *remote = transport->remote; struct branch *branch = branch_get(NULL); int has_merge = branch_has_merge_config(branch); - if (remote && (remote->fetch_refspec_nr || has_merge)) { + if (remote && + (remote->fetch_refspec_nr || + /* Note: has_merge implies non-NULL branch->remote_name */ + (has_merge && !strcmp(branch->remote_name, remote->name)))) { for (i = 0; i < remote->fetch_refspec_nr; i++) { get_fetch_map(remote_refs, &remote->fetch[i], &tail, 0); if (remote->fetch[i].dst && @@ -160,6 +187,8 @@ static struct ref *get_ref_map(struct transport *transport, * if the remote we're fetching from is the same * as given in branch.<name>.remote, we add the * ref given in branch.<name>.merge, too. + * + * Note: has_merge implies non-NULL branch->remote_name */ if (has_merge && !strcmp(branch->remote_name, remote->name)) @@ -167,7 +196,7 @@ static struct ref *get_ref_map(struct transport *transport, } else { ref_map = get_remote_ref(remote_refs, "HEAD"); if (!ref_map) - die("Couldn't find remote ref HEAD"); + die(_("Couldn't find remote ref HEAD")); ref_map->merge = 1; tail = &ref_map->next; } @@ -220,12 +249,12 @@ static int update_local_ref(struct ref *ref, *display = 0; type = sha1_object_info(ref->new_sha1, NULL); if (type < 0) - die("object %s not found", sha1_to_hex(ref->new_sha1)); + die(_("object %s not found"), sha1_to_hex(ref->new_sha1)); if (!hashcmp(ref->old_sha1, ref->new_sha1)) { if (verbosity > 0) sprintf(display, "= %-*s %-*s -> %s", TRANSPORT_SUMMARY_WIDTH, - "[up to date]", REFCOL_WIDTH, remote, + _("[up to date]"), REFCOL_WIDTH, remote, pretty_ref); return 0; } @@ -238,8 +267,8 @@ static int update_local_ref(struct ref *ref, * If this is the head, and it's not okay to update * the head, and the old value of the head isn't empty... */ - sprintf(display, "! %-*s %-*s -> %s (can't fetch in current branch)", - TRANSPORT_SUMMARY_WIDTH, "[rejected]", REFCOL_WIDTH, remote, + sprintf(display, _("! %-*s %-*s -> %s (can't fetch in current branch)"), + TRANSPORT_SUMMARY_WIDTH, _("[rejected]"), REFCOL_WIDTH, remote, pretty_ref); return 1; } @@ -249,8 +278,8 @@ static int update_local_ref(struct ref *ref, int r; r = s_update_ref("updating tag", ref, 0); sprintf(display, "%c %-*s %-*s -> %s%s", r ? '!' : '-', - TRANSPORT_SUMMARY_WIDTH, "[tag update]", REFCOL_WIDTH, remote, - pretty_ref, r ? " (unable to update local ref)" : ""); + TRANSPORT_SUMMARY_WIDTH, _("[tag update]"), REFCOL_WIDTH, remote, + pretty_ref, r ? _(" (unable to update local ref)") : ""); return r; } @@ -262,17 +291,20 @@ static int update_local_ref(struct ref *ref, int r; if (!strncmp(ref->name, "refs/tags/", 10)) { msg = "storing tag"; - what = "[new tag]"; + what = _("[new tag]"); } else { msg = "storing head"; - what = "[new branch]"; + what = _("[new branch]"); + if ((recurse_submodules != RECURSE_SUBMODULES_OFF) && + (recurse_submodules != RECURSE_SUBMODULES_ON)) + check_for_new_submodule_commits(ref->new_sha1); } r = s_update_ref(msg, ref, 0); sprintf(display, "%c %-*s %-*s -> %s%s", r ? '!' : '*', TRANSPORT_SUMMARY_WIDTH, what, REFCOL_WIDTH, remote, pretty_ref, - r ? " (unable to update local ref)" : ""); + r ? _(" (unable to update local ref)") : ""); return r; } @@ -282,10 +314,13 @@ static int update_local_ref(struct ref *ref, strcpy(quickref, find_unique_abbrev(current->object.sha1, DEFAULT_ABBREV)); strcat(quickref, ".."); strcat(quickref, find_unique_abbrev(ref->new_sha1, DEFAULT_ABBREV)); + if ((recurse_submodules != RECURSE_SUBMODULES_OFF) && + (recurse_submodules != RECURSE_SUBMODULES_ON)) + check_for_new_submodule_commits(ref->new_sha1); r = s_update_ref("fast-forward", ref, 1); sprintf(display, "%c %-*s %-*s -> %s%s", r ? '!' : ' ', TRANSPORT_SUMMARY_WIDTH, quickref, REFCOL_WIDTH, remote, - pretty_ref, r ? " (unable to update local ref)" : ""); + pretty_ref, r ? _(" (unable to update local ref)") : ""); return r; } else if (force || ref->force) { char quickref[84]; @@ -293,16 +328,19 @@ static int update_local_ref(struct ref *ref, strcpy(quickref, find_unique_abbrev(current->object.sha1, DEFAULT_ABBREV)); strcat(quickref, "..."); strcat(quickref, find_unique_abbrev(ref->new_sha1, DEFAULT_ABBREV)); + if ((recurse_submodules != RECURSE_SUBMODULES_OFF) && + (recurse_submodules != RECURSE_SUBMODULES_ON)) + check_for_new_submodule_commits(ref->new_sha1); r = s_update_ref("forced-update", ref, 1); sprintf(display, "%c %-*s %-*s -> %s (%s)", r ? '!' : '+', TRANSPORT_SUMMARY_WIDTH, quickref, REFCOL_WIDTH, remote, pretty_ref, - r ? "unable to update local ref" : "forced update"); + r ? _("unable to update local ref") : _("forced update")); return r; } else { - sprintf(display, "! %-*s %-*s -> %s (non-fast-forward)", - TRANSPORT_SUMMARY_WIDTH, "[rejected]", REFCOL_WIDTH, remote, - pretty_ref); + sprintf(display, "! %-*s %-*s -> %s %s", + TRANSPORT_SUMMARY_WIDTH, _("[rejected]"), REFCOL_WIDTH, remote, + pretty_ref, _("(non-fast-forward)")); return 1; } } @@ -320,7 +358,7 @@ static int store_updated_refs(const char *raw_url, const char *remote_name, fp = fopen(filename, "a"); if (!fp) - return error("cannot open %s: %s\n", filename, strerror(errno)); + return error(_("cannot open %s: %s\n"), filename, strerror(errno)); if (raw_url) url = transport_anonymize_url(raw_url); @@ -354,7 +392,7 @@ static int store_updated_refs(const char *raw_url, const char *remote_name, what = rm->name + 10; } else if (!prefixcmp(rm->name, "refs/remotes/")) { - kind = "remote branch"; + kind = "remote-tracking branch"; what = rm->name + 13; } else { @@ -398,7 +436,7 @@ static int store_updated_refs(const char *raw_url, const char *remote_name, REFCOL_WIDTH, *what ? what : "HEAD"); if (*note) { if (verbosity >= 0 && !shown_url) { - fprintf(stderr, "From %.*s\n", + fprintf(stderr, _("From %.*s\n"), url_len, url); shown_url = 1; } @@ -409,9 +447,9 @@ static int store_updated_refs(const char *raw_url, const char *remote_name, free(url); fclose(fp); if (rc & STORE_REF_ERROR_DF_CONFLICT) - error("some local refs could not be updated; try running\n" + error(_("some local refs could not be updated; try running\n" " 'git remote prune %s' to remove any old, conflicting " - "branches", remote_name); + "branches"), remote_name); return rc; } @@ -459,7 +497,7 @@ static int quickfetch(struct ref *ref_map) err = start_command(&revlist); if (err) { - error("could not run rev-list"); + error(_("could not run rev-list")); return err; } @@ -473,14 +511,14 @@ static int quickfetch(struct ref *ref_map) if (write_in_full(revlist.in, sha1_to_hex(ref->old_sha1), 40) < 0 || write_str_in_full(revlist.in, "\n") < 0) { if (errno != EPIPE && errno != EINVAL) - error("failed write to rev-list: %s", strerror(errno)); + error(_("failed write to rev-list: %s"), strerror(errno)); err = -1; break; } } if (close(revlist.in)) { - error("failed to close rev-list's stdin: %s", strerror(errno)); + error(_("failed to close rev-list's stdin: %s"), strerror(errno)); err = -1; } @@ -507,16 +545,16 @@ static int prune_refs(struct transport *transport, struct ref *ref_map) int result = 0; struct ref *ref, *stale_refs = get_stale_heads(transport->remote, ref_map); const char *dangling_msg = dry_run - ? " (%s will become dangling)\n" - : " (%s has become dangling)\n"; + ? _(" (%s will become dangling)\n") + : _(" (%s has become dangling)\n"); for (ref = stale_refs; ref; ref = ref->next) { if (!dry_run) result |= delete_ref(ref->name, NULL, 0); if (verbosity >= 0) { fprintf(stderr, " x %-*s %-*s -> %s\n", - TRANSPORT_SUMMARY_WIDTH, "[deleted]", - REFCOL_WIDTH, "(none)", prettify_refname(ref->name)); + TRANSPORT_SUMMARY_WIDTH, _("[deleted]"), + REFCOL_WIDTH, _("(none)"), prettify_refname(ref->name)); warn_dangling_symref(stderr, dangling_msg, ref->name); } } @@ -544,40 +582,14 @@ static int will_fetch(struct ref **head, const unsigned char *sha1) return 0; } -struct tag_data { - struct ref **head; - struct ref ***tail; -}; - -static int add_to_tail(struct string_list_item *item, void *cb_data) -{ - struct tag_data *data = (struct tag_data *)cb_data; - struct ref *rm = NULL; - - /* We have already decided to ignore this item */ - if (!item->util) - return 0; - - rm = alloc_ref(item->string); - rm->peer_ref = alloc_ref(item->string); - hashcpy(rm->old_sha1, item->util); - - **data->tail = rm; - *data->tail = &rm->next; - - return 0; -} - static void find_non_local_tags(struct transport *transport, struct ref **head, struct ref ***tail) { - struct string_list existing_refs = { NULL, 0, 0, 0 }; - struct string_list remote_refs = { NULL, 0, 0, 0 }; - struct tag_data data; + struct string_list existing_refs = STRING_LIST_INIT_NODUP; + struct string_list remote_refs = STRING_LIST_INIT_NODUP; const struct ref *ref; struct string_list_item *item = NULL; - data.head = head; data.tail = tail; for_each_ref(add_existing, &existing_refs); for (ref = transport_get_remote_refs(transport); ref; ref = ref->next) { @@ -631,10 +643,20 @@ static void find_non_local_tags(struct transport *transport, item->util = NULL; /* - * For all the tags in the remote_refs string list, call - * add_to_tail to add them to the list of refs to be fetched + * For all the tags in the remote_refs string list, + * add them to the list of refs to be fetched */ - for_each_string_list(&remote_refs, add_to_tail, &data); + for_each_string_list_item(item, &remote_refs) { + /* Unless we have already decided to ignore this item... */ + if (item->util) + { + struct ref *rm = alloc_ref(item->string); + rm->peer_ref = alloc_ref(item->string); + hashcpy(rm->old_sha1, item->util); + **tail = rm; + *tail = &rm->next; + } + } string_list_clear(&remote_refs, 0); } @@ -649,8 +671,8 @@ static void check_not_current_branch(struct ref *ref_map) for (; ref_map; ref_map = ref_map->next) if (ref_map->peer_ref && !strcmp(current_branch->refname, ref_map->peer_ref->name)) - die("Refusing to fetch into current branch %s " - "of non-bare repository", current_branch->refname); + die(_("Refusing to fetch into current branch %s " + "of non-bare repository"), current_branch->refname); } static int truncate_fetch_head(void) @@ -659,7 +681,7 @@ static int truncate_fetch_head(void) FILE *fp = fopen(filename, "w"); if (!fp) - return error("cannot open %s: %s\n", filename, strerror(errno)); + return error(_("cannot open %s: %s\n"), filename, strerror(errno)); fclose(fp); return 0; } @@ -667,7 +689,7 @@ static int truncate_fetch_head(void) static int do_fetch(struct transport *transport, struct refspec *refs, int ref_count) { - struct string_list existing_refs = { NULL, 0, 0, 0 }; + struct string_list existing_refs = STRING_LIST_INIT_NODUP; struct string_list_item *peer_item = NULL; struct ref *ref_map; struct ref *rm; @@ -675,13 +697,15 @@ static int do_fetch(struct transport *transport, for_each_ref(add_existing, &existing_refs); - if (transport->remote->fetch_tags == 2 && tags != TAGS_UNSET) - tags = TAGS_SET; - if (transport->remote->fetch_tags == -1) - tags = TAGS_UNSET; + if (tags == TAGS_DEFAULT) { + if (transport->remote->fetch_tags == 2) + tags = TAGS_SET; + if (transport->remote->fetch_tags == -1) + tags = TAGS_UNSET; + } if (!transport->get_refs_list || !transport->fetch) - die("Don't know how to fetch from %s", transport->url); + die(_("Don't know how to fetch from %s"), transport->url); /* if not appending, truncate FETCH_HEAD */ if (!append && !dry_run) { @@ -735,10 +759,10 @@ static void set_option(const char *name, const char *value) { int r = transport_set_option(transport, name, value); if (r < 0) - die("Option \"%s\" value \"%s\" is not valid for %s", + die(_("Option \"%s\" value \"%s\" is not valid for %s"), name, value, transport->url); if (r > 0) - warning("Option \"%s\" is ignored for %s\n", + warning(_("Option \"%s\" is ignored for %s\n"), name, transport->url); } @@ -793,28 +817,38 @@ static int add_remote_or_group(const char *name, struct string_list *list) return 1; } -static int fetch_multiple(struct string_list *list) +static void add_options_to_argv(int *argc, const char **argv) { - int i, result = 0; - const char *argv[11] = { "fetch", "--append" }; - int argc = 2; - if (dry_run) - argv[argc++] = "--dry-run"; + argv[(*argc)++] = "--dry-run"; if (prune) - argv[argc++] = "--prune"; + argv[(*argc)++] = "--prune"; if (update_head_ok) - argv[argc++] = "--update-head-ok"; + argv[(*argc)++] = "--update-head-ok"; if (force) - argv[argc++] = "--force"; + argv[(*argc)++] = "--force"; if (keep) - argv[argc++] = "--keep"; + argv[(*argc)++] = "--keep"; + if (recurse_submodules == RECURSE_SUBMODULES_ON) + argv[(*argc)++] = "--recurse-submodules"; + else if (recurse_submodules == RECURSE_SUBMODULES_ON_DEMAND) + argv[(*argc)++] = "--recurse-submodules=on-demand"; if (verbosity >= 2) - argv[argc++] = "-v"; + argv[(*argc)++] = "-v"; if (verbosity >= 1) - argv[argc++] = "-v"; + argv[(*argc)++] = "-v"; else if (verbosity < 0) - argv[argc++] = "-q"; + argv[(*argc)++] = "-q"; + +} + +static int fetch_multiple(struct string_list *list) +{ + int i, result = 0; + const char *argv[12] = { "fetch", "--append" }; + int argc = 2; + + add_options_to_argv(&argc, argv); if (!append && !dry_run) { int errcode = truncate_fetch_head(); @@ -827,9 +861,9 @@ static int fetch_multiple(struct string_list *list) argv[argc] = name; argv[argc + 1] = NULL; if (verbosity >= 0) - printf("Fetching %s\n", name); + printf(_("Fetching %s\n"), name); if (run_command_v_opt(argv, RUN_GIT_CMD)) { - error("Could not fetch %s", name); + error(_("Could not fetch %s"), name); result = 1; } } @@ -841,11 +875,13 @@ static int fetch_one(struct remote *remote, int argc, const char **argv) { int i; static const char **refs = NULL; + struct refspec *refspec; int ref_nr = 0; int exit_code; if (!remote) - die("Where do you want to fetch from today?"); + die(_("No remote repository specified. Please, specify either a URL or a\n" + "remote name from which new revisions should be fetched.")); transport = transport_get(remote, NULL); transport_set_verbosity(transport, verbosity, progress); @@ -864,7 +900,7 @@ static int fetch_one(struct remote *remote, int argc, const char **argv) char *ref; i++; if (i >= argc) - die("You need to specify a tag name."); + die(_("You need to specify a tag name.")); ref = xmalloc(strlen(argv[i]) * 2 + 22); strcpy(ref, "refs/tags/"); strcat(ref, argv[i]); @@ -880,8 +916,9 @@ static int fetch_one(struct remote *remote, int argc, const char **argv) sigchain_push_common(unlock_pack_on_signal); atexit(unlock_pack); - exit_code = do_fetch(transport, - parse_fetch_refspec(ref_nr, refs), ref_nr); + refspec = parse_fetch_refspec(ref_nr, refs); + exit_code = do_fetch(transport, refspec, ref_nr); + free(refspec); transport_disconnect(transport); transport = NULL; return exit_code; @@ -890,10 +927,12 @@ static int fetch_one(struct remote *remote, int argc, const char **argv) int cmd_fetch(int argc, const char **argv, const char *prefix) { int i; - struct string_list list = { NULL, 0, 0, 0 }; + struct string_list list = STRING_LIST_INIT_NODUP; struct remote *remote; int result = 0; + packet_trace_identity("fetch"); + /* Record the command line for the reflog */ strbuf_addstr(&default_rla, "fetch"); for (i = 1; i < argc; i++) @@ -904,9 +943,9 @@ int cmd_fetch(int argc, const char **argv, const char *prefix) if (all) { if (argc == 1) - die("fetch --all does not take a repository argument"); + die(_("fetch --all does not take a repository argument")); else if (argc > 1) - die("fetch --all does not make sense with refspecs"); + die(_("fetch --all does not make sense with refspecs")); (void) for_each_remote(get_one_remote_for_fetch, &list); result = fetch_multiple(&list); } else if (argc == 0) { @@ -917,7 +956,7 @@ int cmd_fetch(int argc, const char **argv, const char *prefix) /* All arguments are assumed to be remotes or groups */ for (i = 0; i < argc; i++) if (!add_remote_or_group(argv[i], &list)) - die("No such remote or remote group: %s", argv[i]); + die(_("No such remote or remote group: %s"), argv[i]); result = fetch_multiple(&list); } else { /* Single remote or group */ @@ -925,7 +964,7 @@ int cmd_fetch(int argc, const char **argv, const char *prefix) if (list.nr > 1) { /* More than one remote */ if (argc > 1) - die("Fetching a group and specifying refspecs does not make sense"); + die(_("Fetching a group and specifying refspecs does not make sense")); result = fetch_multiple(&list); } else { /* Zero or one remotes */ @@ -934,6 +973,22 @@ 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; + if (recurse_submodules_default) { + int arg = parse_fetch_recurse_submodules_arg("--recurse-submodules-default", recurse_submodules_default); + set_config_fetch_recurse_submodules(arg); + } + gitmodules_config(); + git_config(submodule_config, NULL); + add_options_to_argv(&num_options, options); + result = fetch_populated_submodules(num_options, options, + submodule_prefix, + recurse_submodules, + verbosity < 0); + } + /* All names were strdup()ed or strndup()ed */ list.strdup_strings = 1; string_list_clear(&list, 0); diff --git a/builtin/fmt-merge-msg.c b/builtin/fmt-merge-msg.c index bc3c5e6d3e..75816329d6 100644 --- a/builtin/fmt-merge-msg.c +++ b/builtin/fmt-merge-msg.c @@ -7,21 +7,22 @@ #include "string-list.h" static const char * const fmt_merge_msg_usage[] = { - "git fmt-merge-msg [--log|--no-log] [--file <file>]", + "git fmt-merge-msg [-m <message>] [--log[=<n>]|--no-log] [--file <file>]", NULL }; -static int merge_summary; +static int shortlog_len; static int fmt_merge_msg_config(const char *key, const char *value, void *cb) { - static int found_merge_log = 0; - if (!strcmp("merge.log", key)) { - found_merge_log = 1; - merge_summary = git_config_bool(key, value); + if (!strcmp(key, "merge.log") || !strcmp(key, "merge.summary")) { + int is_bool; + shortlog_len = git_config_bool_or_int(key, value, &is_bool); + if (!is_bool && shortlog_len < 0) + return error("%s: negative length %s", key, value); + if (is_bool && shortlog_len) + shortlog_len = DEFAULT_MERGE_LOG_LEN; } - if (!found_merge_log && !strcmp("merge.summary", key)) - merge_summary = git_config_bool(key, value); return 0; } @@ -30,7 +31,7 @@ struct src_data { int head_status; }; -void init_src_data(struct src_data *data) +static void init_src_data(struct src_data *data) { data->branch.strdup_strings = 1; data->tag.strdup_strings = 1; @@ -38,8 +39,8 @@ void init_src_data(struct src_data *data) data->generic.strdup_strings = 1; } -static struct string_list srcs = { NULL, 0, 0, 1 }; -static struct string_list origins = { NULL, 0, 0, 1 }; +static struct string_list srcs = STRING_LIST_INIT_DUP; +static struct string_list origins = STRING_LIST_INIT_DUP; static int handle_line(char *line) { @@ -99,8 +100,8 @@ static int handle_line(char *line) origin = line; string_list_append(&src_data->tag, origin + 4); src_data->head_status |= 2; - } else if (!prefixcmp(line, "remote branch ")) { - origin = line + 14; + } else if (!prefixcmp(line, "remote-tracking branch ")) { + origin = line + strlen("remote-tracking branch "); string_list_append(&src_data->r_branch, origin); src_data->head_status |= 2; } else { @@ -146,7 +147,7 @@ static void shortlog(const char *name, unsigned char *sha1, int i, count = 0; struct commit *commit; struct object *branch; - struct string_list subjects = { NULL, 0, 0, 1 }; + struct string_list subjects = STRING_LIST_INIT_DUP; int flags = UNINTERESTING | TREESAME | SEEN | SHOWN | ADDED; struct strbuf sb = STRBUF_INIT; @@ -232,7 +233,7 @@ static void do_fmt_merge_msg_title(struct strbuf *out, if (src_data->r_branch.nr) { strbuf_addstr(out, subsep); subsep = ", "; - print_joined("remote branch ", "remote branches ", + print_joined("remote-tracking branch ", "remote-tracking branches ", &src_data->r_branch, out); } if (src_data->tag.nr) { @@ -255,9 +256,9 @@ static void do_fmt_merge_msg_title(struct strbuf *out, strbuf_addf(out, " into %s\n", current_branch); } -static int do_fmt_merge_msg(int merge_title, int merge_summary, - struct strbuf *in, struct strbuf *out) { - int limit = 20, i = 0, pos = 0; +static int do_fmt_merge_msg(int merge_title, struct strbuf *in, + struct strbuf *out, int shortlog_len) { + int i = 0, pos = 0; unsigned char head_sha1[20]; const char *current_branch; @@ -288,7 +289,7 @@ static int do_fmt_merge_msg(int merge_title, int merge_summary, if (merge_title) do_fmt_merge_msg_title(out, current_branch); - if (merge_summary) { + if (shortlog_len) { struct commit *head; struct rev_info rev; @@ -303,27 +304,30 @@ static int do_fmt_merge_msg(int merge_title, int merge_summary, for (i = 0; i < origins.nr; i++) shortlog(origins.items[i].string, origins.items[i].util, - head, &rev, limit, out); + head, &rev, shortlog_len, out); } return 0; } -int fmt_merge_msg(int merge_summary, struct strbuf *in, struct strbuf *out) { - return do_fmt_merge_msg(1, merge_summary, in, out); -} - -int fmt_merge_msg_shortlog(struct strbuf *in, struct strbuf *out) { - return do_fmt_merge_msg(0, 1, in, out); +int fmt_merge_msg(struct strbuf *in, struct strbuf *out, + int merge_title, int shortlog_len) { + return do_fmt_merge_msg(merge_title, in, out, shortlog_len); } int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix) { const char *inpath = NULL; + const char *message = NULL; struct option options[] = { - OPT_BOOLEAN(0, "log", &merge_summary, "populate log with the shortlog"), - { OPTION_BOOLEAN, 0, "summary", &merge_summary, NULL, + { OPTION_INTEGER, 0, "log", &shortlog_len, "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)", - PARSE_OPT_NOARG | PARSE_OPT_HIDDEN }, + 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_END() }; @@ -337,6 +341,14 @@ int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix) 0); if (argc > 0) usage_with_options(fmt_merge_msg_usage, options); + if (message && !shortlog_len) { + char nl = '\n'; + write_in_full(STDOUT_FILENO, message, strlen(message)); + write_in_full(STDOUT_FILENO, &nl, 1); + return 0; + } + if (shortlog_len < 0) + die("Negative --log=%d", shortlog_len); if (inpath && strcmp(inpath, "-")) { in = fopen(inpath, "r"); @@ -346,7 +358,13 @@ int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix) if (strbuf_read(&input, fileno(in), 0) < 0) die_errno("could not read input file"); - ret = fmt_merge_msg(merge_summary, &input, &output); + + if (message) + strbuf_addstr(&output, message); + ret = fmt_merge_msg(&input, &output, + message ? 0 : 1, + shortlog_len); + if (ret) return ret; write_in_full(STDOUT_FILENO, output.buf, output.len); diff --git a/builtin/for-each-ref.c b/builtin/for-each-ref.c index a2b28c6962..89e75c6894 100644 --- a/builtin/for-each-ref.c +++ b/builtin/for-each-ref.c @@ -228,7 +228,8 @@ static void grab_common_values(struct atom_value *val, int deref, struct object v->s = s; } else if (!strcmp(name, "objectname:short")) { - v->s = find_unique_abbrev(obj->sha1, DEFAULT_ABBREV); + v->s = xstrdup(find_unique_abbrev(obj->sha1, + DEFAULT_ABBREV)); } } } diff --git a/builtin/fsck.c b/builtin/fsck.c index 0929c7f245..5ae0366bc8 100644 --- a/builtin/fsck.c +++ b/builtin/fsck.c @@ -74,7 +74,13 @@ static int mark_object(struct object *obj, int type, void *data) { struct object *parent = data; + /* + * The only case data is NULL or type is OBJ_ANY is when + * mark_object_reachable() calls us. All the callers of + * that function has non-NULL obj hence ... + */ if (!obj) { + /* ... these references to parent->fld are safe here */ printf("broken link from %7s %s\n", typename(parent->type), sha1_to_hex(parent->sha1)); printf("broken link from %7s %s\n", @@ -84,6 +90,7 @@ static int mark_object(struct object *obj, int type, void *data) } if (type != OBJ_ANY && obj->type != type) + /* ... and the reference to parent is safe here */ objerror(parent, "wrong object type in link"); if (obj->flags & REACHABLE) @@ -109,7 +116,7 @@ static void mark_object_reachable(struct object *obj) mark_object(obj, OBJ_ANY, NULL); } -static int traverse_one_object(struct object *obj, struct object *parent) +static int traverse_one_object(struct object *obj) { int result; struct tree *tree = NULL; @@ -133,12 +140,11 @@ static int traverse_reachable(void) int result = 0; while (pending.nr) { struct object_array_entry *entry; - struct object *obj, *parent; + struct object *obj; entry = pending.objects + --pending.nr; obj = entry->item; - parent = (struct object *) entry->name; - result |= traverse_one_object(obj, parent); + result |= traverse_one_object(obj); } return !!result; } @@ -385,10 +391,20 @@ static void add_sha1_list(unsigned char *sha1, unsigned long ino) sha1_list.nr = ++nr; } +static inline int is_loose_object_file(struct dirent *de, + char *name, unsigned char *sha1) +{ + if (strlen(de->d_name) != 38) + return 0; + memcpy(name + 2, de->d_name, 39); + return !get_sha1_hex(name, sha1); +} + static void fsck_dir(int i, char *path) { DIR *dir = opendir(path); struct dirent *de; + char name[100]; if (!dir) return; @@ -396,17 +412,13 @@ static void fsck_dir(int i, char *path) if (verbose) fprintf(stderr, "Checking directory %s\n", path); + sprintf(name, "%02x", i); while ((de = readdir(dir)) != NULL) { - char name[100]; unsigned char sha1[20]; if (is_dot_or_dotdot(de->d_name)) continue; - if (strlen(de->d_name) == 38) { - sprintf(name, "%02x", i); - memcpy(name+2, de->d_name, 39); - if (get_sha1_hex(name, sha1) < 0) - break; + if (is_loose_object_file(de, name, sha1)) { add_sha1_list(sha1, DIRENT_SORT_HINT(de)); continue; } @@ -556,8 +568,8 @@ static int fsck_cache_tree(struct cache_tree *it) sha1_to_hex(it->sha1)); return 1; } - mark_object_reachable(obj); obj->used = 1; + mark_object_reachable(obj); if (obj->type != OBJ_TREE) err |= objerror(obj, "non-tree in cache-tree"); } @@ -572,7 +584,7 @@ static char const * const fsck_usage[] = { }; static struct option fsck_opts[] = { - OPT__VERBOSE(&verbose), + OPT__VERBOSE(&verbose, "be verbose"), OPT_BOOLEAN(0, "unreachable", &show_unreachable, "show unreachable objects"), OPT_BOOLEAN(0, "tags", &show_tags, "report tags"), OPT_BOOLEAN(0, "root", &show_root, "report root nodes"), diff --git a/builtin/gc.c b/builtin/gc.c index c304638b78..0498094711 100644 --- a/builtin/gc.c +++ b/builtin/gc.c @@ -60,7 +60,7 @@ static int gc_config(const char *var, const char *value, void *cb) if (value && strcmp(value, "now")) { unsigned long now = approxidate("now"); if (approxidate(value) >= now) - return error("Invalid %s: '%s'", var, value); + return error(_("Invalid %s: '%s'"), var, value); } return git_config_string(&prune_expire, var, value); } @@ -75,7 +75,7 @@ static void append_option(const char **cmd, const char *opt, int max_length) ; if (i + 2 >= max_length) - die("Too many options specified"); + die(_("Too many options specified")); cmd[i++] = opt; cmd[i] = NULL; } @@ -100,7 +100,7 @@ static int too_many_loose_objects(void) return 0; if (sizeof(path) <= snprintf(path, sizeof(path), "%s/17", objdir)) { - warning("insanely long object directory %.*s", 50, objdir); + warning(_("insanely long object directory %.*s"), 50, objdir); return 0; } dir = opendir(path); @@ -180,7 +180,7 @@ int cmd_gc(int argc, const char **argv, const char *prefix) char buf[80]; struct option builtin_gc_options[] = { - OPT__QUIET(&quiet), + OPT__QUIET(&quiet, "suppress progress reporting"), { OPTION_STRING, 0, "prune", &prune_expire, "date", "prune unreferenced objects", PARSE_OPT_OPTARG, NULL, (intptr_t)prune_expire }, @@ -189,6 +189,9 @@ int cmd_gc(int argc, const char **argv, const char *prefix) OPT_END() }; + if (argc == 2 && !strcmp(argv[1], "-h")) + usage_with_options(builtin_gc_usage, builtin_gc_options); + git_config(gc_config, NULL); if (pack_refs < 0) @@ -216,13 +219,13 @@ int cmd_gc(int argc, const char **argv, const char *prefix) */ if (!need_to_gc()) return 0; - fprintf(stderr, - "Auto packing the repository for optimum performance.%s\n", - quiet - ? "" - : (" You may also\n" - "run \"git gc\" manually. See " - "\"git help gc\" for more information.")); + if (quiet) + fprintf(stderr, _("Auto packing the repository for optimum performance.\n")); + else + fprintf(stderr, + _("Auto packing the repository for optimum performance. You may also\n" + "run \"git gc\" manually. See " + "\"git help gc\" for more information.\n")); } else append_option(argv_repack, prune_expire && !strcmp(prune_expire, "now") @@ -248,8 +251,8 @@ int cmd_gc(int argc, const char **argv, const char *prefix) return error(FAILED_RUN, argv_rerere[0]); if (auto_gc && too_many_loose_objects()) - warning("There are too many unreachable loose objects; " - "run 'git prune' to remove them."); + warning(_("There are too many unreachable loose objects; " + "run 'git prune' to remove them.")); return 0; } diff --git a/builtin/grep.c b/builtin/grep.c index 597f76bc42..1c359c2671 100644 --- a/builtin/grep.c +++ b/builtin/grep.c @@ -17,14 +17,10 @@ #include "grep.h" #include "quote.h" #include "dir.h" - -#ifndef NO_PTHREADS -#include <pthread.h> #include "thread-utils.h" -#endif static char const * const grep_usage[] = { - "git grep [options] [-e] <pattern> [<rev>...] [[--] path...]", + "git grep [options] [-e] <pattern> [<rev>...] [[--] <path>...]", NULL }; @@ -44,8 +40,7 @@ enum work_type {WORK_SHA1, WORK_FILE}; * threads. The producer adds struct work_items to 'todo' and the * consumers pick work items from the same array. */ -struct work_item -{ +struct work_item { enum work_type type; char *name; @@ -98,8 +93,7 @@ static pthread_cond_t cond_write; /* Signalled when we are finished with everything. */ static pthread_cond_t cond_result; -static int print_hunk_marks_between_files; -static int printed_something; +static int skip_first_line; static void add_work(enum work_type type, char *name, void *id) { @@ -165,10 +159,20 @@ static void work_done(struct work_item *w) todo_done = (todo_done+1) % ARRAY_SIZE(todo)) { w = &todo[todo_done]; if (w->out.len) { - if (print_hunk_marks_between_files && printed_something) - write_or_die(1, "--\n", 3); - write_or_die(1, w->out.buf, w->out.len); - printed_something = 1; + const char *p = w->out.buf; + size_t len = w->out.len; + + /* Skip the leading hunk mark of the first file. */ + if (skip_first_line) { + while (len) { + len--; + if (*p++ == '\n') + break; + } + skip_first_line = 0; + } + + write_or_die(1, p, len); } free(w->name); free(w->identifier); @@ -249,7 +253,7 @@ static void start_threads(struct grep_opt *opt) err = pthread_create(&threads[i], NULL, run, o); if (err) - die("grep: failed to create thread: %s", + die(_("grep: failed to create thread: %s"), strerror(err)); } } @@ -307,8 +311,21 @@ static int grep_config(const char *var, const char *value, void *cb) default: return 0; } + if (!strcmp(var, "grep.extendedregexp")) { + if (git_config_bool(var, value)) + opt->regflags |= REG_EXTENDED; + else + opt->regflags &= ~REG_EXTENDED; + return 0; + } + + if (!strcmp(var, "grep.linenumber")) { + opt->linenum = git_config_bool(var, value); + return 0; + } + if (!strcmp(var, "color.grep")) - opt->color = git_config_colorbool(var, value, -1); + opt->color = git_config_colorbool(var, value); else if (!strcmp(var, "color.grep.context")) color = opt->color_context; else if (!strcmp(var, "color.grep.filename")) @@ -333,106 +350,6 @@ static int grep_config(const char *var, const char *value, void *cb) return 0; } -/* - * Return non-zero if max_depth is negative or path has no more then max_depth - * slashes. - */ -static int accept_subdir(const char *path, int max_depth) -{ - if (max_depth < 0) - return 1; - - while ((path = strchr(path, '/')) != NULL) { - max_depth--; - if (max_depth < 0) - return 0; - path++; - } - return 1; -} - -/* - * Return non-zero if name is a subdirectory of match and is not too deep. - */ -static int is_subdir(const char *name, int namelen, - const char *match, int matchlen, int max_depth) -{ - if (matchlen > namelen || strncmp(name, match, matchlen)) - return 0; - - if (name[matchlen] == '\0') /* exact match */ - return 1; - - if (!matchlen || match[matchlen-1] == '/' || name[matchlen] == '/') - return accept_subdir(name + matchlen + 1, max_depth); - - return 0; -} - -/* - * git grep pathspecs are somewhat different from diff-tree pathspecs; - * pathname wildcards are allowed. - */ -static int pathspec_matches(const char **paths, const char *name, int max_depth) -{ - int namelen, i; - if (!paths || !*paths) - return accept_subdir(name, max_depth); - namelen = strlen(name); - for (i = 0; paths[i]; i++) { - const char *match = paths[i]; - int matchlen = strlen(match); - const char *cp, *meta; - - if (is_subdir(name, namelen, match, matchlen, max_depth)) - return 1; - if (!fnmatch(match, name, 0)) - return 1; - if (name[namelen-1] != '/') - continue; - - /* We are being asked if the directory ("name") is worth - * descending into. - * - * Find the longest leading directory name that does - * not have metacharacter in the pathspec; the name - * we are looking at must overlap with that directory. - */ - for (cp = match, meta = NULL; cp - match < matchlen; cp++) { - char ch = *cp; - if (ch == '*' || ch == '[' || ch == '?') { - meta = cp; - break; - } - } - if (!meta) - meta = cp; /* fully literal */ - - if (namelen <= meta - match) { - /* Looking at "Documentation/" and - * the pattern says "Documentation/howto/", or - * "Documentation/diff*.txt". The name we - * have should match prefix. - */ - if (!memcmp(match, name, namelen)) - return 1; - continue; - } - - if (meta - match < namelen) { - /* Looking at "Documentation/howto/" and - * the pattern says "Documentation/h*"; - * match up to "Do.../h"; this avoids descending - * into "Documentation/technical/". - */ - if (!memcmp(match, name, meta - match)) - return 1; - continue; - } - } - return 0; -} - static void *lock_and_read_sha1_file(const unsigned char *sha1, enum object_type *type, unsigned long *size) { void *data; @@ -454,7 +371,7 @@ static void *load_sha1(const unsigned char *sha1, unsigned long *size, void *data = lock_and_read_sha1_file(sha1, &type, size); if (!data) - error("'%s': unable to read %s", name, sha1_to_hex(sha1)); + error(_("'%s': unable to read %s"), name, sha1_to_hex(sha1)); return data; } @@ -505,21 +422,21 @@ static void *load_file(const char *filename, size_t *sz) if (lstat(filename, &st) < 0) { err_ret: if (errno != ENOENT) - error("'%s': %s", filename, strerror(errno)); - return 0; + error(_("'%s': %s"), filename, strerror(errno)); + return NULL; } if (!S_ISREG(st.st_mode)) - return 0; + return NULL; *sz = xsize_t(st.st_size); i = open(filename, O_RDONLY); if (i < 0) goto err_ret; data = xmalloc(*sz + 1); if (st.st_size != read_in_full(i, data, *sz)) { - error("'%s': short read %s", filename, strerror(errno)); + error(_("'%s': short read %s"), filename, strerror(errno)); close(i); free(data); - return 0; + return NULL; } close(i); data[*sz] = 0; @@ -578,14 +495,14 @@ static void run_pager(struct grep_opt *opt, const char *prefix) argv[path_list->nr] = NULL; if (prefix && chdir(prefix)) - die("Failed to chdir: %s", prefix); + die(_("Failed to chdir: %s"), prefix); status = run_command_v_opt(argv, RUN_USING_SHELL); if (status) exit(status); free(argv); } -static int grep_cache(struct grep_opt *opt, const char **paths, int cached) +static int grep_cache(struct grep_opt *opt, const struct pathspec *pathspec, int cached) { int hit = 0; int nr; @@ -595,7 +512,7 @@ static int grep_cache(struct grep_opt *opt, const char **paths, int cached) struct cache_entry *ce = active_cache[nr]; if (!S_ISREG(ce->ce_mode)) continue; - if (!pathspec_matches(paths, ce->name, opt->max_depth)) + if (!match_pathspec_depth(pathspec, ce->name, ce_namelen(ce), 0, NULL)) continue; /* * If CE_VALID is on, we assume worktree file and its cache entry @@ -622,44 +539,29 @@ static int grep_cache(struct grep_opt *opt, const char **paths, int cached) return hit; } -static int grep_tree(struct grep_opt *opt, const char **paths, - struct tree_desc *tree, - const char *tree_name, const char *base) +static int grep_tree(struct grep_opt *opt, const struct pathspec *pathspec, + struct tree_desc *tree, struct strbuf *base, int tn_len) { - int len; - int hit = 0; + int hit = 0, match = 0; struct name_entry entry; - char *down; - int tn_len = strlen(tree_name); - struct strbuf pathbuf; - - strbuf_init(&pathbuf, PATH_MAX + tn_len); - - if (tn_len) { - strbuf_add(&pathbuf, tree_name, tn_len); - strbuf_addch(&pathbuf, ':'); - tn_len = pathbuf.len; - } - strbuf_addstr(&pathbuf, base); - len = pathbuf.len; + int old_baselen = base->len; while (tree_entry(tree, &entry)) { int te_len = tree_entry_len(entry.path, entry.sha1); - pathbuf.len = len; - strbuf_add(&pathbuf, entry.path, te_len); - - if (S_ISDIR(entry.mode)) - /* Match "abc/" against pathspec to - * decide if we want to descend into "abc" - * directory. - */ - strbuf_addch(&pathbuf, '/'); - - down = pathbuf.buf + tn_len; - if (!pathspec_matches(paths, down, opt->max_depth)) - ; - else if (S_ISREG(entry.mode)) - hit |= grep_sha1(opt, entry.sha1, pathbuf.buf, tn_len); + + if (match != 2) { + match = tree_entry_interesting(&entry, base, tn_len, pathspec); + if (match < 0) + break; + if (match == 0) + continue; + } + + strbuf_add(base, entry.path, te_len); + + if (S_ISREG(entry.mode)) { + hit |= grep_sha1(opt, entry.sha1, base->buf, tn_len); + } else if (S_ISDIR(entry.mode)) { enum object_type type; struct tree_desc sub; @@ -668,20 +570,23 @@ static int grep_tree(struct grep_opt *opt, const char **paths, data = lock_and_read_sha1_file(entry.sha1, &type, &size); if (!data) - die("unable to read tree (%s)", + die(_("unable to read tree (%s)"), sha1_to_hex(entry.sha1)); + + strbuf_addch(base, '/'); init_tree_desc(&sub, data, size); - hit |= grep_tree(opt, paths, &sub, tree_name, down); + hit |= grep_tree(opt, pathspec, &sub, base, tn_len); free(data); } + strbuf_setlen(base, old_baselen); + if (hit && opt->status_only) break; } - strbuf_release(&pathbuf); return hit; } -static int grep_object(struct grep_opt *opt, const char **paths, +static int grep_object(struct grep_opt *opt, const struct pathspec *pathspec, struct object *obj, const char *name) { if (obj->type == OBJ_BLOB) @@ -690,20 +595,30 @@ static int grep_object(struct grep_opt *opt, const char **paths, struct tree_desc tree; void *data; unsigned long size; - int hit; + struct strbuf base; + int hit, len; + data = read_object_with_reference(obj->sha1, tree_type, &size, NULL); if (!data) - die("unable to read tree (%s)", sha1_to_hex(obj->sha1)); + die(_("unable to read tree (%s)"), sha1_to_hex(obj->sha1)); + + len = name ? strlen(name) : 0; + strbuf_init(&base, PATH_MAX + len + 1); + if (len) { + strbuf_add(&base, name, len); + strbuf_addch(&base, ':'); + } init_tree_desc(&tree, data, size); - hit = grep_tree(opt, paths, &tree, name, ""); + hit = grep_tree(opt, pathspec, &tree, &base, base.len); + strbuf_release(&base); free(data); return hit; } - die("unable to grep from object of type %s", typename(obj->type)); + die(_("unable to grep from object of type %s"), typename(obj->type)); } -static int grep_objects(struct grep_opt *opt, const char **paths, +static int grep_objects(struct grep_opt *opt, const struct pathspec *pathspec, const struct object_array *list) { unsigned int i; @@ -713,7 +628,7 @@ static int grep_objects(struct grep_opt *opt, const char **paths, for (i = 0; i < nr; i++) { struct object *real_obj; real_obj = deref_tag(list->objects[i].item, NULL, 0); - if (grep_object(opt, paths, real_obj, list->objects[i].name)) { + if (grep_object(opt, pathspec, real_obj, list->objects[i].name)) { hit = 1; if (opt->status_only) break; @@ -722,7 +637,7 @@ static int grep_objects(struct grep_opt *opt, const char **paths, return hit; } -static int grep_directory(struct grep_opt *opt, const char **paths) +static int grep_directory(struct grep_opt *opt, const struct pathspec *pathspec) { struct dir_struct dir; int i, hit = 0; @@ -730,8 +645,12 @@ static int grep_directory(struct grep_opt *opt, const char **paths) memset(&dir, 0, sizeof(dir)); setup_standard_excludes(&dir); - fill_directory(&dir, paths); + fill_directory(&dir, pathspec->raw); for (i = 0; i < dir.nr; i++) { + const char *name = dir.entries[i]->name; + int namelen = strlen(name); + if (!match_pathspec_depth(pathspec, name, namelen, 0, NULL)) + continue; hit |= grep_file(opt, dir.entries[i]->name); if (hit && opt->status_only) break; @@ -752,7 +671,7 @@ static int context_callback(const struct option *opt, const char *arg, } value = strtol(arg, (char **)&endp, 10); if (*endp) { - return error("switch `%c' expects a numerical value", + return error(_("switch `%c' expects a numerical value"), opt->short_name); } grep_opt->pre_context = grep_opt->post_context = value; @@ -762,13 +681,14 @@ static int context_callback(const struct option *opt, const char *arg, static int file_callback(const struct option *opt, const char *arg, int unset) { struct grep_opt *grep_opt = opt->value; + int from_stdin = !strcmp(arg, "-"); FILE *patterns; int lno = 0; struct strbuf sb = STRBUF_INIT; - patterns = fopen(arg, "r"); + patterns = from_stdin ? stdin : fopen(arg, "r"); if (!patterns) - die_errno("cannot open '%s'", arg); + die_errno(_("cannot open '%s'"), arg); while (strbuf_getline(&sb, patterns, '\n') == 0) { char *s; size_t len; @@ -780,7 +700,8 @@ static int file_callback(const struct option *opt, const char *arg, int unset) s = strbuf_detach(&sb, &len); append_grep_pat(grep_opt, s, len, arg, ++lno, GREP_PATTERN); } - fclose(patterns); + if (!from_stdin) + fclose(patterns); strbuf_release(&sb); return 0; } @@ -834,12 +755,22 @@ int cmd_grep(int argc, const char **argv, const char *prefix) int external_grep_allowed__ignored; const char *show_in_pager = NULL, *default_pager = "dummy"; struct grep_opt opt; - struct object_array list = { 0, 0, NULL }; + struct object_array list = OBJECT_ARRAY_INIT; const char **paths = NULL; - struct string_list path_list = { NULL, 0, 0, 0 }; + struct pathspec pathspec; + struct string_list path_list = STRING_LIST_INIT_NODUP; int i; int dummy; - int nongit = 0, use_index = 1; + int use_index = 1; + enum { + pattern_type_unspecified = 0, + pattern_type_bre, + pattern_type_ere, + pattern_type_fixed, + pattern_type_pcre, + }; + int pattern_type = pattern_type_unspecified; + struct option options[] = { OPT_BOOLEAN(0, "cached", &cached, "search in index instead of in the work tree"), @@ -861,15 +792,20 @@ int cmd_grep(int argc, const char **argv, const char *prefix) "descend at most <depth> levels", PARSE_OPT_NONEG, NULL, 1 }, OPT_GROUP(""), - OPT_BIT('E', "extended-regexp", &opt.regflags, - "use extended POSIX regular expressions", REG_EXTENDED), - OPT_NEGBIT('G', "basic-regexp", &opt.regflags, - "use basic POSIX regular expressions (default)", - REG_EXTENDED), - OPT_BOOLEAN('F', "fixed-strings", &opt.fixed, - "interpret patterns as fixed strings"), + OPT_SET_INT('E', "extended-regexp", &pattern_type, + "use extended POSIX regular expressions", + pattern_type_ere), + OPT_SET_INT('G', "basic-regexp", &pattern_type, + "use basic POSIX regular expressions (default)", + pattern_type_bre), + OPT_SET_INT('F', "fixed-strings", &pattern_type, + "interpret patterns as fixed strings", + pattern_type_fixed), + OPT_SET_INT('P', "perl-regexp", &pattern_type, + "use Perl-compatible regular expressions", + pattern_type_pcre), OPT_GROUP(""), - OPT_BOOLEAN('n', NULL, &opt.linenum, "show line numbers"), + OPT_BOOLEAN('n', "line-number", &opt.linenum, "show line numbers"), OPT_NEGBIT('h', NULL, &opt.pathname, "don't show filenames", 1), OPT_BIT('H', NULL, &opt.pathname, "show filenames", 1), OPT_NEGBIT(0, "full-name", &opt.relative, @@ -886,18 +822,24 @@ int cmd_grep(int argc, const char **argv, const char *prefix) OPT_BOOLEAN('c', "count", &opt.count, "show the number of matches instead of matching lines"), OPT__COLOR(&opt.color, "highlight matches"), + OPT_BOOLEAN(0, "break", &opt.file_break, + "print empty line between matches from different files"), + OPT_BOOLEAN(0, "heading", &opt.heading, + "show filename only once above matches from same file"), OPT_GROUP(""), - OPT_CALLBACK('C', NULL, &opt, "n", + OPT_CALLBACK('C', "context", &opt, "n", "show <n> context lines before and after matches", context_callback), - OPT_INTEGER('B', NULL, &opt.pre_context, + OPT_INTEGER('B', "before-context", &opt.pre_context, "show <n> context lines before matches"), - OPT_INTEGER('A', NULL, &opt.post_context, + OPT_INTEGER('A', "after-context", &opt.post_context, "show <n> context lines after matches"), OPT_NUMBER_CALLBACK(&opt, "shortcut for -C NUM", context_callback), OPT_BOOLEAN('p', "show-function", &opt.funcname, "show a line with the function name before matches"), + OPT_BOOLEAN('W', "function-context", &opt.funcbody, + "show the surrounding function"), OPT_GROUP(""), OPT_CALLBACK('f', NULL, &opt, "file", "read patterns from file", file_callback), @@ -915,8 +857,8 @@ int cmd_grep(int argc, const char **argv, const char *prefix) { OPTION_CALLBACK, ')', NULL, &opt, NULL, "", PARSE_OPT_NOARG | PARSE_OPT_NONEG | PARSE_OPT_NODASH, close_callback }, - OPT_BOOLEAN('q', "quiet", &opt.status_only, - "indicate hit with exit status without output"), + OPT__QUIET(&opt.status_only, + "indicate hit with exit status without output"), OPT_BOOLEAN(0, "all-match", &opt.all_match, "show only matches from files that match all patterns"), OPT_GROUP(""), @@ -930,8 +872,6 @@ int cmd_grep(int argc, const char **argv, const char *prefix) OPT_END() }; - prefix = setup_git_directory_gently(&nongit); - /* * 'git grep -h', unlike 'git grep -h <pattern>', is a request * to show usage information and exit. @@ -958,8 +898,6 @@ int cmd_grep(int argc, const char **argv, const char *prefix) strcpy(opt.color_sep, GIT_COLOR_CYAN); opt.color = -1; git_config(grep_config, &opt); - if (opt.color == -1) - opt.color = git_use_color_default; /* * If there is no -- then the paths must exist in the working @@ -975,8 +913,30 @@ int cmd_grep(int argc, const char **argv, const char *prefix) PARSE_OPT_KEEP_DASHDASH | PARSE_OPT_STOP_AT_NON_OPTION | PARSE_OPT_NO_INTERNAL_HELP); + switch (pattern_type) { + case pattern_type_fixed: + opt.fixed = 1; + opt.pcre = 0; + break; + case pattern_type_bre: + opt.fixed = 0; + opt.pcre = 0; + opt.regflags &= ~REG_EXTENDED; + break; + case pattern_type_ere: + opt.fixed = 0; + opt.pcre = 0; + opt.regflags |= REG_EXTENDED; + break; + case pattern_type_pcre: + opt.fixed = 0; + opt.pcre = 1; + break; + default: + break; /* nothing */ + } - if (use_index && nongit) + if (use_index && !startup_info->have_repository) /* die the same way as if we did it at the beginning */ setup_git_directory(); @@ -1011,19 +971,18 @@ int cmd_grep(int argc, const char **argv, const char *prefix) } if (!opt.pattern_list) - die("no pattern given."); + die(_("no pattern given.")); if (!opt.fixed && opt.ignore_case) opt.regflags |= REG_ICASE; - if ((opt.regflags != REG_NEWLINE) && opt.fixed) - die("cannot mix --fixed-strings and regexp"); #ifndef NO_PTHREADS if (online_cpus() == 1 || !grep_threads_ok(&opt)) use_threads = 0; if (use_threads) { - if (opt.pre_context || opt.post_context) - print_hunk_marks_between_files = 1; + if (opt.pre_context || opt.post_context || opt.file_break || + opt.funcbody) + skip_first_line = 1; start_threads(&opt); } #else @@ -1040,7 +999,7 @@ int cmd_grep(int argc, const char **argv, const char *prefix) if (!get_sha1(arg, sha1)) { struct object *object = parse_object(sha1); if (!object) - die("bad object %s", arg); + die(_("bad object %s"), arg); add_object_array(object, arg, &list); continue; } @@ -1058,16 +1017,13 @@ int cmd_grep(int argc, const char **argv, const char *prefix) verify_filename(prefix, argv[j]); } - if (i < argc) - paths = get_pathspec(prefix, argv + i); - else if (prefix) { - paths = xcalloc(2, sizeof(const char *)); - paths[0] = prefix; - paths[1] = NULL; - } + paths = get_pathspec(prefix, argv + i); + init_pathspec(&pathspec, paths); + pathspec.max_depth = opt.max_depth; + pathspec.recursive = 1; if (show_in_pager && (cached || list.nr)) - die("--open-files-in-pager only works on the worktree"); + die(_("--open-files-in-pager only works on the worktree")); if (show_in_pager && opt.pattern_list && !opt.pattern_list->next) { const char *pager = path_list.items[0].string; @@ -1092,19 +1048,19 @@ int cmd_grep(int argc, const char **argv, const char *prefix) if (!use_index) { if (cached) - die("--cached cannot be used with --no-index."); + die(_("--cached cannot be used with --no-index.")); if (list.nr) - die("--no-index cannot be used with revs."); - hit = grep_directory(&opt, paths); + die(_("--no-index cannot be used with revs.")); + hit = grep_directory(&opt, &pathspec); } else if (!list.nr) { if (!cached) setup_work_tree(); - hit = grep_cache(&opt, paths, cached); + hit = grep_cache(&opt, &pathspec, cached); } else { if (cached) - die("both --cached and trees are given."); - hit = grep_objects(&opt, paths, &list); + die(_("both --cached and trees are given.")); + hit = grep_objects(&opt, &pathspec, &list); } if (use_threads) diff --git a/builtin/hash-object.c b/builtin/hash-object.c index 080af1a01b..33911fd5e9 100644 --- a/builtin/hash-object.c +++ b/builtin/hash-object.c @@ -4,7 +4,7 @@ * Copyright (C) Linus Torvalds, 2005 * Copyright (C) Junio C Hamano, 2005 */ -#include "cache.h" +#include "builtin.h" #include "blob.h" #include "quote.h" #include "parse-options.h" @@ -14,8 +14,11 @@ static void hash_fd(int fd, const char *type, int write_object, const char *path { struct stat st; unsigned char sha1[20]; + unsigned flags = (HASH_FORMAT_CHECK | + (write_object ? HASH_WRITE_OBJECT : 0)); + if (fstat(fd, &st) < 0 || - index_fd(sha1, fd, &st, write_object, type_from_string(type), path)) + index_fd(sha1, fd, &st, type_from_string(type), path, flags)) die(write_object ? "Unable to add %s to database" : "Unable to hash %s", path); diff --git a/builtin/help.c b/builtin/help.c index a9836b00ae..61ff79839b 100644 --- a/builtin/help.c +++ b/builtin/help.c @@ -120,7 +120,7 @@ static void exec_woman_emacs(const char *path, const char *page) if (!path) path = "emacsclient"; strbuf_addf(&man_page, "(woman \"%s\")", page); - execlp(path, "emacsclient", "-e", man_page.buf, NULL); + execlp(path, "emacsclient", "-e", man_page.buf, (char *)NULL); warning("failed to exec '%s': %s", path, strerror(errno)); } } @@ -148,7 +148,7 @@ static void exec_man_konqueror(const char *path, const char *page) } else path = "kfmclient"; strbuf_addf(&man_page, "man:%s(1)", page); - execlp(path, filename, "newTab", man_page.buf, NULL); + execlp(path, filename, "newTab", man_page.buf, (char *)NULL); warning("failed to exec '%s': %s", path, strerror(errno)); } } @@ -157,7 +157,7 @@ static void exec_man_man(const char *path, const char *page) { if (!path) path = "man"; - execlp(path, "man", page, NULL); + execlp(path, "man", page, (char *)NULL); warning("failed to exec '%s': %s", path, strerror(errno)); } @@ -165,7 +165,7 @@ static void exec_man_cmd(const char *cmd, const char *page) { struct strbuf shell_cmd = STRBUF_INIT; strbuf_addf(&shell_cmd, "%s %s", cmd, page); - execl("/bin/sh", "sh", "-c", shell_cmd.buf, NULL); + execl("/bin/sh", "sh", "-c", shell_cmd.buf, (char *)NULL); warning("failed to exec '%s': %s", cmd, strerror(errno)); } @@ -372,7 +372,7 @@ static void show_info_page(const char *git_cmd) { const char *page = cmd_to_page(git_cmd); setenv("INFOPATH", system_path(GIT_INFO_PATH), 1); - execlp("info", "info", "gitman", page, NULL); + execlp("info", "info", "gitman", page, (char *)NULL); die("no info viewer handled the request"); } @@ -398,7 +398,7 @@ static void get_html_page_path(struct strbuf *page_path, const char *page) #ifndef open_html static void open_html(const char *path) { - execl_git_cmd("web--browse", "-c", "help.browser", path, NULL); + execl_git_cmd("web--browse", "-c", "help.browser", path, (char *)NULL); } #endif diff --git a/builtin/index-pack.c b/builtin/index-pack.c index a89ae831dd..0945adbb3b 100644 --- a/builtin/index-pack.c +++ b/builtin/index-pack.c @@ -1,4 +1,4 @@ -#include "cache.h" +#include "builtin.h" #include "delta.h" #include "pack.h" #include "csum-file.h" @@ -11,15 +11,16 @@ #include "exec_cmd.h" static const char index_pack_usage[] = -"git index-pack [-v] [-o <index-file>] [{ --keep | --keep=<msg> }] [--strict] { <pack-file> | --stdin [--fix-thin] [<pack-file>] }"; +"git index-pack [-v] [-o <index-file>] [--keep | --keep=<msg>] [--verify] [--strict] (<pack-file> | --stdin [--fix-thin] [<pack-file>])"; -struct object_entry -{ +struct object_entry { struct pack_idx_entry idx; unsigned long size; unsigned int hdr_size; enum object_type type; enum object_type real_type; + unsigned delta_depth; + int base_object_no; }; union delta_base { @@ -44,8 +45,7 @@ struct base_data { #define FLAG_LINK (1u<<20) #define FLAG_CHECKED (1u<<21) -struct delta_entry -{ +struct delta_entry { union delta_base base; int obj_no; }; @@ -68,6 +68,7 @@ static struct progress *progress; static unsigned char input_buffer[4096]; static unsigned int input_offset, input_len; static off_t consumed_bytes; +static unsigned deepest_delta; static git_SHA_CTX input_ctx; static uint32_t input_crc32; static int input_fd, output_fd, pack_fd; @@ -161,7 +162,7 @@ static void use(int bytes) input_offset += bytes; /* make sure off_t is sufficiently large not to wrap */ - if (consumed_bytes > consumed_bytes + bytes) + if (signed_add_overflows(consumed_bytes, bytes)) die("pack too large for current definition of off_t"); consumed_bytes += bytes; } @@ -209,7 +210,7 @@ static void parse_pack_header(void) static NORETURN void bad_object(unsigned long offset, const char *format, ...) __attribute__((format (printf, 2, 3))); -static void bad_object(unsigned long offset, const char *format, ...) +static NORETURN void bad_object(unsigned long offset, const char *format, ...) { va_list params; char buf[1024]; @@ -267,7 +268,7 @@ static void unlink_base_data(struct base_data *c) static void *unpack_entry_data(unsigned long offset, unsigned long size) { int status; - z_stream stream; + git_zstream stream; void *buf = xmalloc(size); memset(&stream, 0, sizeof(stream)); @@ -296,7 +297,7 @@ static void *unpack_raw_entry(struct object_entry *obj, union delta_base *delta_ void *data; obj->idx.offset = consumed_bytes; - input_crc32 = crc32(0, Z_NULL, 0); + input_crc32 = crc32(0, NULL, 0); p = fill(1); c = *p; @@ -357,7 +358,7 @@ static void *get_data_from_pack(struct object_entry *obj) off_t from = obj[0].idx.offset + obj[0].hdr_size; unsigned long len = obj[1].idx.offset - from; unsigned char *data, *inbuf; - z_stream stream; + git_zstream stream; int status; data = xmalloc(obj->size); @@ -391,7 +392,18 @@ static void *get_data_from_pack(struct object_entry *obj) return data; } -static int find_delta(const union delta_base *base) +static int compare_delta_bases(const union delta_base *base1, + const union delta_base *base2, + enum object_type type1, + enum object_type type2) +{ + int cmp = type1 - type2; + if (cmp) + return cmp; + return memcmp(base1, base2, UNION_BASE_SZ); +} + +static int find_delta(const union delta_base *base, enum object_type type) { int first = 0, last = nr_deltas; @@ -400,7 +412,8 @@ static int find_delta(const union delta_base *base) struct delta_entry *delta = &deltas[next]; int cmp; - cmp = memcmp(base, &delta->base, UNION_BASE_SZ); + cmp = compare_delta_bases(base, &delta->base, + type, objects[delta->obj_no].type); if (!cmp) return next; if (cmp < 0) { @@ -413,9 +426,10 @@ static int find_delta(const union delta_base *base) } static void find_delta_children(const union delta_base *base, - int *first_index, int *last_index) + int *first_index, int *last_index, + enum object_type type) { - int first = find_delta(base); + int first = find_delta(base, type); int last = first; int end = nr_deltas - 1; @@ -485,12 +499,17 @@ static void sha1_object(const void *data, unsigned long size, } } +static int is_delta_type(enum object_type type) +{ + return (type == OBJ_REF_DELTA || type == OBJ_OFS_DELTA); +} + static void *get_base_data(struct base_data *c) { if (!c->data) { struct object_entry *obj = c->obj; - if (obj->type == OBJ_REF_DELTA || obj->type == OBJ_OFS_DELTA) { + if (is_delta_type(obj->type)) { void *base = get_base_data(c->base); void *raw = get_data_from_pack(obj); c->data = patch_delta( @@ -517,6 +536,10 @@ static void resolve_delta(struct object_entry *delta_obj, void *base_data, *delta_data; delta_obj->real_type = base->obj->real_type; + delta_obj->delta_depth = base->obj->delta_depth + 1; + if (deepest_delta < delta_obj->delta_depth) + deepest_delta = delta_obj->delta_depth; + delta_obj->base_object_no = base->obj - objects; delta_data = get_data_from_pack(delta_obj); base_data = get_base_data(base); result->obj = delta_obj; @@ -543,11 +566,13 @@ static void find_unresolved_deltas(struct base_data *base, union delta_base base_spec; hashcpy(base_spec.sha1, base->obj->idx.sha1); - find_delta_children(&base_spec, &ref_first, &ref_last); + find_delta_children(&base_spec, + &ref_first, &ref_last, OBJ_REF_DELTA); memset(&base_spec, 0, sizeof(base_spec)); base_spec.offset = base->obj->idx.offset; - find_delta_children(&base_spec, &ofs_first, &ofs_last); + find_delta_children(&base_spec, + &ofs_first, &ofs_last, OBJ_OFS_DELTA); } if (ref_last == -1 && ofs_last == -1) { @@ -559,24 +584,24 @@ static void find_unresolved_deltas(struct base_data *base, for (i = ref_first; i <= ref_last; i++) { struct object_entry *child = objects + deltas[i].obj_no; - if (child->real_type == OBJ_REF_DELTA) { - struct base_data result; - resolve_delta(child, base, &result); - if (i == ref_last && ofs_last == -1) - free_base_data(base); - find_unresolved_deltas(&result, base); - } + struct base_data result; + + assert(child->real_type == OBJ_REF_DELTA); + resolve_delta(child, base, &result); + if (i == ref_last && ofs_last == -1) + free_base_data(base); + find_unresolved_deltas(&result, base); } for (i = ofs_first; i <= ofs_last; i++) { struct object_entry *child = objects + deltas[i].obj_no; - if (child->real_type == OBJ_OFS_DELTA) { - struct base_data result; - resolve_delta(child, base, &result); - if (i == ofs_last) - free_base_data(base); - find_unresolved_deltas(&result, base); - } + struct base_data result; + + assert(child->real_type == OBJ_OFS_DELTA); + resolve_delta(child, base, &result); + if (i == ofs_last) + free_base_data(base); + find_unresolved_deltas(&result, base); } unlink_base_data(base); @@ -586,7 +611,11 @@ static int compare_delta_entry(const void *a, const void *b) { const struct delta_entry *delta_a = a; const struct delta_entry *delta_b = b; - return memcmp(&delta_a->base, &delta_b->base, UNION_BASE_SZ); + + /* group by type (ref vs ofs) and then by value (sha-1 or offset) */ + return compare_delta_bases(&delta_a->base, &delta_b->base, + objects[delta_a->obj_no].type, + objects[delta_b->obj_no].type); } /* Parse all objects and return the pack content SHA1 hash */ @@ -610,7 +639,7 @@ static void parse_pack_objects(unsigned char *sha1) struct object_entry *obj = &objects[i]; void *data = unpack_raw_entry(obj, &delta->base); obj->real_type = obj->type; - if (obj->type == OBJ_REF_DELTA || obj->type == OBJ_OFS_DELTA) { + if (is_delta_type(obj->type)) { nr_deltas++; delta->obj_no = i; delta++; @@ -657,7 +686,7 @@ static void parse_pack_objects(unsigned char *sha1) struct object_entry *obj = &objects[i]; struct base_data base_obj; - if (obj->type == OBJ_REF_DELTA || obj->type == OBJ_OFS_DELTA) + if (is_delta_type(obj->type)) continue; base_obj.obj = obj; base_obj.data = NULL; @@ -668,26 +697,26 @@ static void parse_pack_objects(unsigned char *sha1) static int write_compressed(struct sha1file *f, void *in, unsigned int size) { - z_stream stream; + git_zstream stream; int status; unsigned char outbuf[4096]; memset(&stream, 0, sizeof(stream)); - deflateInit(&stream, zlib_compression_level); + git_deflate_init(&stream, zlib_compression_level); stream.next_in = in; stream.avail_in = size; do { stream.next_out = outbuf; stream.avail_out = sizeof(outbuf); - status = deflate(&stream, Z_FINISH); + status = git_deflate(&stream, Z_FINISH); sha1write(f, outbuf, sizeof(outbuf) - stream.avail_out); } while (status == Z_OK); if (status != Z_STREAM_END) die("unable to deflate appended object (%d)", status); size = stream.total_out; - deflateEnd(&stream); + git_deflate_end(&stream); return size; } @@ -861,48 +890,148 @@ static void final(const char *final_pack_name, const char *curr_pack_name, static int git_index_pack_config(const char *k, const char *v, void *cb) { + struct pack_idx_option *opts = cb; + if (!strcmp(k, "pack.indexversion")) { - pack_idx_default_version = git_config_int(k, v); - if (pack_idx_default_version > 2) - die("bad pack.indexversion=%"PRIu32, - pack_idx_default_version); + opts->version = git_config_int(k, v); + if (opts->version > 2) + die("bad pack.indexversion=%"PRIu32, opts->version); return 0; } return git_default_config(k, v, cb); } +static int cmp_uint32(const void *a_, const void *b_) +{ + uint32_t a = *((uint32_t *)a_); + uint32_t b = *((uint32_t *)b_); + + return (a < b) ? -1 : (a != b); +} + +static void read_v2_anomalous_offsets(struct packed_git *p, + struct pack_idx_option *opts) +{ + const uint32_t *idx1, *idx2; + uint32_t i; + + /* The address of the 4-byte offset table */ + idx1 = (((const uint32_t *)p->index_data) + + 2 /* 8-byte header */ + + 256 /* fan out */ + + 5 * p->num_objects /* 20-byte SHA-1 table */ + + p->num_objects /* CRC32 table */ + ); + + /* The address of the 8-byte offset table */ + idx2 = idx1 + p->num_objects; + + for (i = 0; i < p->num_objects; i++) { + uint32_t off = ntohl(idx1[i]); + if (!(off & 0x80000000)) + continue; + off = off & 0x7fffffff; + if (idx2[off * 2]) + continue; + /* + * The real offset is ntohl(idx2[off * 2]) in high 4 + * octets, and ntohl(idx2[off * 2 + 1]) in low 4 + * octets. But idx2[off * 2] is Zero!!! + */ + ALLOC_GROW(opts->anomaly, opts->anomaly_nr + 1, opts->anomaly_alloc); + opts->anomaly[opts->anomaly_nr++] = ntohl(idx2[off * 2 + 1]); + } + + if (1 < opts->anomaly_nr) + qsort(opts->anomaly, opts->anomaly_nr, sizeof(uint32_t), cmp_uint32); +} + +static void read_idx_option(struct pack_idx_option *opts, const char *pack_name) +{ + struct packed_git *p = add_packed_git(pack_name, strlen(pack_name), 1); + + if (!p) + die("Cannot open existing pack file '%s'", pack_name); + if (open_pack_index(p)) + die("Cannot open existing pack idx file for '%s'", pack_name); + + /* Read the attributes from the existing idx file */ + opts->version = p->index_version; + + if (opts->version == 2) + read_v2_anomalous_offsets(p, opts); + + /* + * Get rid of the idx file as we do not need it anymore. + * NEEDSWORK: extract this bit from free_pack_by_name() in + * sha1_file.c, perhaps? It shouldn't matter very much as we + * know we haven't installed this pack (hence we never have + * read anything from it). + */ + close_pack_index(p); + free(p); +} + +static void show_pack_info(int stat_only) +{ + int i, baseobjects = nr_objects - nr_deltas; + unsigned long *chain_histogram = NULL; + + if (deepest_delta) + chain_histogram = xcalloc(deepest_delta, sizeof(unsigned long)); + + for (i = 0; i < nr_objects; i++) { + struct object_entry *obj = &objects[i]; + + if (is_delta_type(obj->type)) + chain_histogram[obj->delta_depth - 1]++; + if (stat_only) + continue; + printf("%s %-6s %lu %lu %"PRIuMAX, + sha1_to_hex(obj->idx.sha1), + typename(obj->real_type), obj->size, + (unsigned long)(obj[1].idx.offset - obj->idx.offset), + (uintmax_t)obj->idx.offset); + if (is_delta_type(obj->type)) { + struct object_entry *bobj = &objects[obj->base_object_no]; + printf(" %u %s", obj->delta_depth, sha1_to_hex(bobj->idx.sha1)); + } + putchar('\n'); + } + + if (baseobjects) + printf("non delta: %d object%s\n", + baseobjects, baseobjects > 1 ? "s" : ""); + for (i = 0; i < deepest_delta; i++) { + if (!chain_histogram[i]) + continue; + printf("chain length = %d: %lu object%s\n", + i + 1, + chain_histogram[i], + chain_histogram[i] > 1 ? "s" : ""); + } +} + int cmd_index_pack(int argc, const char **argv, const char *prefix) { - int i, fix_thin_pack = 0; + int i, fix_thin_pack = 0, verify = 0, stat_only = 0, stat = 0; const char *curr_pack, *curr_index; const char *index_name = NULL, *pack_name = NULL; const char *keep_name = NULL, *keep_msg = NULL; char *index_name_buf = NULL, *keep_name_buf = NULL; struct pack_idx_entry **idx_objects; + struct pack_idx_option opts; unsigned char pack_sha1[20]; if (argc == 2 && !strcmp(argv[1], "-h")) usage(index_pack_usage); - /* - * We wish to read the repository's config file if any, and - * for that it is necessary to call setup_git_directory_gently(). - * However if the cwd was inside .git/objects/pack/ then we need - * to go back there or all the pack name arguments will be wrong. - * And in that case we cannot rely on any prefix returned by - * setup_git_directory_gently() either. - */ - { - char cwd[PATH_MAX+1]; - int nongit; - - if (!getcwd(cwd, sizeof(cwd)-1)) - die("Unable to get current working directory"); - setup_git_directory_gently(&nongit); - git_config(git_index_pack_config, NULL); - if (chdir(cwd)) - die("Cannot come back to cwd"); - } + read_replace_refs = 0; + + reset_pack_idx_option(&opts); + git_config(git_index_pack_config, &opts); + if (prefix && chdir(prefix)) + die("Cannot come back to cwd"); for (i = 1; i < argc; i++) { const char *arg = argv[i]; @@ -914,6 +1043,15 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix) fix_thin_pack = 1; } else if (!strcmp(arg, "--strict")) { strict = 1; + } else if (!strcmp(arg, "--verify")) { + verify = 1; + } else if (!strcmp(arg, "--verify-stat")) { + verify = 1; + stat = 1; + } else if (!strcmp(arg, "--verify-stat-only")) { + verify = 1; + stat = 1; + stat_only = 1; } else if (!strcmp(arg, "--keep")) { keep_msg = ""; } else if (!prefixcmp(arg, "--keep=")) { @@ -939,12 +1077,12 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix) index_name = argv[++i]; } else if (!prefixcmp(arg, "--index-version=")) { char *c; - pack_idx_default_version = strtoul(arg + 16, &c, 10); - if (pack_idx_default_version > 2) + opts.version = strtoul(arg + 16, &c, 10); + if (opts.version > 2) die("bad %s", arg); if (*c == ',') - pack_idx_off32_limit = strtoul(c+1, &c, 0); - if (*c || pack_idx_off32_limit & 0x80000000) + opts.off32_limit = strtoul(c+1, &c, 0); + if (*c || opts.off32_limit & 0x80000000) die("bad %s", arg); } else usage(index_pack_usage); @@ -980,11 +1118,17 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix) strcpy(keep_name_buf + len - 5, ".keep"); keep_name = keep_name_buf; } + if (verify) { + if (!index_name) + die("--verify with no packfile name given"); + read_idx_option(&opts, index_name); + opts.flags |= WRITE_IDX_VERIFY; + } curr_pack = open_pack_file(pack_name); parse_pack_header(); - objects = xmalloc((nr_objects + 1) * sizeof(struct object_entry)); - deltas = xmalloc(nr_objects * sizeof(struct delta_entry)); + objects = xcalloc(nr_objects + 1, sizeof(struct object_entry)); + deltas = xcalloc(nr_objects, sizeof(struct delta_entry)); parse_pack_objects(pack_sha1); if (nr_deltas == nr_resolved_deltas) { stop_progress(&progress); @@ -1024,16 +1168,22 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix) if (strict) check_objects(); + if (stat) + show_pack_info(stat_only); + idx_objects = xmalloc((nr_objects) * sizeof(struct pack_idx_entry *)); for (i = 0; i < nr_objects; i++) idx_objects[i] = &objects[i].idx; - curr_index = write_idx_file(index_name, idx_objects, nr_objects, pack_sha1); + curr_index = write_idx_file(index_name, idx_objects, nr_objects, &opts, pack_sha1); free(idx_objects); - final(pack_name, curr_pack, - index_name, curr_index, - keep_name, keep_msg, - pack_sha1); + if (!verify) + final(pack_name, curr_pack, + index_name, curr_index, + keep_name, keep_msg, + pack_sha1); + else + close(input_fd); free(objects); free(index_name_buf); free(keep_name_buf); diff --git a/builtin/init-db.c b/builtin/init-db.c index 0271285fad..d07554c884 100644 --- a/builtin/init-db.c +++ b/builtin/init-db.c @@ -21,6 +21,7 @@ static int init_is_bare_repository = 0; static int init_shared_repository = -1; static const char *init_db_template_dir; +static const char *git_link; static void safe_create_dir(const char *dir, int share) { @@ -31,7 +32,7 @@ static void safe_create_dir(const char *dir, int share) } } else if (share && adjust_shared_perm(dir)) - die("Could not make %s writable by group", dir); + die(_("Could not make %s writable by group"), dir); } static void copy_templates_1(char *path, int baselen, @@ -58,25 +59,25 @@ static void copy_templates_1(char *path, int baselen, namelen = strlen(de->d_name); if ((PATH_MAX <= baselen + namelen) || (PATH_MAX <= template_baselen + namelen)) - die("insanely long template name %s", de->d_name); + die(_("insanely long template name %s"), de->d_name); memcpy(path + baselen, de->d_name, namelen+1); memcpy(template + template_baselen, de->d_name, namelen+1); if (lstat(path, &st_git)) { if (errno != ENOENT) - die_errno("cannot stat '%s'", path); + die_errno(_("cannot stat '%s'"), path); } else exists = 1; if (lstat(template, &st_template)) - die_errno("cannot stat template '%s'", template); + die_errno(_("cannot stat template '%s'"), template); if (S_ISDIR(st_template.st_mode)) { DIR *subdir = opendir(template); int baselen_sub = baselen + namelen; int template_baselen_sub = template_baselen + namelen; if (!subdir) - die_errno("cannot opendir '%s'", template); + die_errno(_("cannot opendir '%s'"), template); path[baselen_sub++] = template[template_baselen_sub++] = '/'; path[baselen_sub] = @@ -93,20 +94,20 @@ static void copy_templates_1(char *path, int baselen, int len; len = readlink(template, lnk, sizeof(lnk)); if (len < 0) - die_errno("cannot readlink '%s'", template); + die_errno(_("cannot readlink '%s'"), template); if (sizeof(lnk) <= len) - die("insanely long symlink %s", template); + die(_("insanely long symlink %s"), template); lnk[len] = 0; if (symlink(lnk, path)) - die_errno("cannot symlink '%s' '%s'", lnk, path); + die_errno(_("cannot symlink '%s' '%s'"), lnk, path); } else if (S_ISREG(st_template.st_mode)) { if (copy_file(path, template, st_template.st_mode)) - die_errno("cannot copy '%s' to '%s'", template, + die_errno(_("cannot copy '%s' to '%s'"), template, path); } else - error("ignoring template %s", template); + error(_("ignoring template %s"), template); } } @@ -129,7 +130,7 @@ static void copy_templates(const char *template_dir) return; template_len = strlen(template_dir); if (PATH_MAX <= (template_len+strlen("/config"))) - die("insanely long template path %s", template_dir); + die(_("insanely long template path %s"), template_dir); strcpy(template_path, template_dir); if (template_path[template_len-1] != '/') { template_path[template_len++] = '/'; @@ -137,7 +138,7 @@ static void copy_templates(const char *template_dir) } dir = opendir(template_path); if (!dir) { - warning("templates not found %s", template_dir); + warning(_("templates not found %s"), template_dir); return; } @@ -150,8 +151,8 @@ static void copy_templates(const char *template_dir) if (repository_format_version && repository_format_version != GIT_REPO_VERSION) { - warning("not copying templates of " - "a wrong format version %d from '%s'", + warning(_("not copying templates of " + "a wrong format version %d from '%s'"), repository_format_version, template_dir); closedir(dir); @@ -188,7 +189,7 @@ static int create_default_files(const char *template_path) int filemode; if (len > sizeof(path)-50) - die("insane git directory %s", git_dir); + die(_("insane git directory %s"), git_dir); memcpy(path, git_dir, len); if (len && path[len-1] != '/') @@ -294,13 +295,84 @@ static int create_default_files(const char *template_path) return reinit; } +static void create_object_directory(void) +{ + const char *object_directory = get_object_directory(); + int len = strlen(object_directory); + char *path = xmalloc(len + 40); + + memcpy(path, object_directory, len); + + safe_create_dir(object_directory, 1); + strcpy(path+len, "/pack"); + safe_create_dir(path, 1); + strcpy(path+len, "/info"); + safe_create_dir(path, 1); + + free(path); +} + +int set_git_dir_init(const char *git_dir, const char *real_git_dir, + int exist_ok) +{ + if (real_git_dir) { + struct stat st; + + if (!exist_ok && !stat(git_dir, &st)) + die(_("%s already exists"), git_dir); + + if (!exist_ok && !stat(real_git_dir, &st)) + die(_("%s already exists"), real_git_dir); + + /* + * make sure symlinks are resolved because we'll be + * moving the target repo later on in separate_git_dir() + */ + git_link = xstrdup(real_path(git_dir)); + } + else { + real_git_dir = real_path(git_dir); + git_link = NULL; + } + set_git_dir(real_path(real_git_dir)); + return 0; +} + +static void separate_git_dir(const char *git_dir) +{ + struct stat st; + FILE *fp; + + if (!stat(git_link, &st)) { + const char *src; + + if (S_ISREG(st.st_mode)) + src = read_gitfile(git_link); + else if (S_ISDIR(st.st_mode)) + src = git_link; + else + die(_("unable to handle file type %d"), st.st_mode); + + if (rename(src, git_dir)) + die_errno(_("unable to move %s to %s"), src, git_dir); + } + + fp = fopen(git_link, "w"); + if (!fp) + die(_("Could not create git link %s"), git_link); + fprintf(fp, "gitdir: %s\n", git_dir); + fclose(fp); +} + int init_db(const char *template_dir, unsigned int flags) { - const char *sha1_dir; - char *path; - int len, reinit; + int reinit; + const char *git_dir = get_git_dir(); - safe_create_dir(get_git_dir(), 0); + if (git_link) + separate_git_dir(git_dir); + + safe_create_dir(git_dir, 0); init_is_bare_repository = is_bare_repository(); @@ -313,16 +385,7 @@ int init_db(const char *template_dir, unsigned int flags) reinit = create_default_files(template_dir); - sha1_dir = get_object_directory(); - len = strlen(sha1_dir); - path = xmalloc(len + 40); - memcpy(path, sha1_dir, len); - - safe_create_dir(sha1_dir, 1); - strcpy(path+len, "/pack"); - safe_create_dir(path, 1); - strcpy(path+len, "/info"); - safe_create_dir(path, 1); + create_object_directory(); if (shared_repository) { char buf[10]; @@ -346,11 +409,16 @@ int init_db(const char *template_dir, unsigned int flags) } if (!(flags & INIT_DB_QUIET)) { - const char *git_dir = get_git_dir(); int len = strlen(git_dir); - printf("%s%s Git repository in %s%s\n", - reinit ? "Reinitialized existing" : "Initialized empty", - shared_repository ? " shared" : "", + + /* + * TRANSLATORS: The first '%s' is either "Reinitialized + * existing" or "Initialized empty", the second " shared" or + * "", and the last '%s%s' is the verbatim directory name. + */ + printf(_("%s%s Git repository in %s%s\n"), + reinit ? _("Reinitialized existing") : _("Initialized empty"), + shared_repository ? _(" shared") : "", git_dir, len && git_dir[len-1] != '/' ? "/" : ""); } @@ -369,7 +437,7 @@ static int guess_repository_type(const char *git_dir) if (!strcmp(".", git_dir)) return 1; if (!getcwd(cwd, sizeof(cwd))) - die_errno("cannot tell cwd"); + die_errno(_("cannot tell cwd")); if (!strcmp(git_dir, cwd)) return 1; /* @@ -408,11 +476,13 @@ static const char *const init_db_usage[] = { int cmd_init_db(int argc, const char **argv, const char *prefix) { const char *git_dir; + const char *real_git_dir = NULL; + const char *work_tree; const char *template_dir = NULL; unsigned int flags = 0; const struct option init_db_options[] = { OPT_STRING(0, "template", &template_dir, "template-directory", - "provide the directory from which templates will be used"), + "directory from which templates will be used"), OPT_SET_INT(0, "bare", &is_bare_repository_cfg, "create a bare repository", 1), { OPTION_CALLBACK, 0, "shared", &init_shared_repository, @@ -420,11 +490,16 @@ int cmd_init_db(int argc, const char **argv, const char *prefix) "specify that the git repository is to be shared amongst several users", PARSE_OPT_OPTARG | PARSE_OPT_NONEG, shared_callback, 0}, OPT_BIT('q', "quiet", &flags, "be quiet", INIT_DB_QUIET), + OPT_STRING(0, "separate-git-dir", &real_git_dir, "gitdir", + "separate git dir from working tree"), OPT_END() }; argc = parse_options(argc, argv, prefix, init_db_options, init_db_usage, 0); + if (real_git_dir && !is_absolute_path(real_git_dir)) + real_git_dir = xstrdup(real_path(real_git_dir)); + if (argc == 1) { int mkdir_tried = 0; retry: @@ -443,18 +518,18 @@ int cmd_init_db(int argc, const char **argv, const char *prefix) errno = EEXIST; /* fallthru */ case -1: - die_errno("cannot mkdir %s", argv[0]); + die_errno(_("cannot mkdir %s"), argv[0]); break; default: break; } shared_repository = saved; if (mkdir(argv[0], 0777) < 0) - die_errno("cannot mkdir %s", argv[0]); + die_errno(_("cannot mkdir %s"), argv[0]); mkdir_tried = 1; goto retry; } - die_errno("cannot chdir to %s", argv[0]); + die_errno(_("cannot chdir to %s"), argv[0]); } } else if (0 < argc) { usage(init_db_usage[0]); @@ -474,10 +549,10 @@ int cmd_init_db(int argc, const char **argv, const char *prefix) * without --bare. Catch the error early. */ git_dir = getenv(GIT_DIR_ENVIRONMENT); - if ((!git_dir || is_bare_repository_cfg == 1) - && getenv(GIT_WORK_TREE_ENVIRONMENT)) - die("%s (or --work-tree=<directory>) not allowed without " - "specifying %s (or --git-dir=<directory>)", + work_tree = getenv(GIT_WORK_TREE_ENVIRONMENT); + if ((!git_dir || is_bare_repository_cfg == 1) && work_tree) + die(_("%s (or --work-tree=<directory>) not allowed without " + "specifying %s (or --git-dir=<directory>)"), GIT_WORK_TREE_ENVIRONMENT, GIT_DIR_ENVIRONMENT); @@ -491,25 +566,31 @@ int cmd_init_db(int argc, const char **argv, const char *prefix) is_bare_repository_cfg = guess_repository_type(git_dir); if (!is_bare_repository_cfg) { - if (git_dir) { - const char *git_dir_parent = strrchr(git_dir, '/'); - if (git_dir_parent) { - char *rel = xstrndup(git_dir, git_dir_parent - git_dir); - git_work_tree_cfg = xstrdup(make_absolute_path(rel)); - free(rel); - } + const char *git_dir_parent = strrchr(git_dir, '/'); + if (git_dir_parent) { + char *rel = xstrndup(git_dir, git_dir_parent - git_dir); + git_work_tree_cfg = xstrdup(real_path(rel)); + free(rel); } if (!git_work_tree_cfg) { git_work_tree_cfg = xcalloc(PATH_MAX, 1); if (!getcwd(git_work_tree_cfg, PATH_MAX)) - die_errno ("Cannot access current working directory"); + die_errno (_("Cannot access current working directory")); } + if (work_tree) + set_git_work_tree(real_path(work_tree)); + else + set_git_work_tree(git_work_tree_cfg); if (access(get_git_work_tree(), X_OK)) - die_errno ("Cannot access work tree '%s'", + die_errno (_("Cannot access work tree '%s'"), get_git_work_tree()); } + else { + if (work_tree) + set_git_work_tree(real_path(work_tree)); + } - set_git_dir(make_absolute_path(git_dir)); + set_git_dir_init(git_dir, real_git_dir, 1); return init_db(template_dir, flags); } diff --git a/builtin/log.c b/builtin/log.c index 08b872263c..d760ee0885 100644 --- a/builtin/log.c +++ b/builtin/log.c @@ -23,14 +23,18 @@ /* Set a default date-time format for git log ("log.date" config variable) */ static const char *default_date_mode = NULL; +static int default_abbrev_commit; static int default_show_root = 1; static int decoration_style; +static int decoration_given; static const char *fmt_patch_subject_prefix = "PATCH"; static const char *fmt_pretty; -static const char * const builtin_log_usage = +static const char * const builtin_log_usage[] = { "git log [<options>] [<since>..<until>] [[--] <path>...]\n" - " or: git show [options] <object>..."; + " or: git show [options] <object>...", + NULL +}; static int parse_decoration_style(const char *var, const char *value) { @@ -49,33 +53,66 @@ static int parse_decoration_style(const char *var, const char *value) return -1; } -static void cmd_log_init(int argc, const char **argv, const char *prefix, - struct rev_info *rev, struct setup_revision_opt *opt) +static int decorate_callback(const struct option *opt, const char *arg, int unset) { - int i; - int decoration_given = 0; - struct userformat_want w; + if (unset) + decoration_style = 0; + else if (arg) + decoration_style = parse_decoration_style("command line", arg); + else + decoration_style = DECORATE_SHORT_REFS; + if (decoration_style < 0) + die("invalid --decorate option: %s", arg); + + decoration_given = 1; + + return 0; +} + +static void cmd_log_init_defaults(struct rev_info *rev) +{ rev->abbrev = DEFAULT_ABBREV; rev->commit_format = CMIT_FMT_DEFAULT; if (fmt_pretty) get_commit_format(fmt_pretty, rev); rev->verbose_header = 1; DIFF_OPT_SET(&rev->diffopt, RECURSIVE); + rev->abbrev_commit = default_abbrev_commit; rev->show_root_diff = default_show_root; rev->subject_prefix = fmt_patch_subject_prefix; DIFF_OPT_SET(&rev->diffopt, ALLOW_TEXTCONV); if (default_date_mode) rev->date_mode = parse_date_format(default_date_mode); +} + +static void cmd_log_init_finish(int argc, const char **argv, const char *prefix, + struct rev_info *rev, struct setup_revision_opt *opt) +{ + struct userformat_want w; + int quiet = 0, source = 0; + + const struct option builtin_log_options[] = { + OPT_BOOLEAN(0, "quiet", &quiet, "suppress diff output"), + OPT_BOOLEAN(0, "source", &source, "show source"), + { OPTION_CALLBACK, 0, "decorate", NULL, NULL, "decorate options", + PARSE_OPT_OPTARG, decorate_callback}, + OPT_END() + }; + + argc = parse_options(argc, argv, prefix, + builtin_log_options, builtin_log_usage, + PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN | + PARSE_OPT_KEEP_DASHDASH); - /* - * Check for -h before setup_revisions(), or "git log -h" will - * fail when run without a git directory. - */ - if (argc == 2 && !strcmp(argv[1], "-h")) - usage(builtin_log_usage); argc = setup_revisions(argc, argv, rev, opt); + if (quiet) + rev->diffopt.output_format |= DIFF_FORMAT_NO_OUTPUT; + + /* Any arguments at this point are not recognized */ + if (argc > 1) + die("unrecognized argument: %s", argv[1]); memset(&w, 0, sizeof(w)); userformat_find_requirements(NULL, &w); @@ -89,42 +126,36 @@ static void cmd_log_init(int argc, const char **argv, const char *prefix, rev->always_show_header = 0; if (DIFF_OPT_TST(&rev->diffopt, FOLLOW_RENAMES)) { rev->always_show_header = 0; - if (rev->diffopt.nr_paths != 1) + if (rev->diffopt.pathspec.nr != 1) usage("git logs can only follow renames on one pathname at a time"); } - for (i = 1; i < argc; i++) { - const char *arg = argv[i]; - if (!strcmp(arg, "--decorate")) { - decoration_style = DECORATE_SHORT_REFS; - decoration_given = 1; - } else if (!prefixcmp(arg, "--decorate=")) { - const char *v = skip_prefix(arg, "--decorate="); - decoration_style = parse_decoration_style(arg, v); - if (decoration_style < 0) - die("invalid --decorate option: %s", arg); - decoration_given = 1; - } else if (!strcmp(arg, "--no-decorate")) { + + if (source) + rev->show_source = 1; + + if (rev->pretty_given && rev->commit_format == CMIT_FMT_RAW) { + /* + * "log --pretty=raw" is special; ignore UI oriented + * configuration variables such as decoration. + */ + if (!decoration_given) decoration_style = 0; - } else if (!strcmp(arg, "--source")) { - rev->show_source = 1; - } else if (!strcmp(arg, "-h")) { - usage(builtin_log_usage); - } else - die("unrecognized argument: %s", arg); + if (!rev->abbrev_commit_given) + rev->abbrev_commit = 0; } - /* - * defeat log.decorate configuration interacting with --pretty=raw - * from the command line. - */ - if (!decoration_given && rev->pretty_given - && rev->commit_format == CMIT_FMT_RAW) - decoration_style = 0; - if (decoration_style) { rev->show_decorations = 1; load_ref_decorations(decoration_style); } + setup_pager(); +} + +static void cmd_log_init(int argc, const char **argv, const char *prefix, + struct rev_info *rev, struct setup_revision_opt *opt) +{ + cmd_log_init_defaults(rev); + cmd_log_init_finish(argc, argv, prefix, rev, opt); } /* @@ -152,7 +183,7 @@ static void show_early_header(struct rev_info *rev, const char *stage, int nr) if (rev->commit_format != CMIT_FMT_ONELINE) putchar(rev->diffopt.line_termination); } - printf("Final output: %d %s\n", nr, stage); + printf(_("Final output: %d %s\n"), nr, stage); } static struct itimerval early_output_timer; @@ -246,12 +277,14 @@ static void finish_early_output(struct rev_info *rev) static int cmd_log_walk(struct rev_info *rev) { struct commit *commit; + int saved_nrl = 0; + int saved_dcctc = 0; if (rev->early_output) setup_early_output(rev); if (prepare_revision_walk(rev)) - die("revision walk setup failed"); + die(_("revision walk setup failed")); if (rev->early_output) finish_early_output(rev); @@ -262,7 +295,13 @@ static int cmd_log_walk(struct rev_info *rev) * retain that state information if replacing rev->diffopt in this loop */ while ((commit = get_revision(rev)) != NULL) { - log_tree_commit(rev, commit); + if (!log_tree_commit(rev, commit) && + rev->max_count >= 0) + /* + * We decremented max_count in get_revision, + * but we didn't actually show the commit. + */ + rev->max_count++; if (!rev->reflog_info) { /* we allow cycles in reflog ancestry */ free(commit->buffer); @@ -270,7 +309,14 @@ static int cmd_log_walk(struct rev_info *rev) } free_commit_list(commit->parents); commit->parents = NULL; + if (saved_nrl < rev->diffopt.needed_rename_limit) + saved_nrl = rev->diffopt.needed_rename_limit; + if (rev->diffopt.degraded_cc_to_c) + saved_dcctc = 1; } + rev->diffopt.degraded_cc_to_c = saved_dcctc; + rev->diffopt.needed_rename_limit = saved_nrl; + if (rev->diffopt.output_format & DIFF_FORMAT_CHECKDIFF && DIFF_OPT_TST(&rev->diffopt, CHECK_FAILED)) { return 02; @@ -284,6 +330,10 @@ static int git_log_config(const char *var, const char *value, void *cb) return git_config_string(&fmt_pretty, var, value); if (!strcmp(var, "format.subjectprefix")) return git_config_string(&fmt_patch_subject_prefix, var, value); + if (!strcmp(var, "log.abbrevcommit")) { + default_abbrev_commit = git_config_bool(var, value); + return 0; + } if (!strcmp(var, "log.date")) return git_config_string(&default_date_mode, var, value); if (!strcmp(var, "log.decorate")) { @@ -309,9 +359,6 @@ int cmd_whatchanged(int argc, const char **argv, const char *prefix) git_config(git_log_config, NULL); - if (diff_use_color_default == -1) - diff_use_color_default = git_use_color_default; - init_revisions(&rev, prefix); rev.diff = 1; rev.simplify_history = 0; @@ -326,10 +373,11 @@ int cmd_whatchanged(int argc, const char **argv, const char *prefix) static void show_tagger(char *buf, int len, struct rev_info *rev) { struct strbuf out = STRBUF_INIT; + struct pretty_print_context pp = {0}; - pp_user_info("Tagger", rev->commit_format, &out, buf, rev->date_mode, - git_log_output_encoding ? - git_log_output_encoding: git_commit_encoding); + pp.fmt = rev->commit_format; + pp.date_mode = rev->date_mode; + pp_user_info(&pp, "Tagger", &out, buf, get_log_output_encoding()); printf("%s", out.buf); strbuf_release(&out); } @@ -343,7 +391,7 @@ static int show_object(const unsigned char *sha1, int show_tag_object, int offset = 0; if (!buf) - return error("Could not read object %s", sha1_to_hex(sha1)); + return error(_("Could not read object %s"), sha1_to_hex(sha1)); if (show_tag_object) while (offset < size && buf[offset] != '\n') { @@ -390,13 +438,12 @@ int cmd_show(int argc, const char **argv, const char *prefix) struct rev_info rev; struct object_array_entry *objects; struct setup_revision_opt opt; + struct pathspec match_all; int i, count, ret = 0; git_config(git_log_config, NULL); - if (diff_use_color_default == -1) - diff_use_color_default = git_use_color_default; - + init_pathspec(&match_all, NULL); init_revisions(&rev, prefix); rev.diff = 1; rev.always_show_header = 1; @@ -430,7 +477,7 @@ int cmd_show(int argc, const char **argv, const char *prefix) break; o = parse_object(t->tagged->sha1); if (!o) - ret = error("Could not read object %s", + ret = error(_("Could not read object %s"), sha1_to_hex(t->tagged->sha1)); objects[i].item = o; i--; @@ -443,7 +490,7 @@ int cmd_show(int argc, const char **argv, const char *prefix) diff_get_color_opt(&rev.diffopt, DIFF_COMMIT), name, diff_get_color_opt(&rev.diffopt, DIFF_RESET)); - read_tree_recursive((struct tree *)o, "", 0, 0, NULL, + read_tree_recursive((struct tree *)o, "", 0, 0, &match_all, show_tree_object, NULL); rev.shown_one = 1; break; @@ -454,7 +501,7 @@ int cmd_show(int argc, const char **argv, const char *prefix) ret = cmd_log_walk(&rev); break; default: - ret = error("Unknown type: %d", o->type); + ret = error(_("Unknown type: %d"), o->type); } } free(objects); @@ -471,31 +518,17 @@ int cmd_log_reflog(int argc, const char **argv, const char *prefix) git_config(git_log_config, NULL); - if (diff_use_color_default == -1) - diff_use_color_default = git_use_color_default; - init_revisions(&rev, prefix); init_reflog_walk(&rev.reflog_info); - rev.abbrev_commit = 1; rev.verbose_header = 1; memset(&opt, 0, sizeof(opt)); opt.def = "HEAD"; - cmd_log_init(argc, argv, prefix, &rev, &opt); - - /* - * This means that we override whatever commit format the user gave - * on the cmd line. Sad, but cmd_log_init() currently doesn't - * allow us to set a different default. - */ + cmd_log_init_defaults(&rev); + rev.abbrev_commit = 1; rev.commit_format = CMIT_FMT_ONELINE; rev.use_terminator = 1; rev.always_show_header = 1; - - /* - * We get called through "git reflog", so unlike the other log - * routines, we need to set up our pager manually.. - */ - setup_pager(); + cmd_log_init_finish(argc, argv, prefix, &rev, &opt); return cmd_log_walk(&rev); } @@ -507,9 +540,6 @@ int cmd_log(int argc, const char **argv, const char *prefix) git_config(git_log_config, NULL); - if (diff_use_color_default == -1) - diff_use_color_default = git_use_color_default; - init_revisions(&rev, prefix); rev.always_show_header = 1; memset(&opt, 0, sizeof(opt)); @@ -560,7 +590,7 @@ static int git_format_config(const char *var, const char *value, void *cb) { if (!strcmp(var, "format.headers")) { if (!value) - die("format.headers without value"); + die(_("format.headers without value")); add_header(value); return 0; } @@ -623,7 +653,7 @@ static FILE *realstdout = NULL; static const char *output_directory = NULL; static int outdir_offset; -static int reopen_stdout(struct commit *commit, struct rev_info *rev) +static int reopen_stdout(struct commit *commit, struct rev_info *rev, int quiet) { struct strbuf filename = STRBUF_INIT; int suffix_len = strlen(fmt_patch_suffix) + 1; @@ -632,18 +662,18 @@ static int reopen_stdout(struct commit *commit, struct rev_info *rev) strbuf_addstr(&filename, output_directory); if (filename.len >= PATH_MAX - FORMAT_PATCH_NAME_MAX - suffix_len) - return error("name of output directory is too long"); + return error(_("name of output directory is too long")); if (filename.buf[filename.len - 1] != '/') strbuf_addch(&filename, '/'); } get_patch_filename(commit, rev->nr, fmt_patch_suffix, &filename); - if (!DIFF_OPT_TST(&rev->diffopt, QUICK)) + if (!quiet) fprintf(realstdout, "%s\n", filename.buf + outdir_offset); if (freopen(filename.buf, "w", stdout) == NULL) - return error("Cannot open patch file %s", filename.buf); + return error(_("Cannot open patch file %s"), filename.buf); strbuf_release(&filename); return 0; @@ -657,7 +687,7 @@ static void get_patch_ids(struct rev_info *rev, struct patch_ids *ids, const cha unsigned flags1, flags2; if (rev->pending.nr != 2) - die("Need exactly one range."); + die(_("Need exactly one range.")); o1 = rev->pending.objects[0].item; flags1 = o1->flags; @@ -665,7 +695,7 @@ static void get_patch_ids(struct rev_info *rev, struct patch_ids *ids, const cha flags2 = o2->flags; if ((flags1 & UNINTERESTING) == (flags2 & UNINTERESTING)) - die("Not a range."); + die(_("Not a range.")); init_patch_ids(ids); @@ -676,7 +706,7 @@ static void get_patch_ids(struct rev_info *rev, struct patch_ids *ids, const cha add_pending_object(&check_rev, o1, "o1"); add_pending_object(&check_rev, o2, "o2"); if (prepare_revision_walk(&check_rev)) - die("revision walk setup failed"); + die(_("revision walk setup failed")); while ((commit = get_revision(&check_rev)) != NULL) { /* ignore merges */ @@ -702,7 +732,7 @@ static void gen_message_id(struct rev_info *info, char *base) const char *email_end = strrchr(committer, '>'); struct strbuf buf = STRBUF_INIT; if (!email_start || !email_end || email_start > email_end - 1) - die("Could not extract email from committer identity."); + die(_("Could not extract email from committer identity.")); strbuf_addf(&buf, "%s.%lu.git.%.*s", base, (unsigned long) time(NULL), (int)(email_end - email_start - 1), email_start + 1); @@ -718,13 +748,12 @@ static void print_signature(void) static void make_cover_letter(struct rev_info *rev, int use_stdout, int numbered, int numbered_files, struct commit *origin, - int nr, struct commit **list, struct commit *head) + int nr, struct commit **list, struct commit *head, + int quiet) { const char *committer; - const char *subject_start = NULL; const char *body = "*** SUBJECT HERE ***\n\n*** BLURB HERE ***\n"; const char *msg; - const char *extra_headers = rev->extra_headers; struct shortlog log; struct strbuf sb = STRBUF_INIT; int i; @@ -732,9 +761,10 @@ static void make_cover_letter(struct rev_info *rev, int use_stdout, struct diff_options opts; int need_8bit_cte = 0; struct commit *commit = NULL; + struct pretty_print_context pp = {0}; if (rev->commit_format != CMIT_FMT_EMAIL) - die("Cover letter needs email format"); + die(_("Cover letter needs email format")); committer = git_committer_info(0); @@ -754,7 +784,7 @@ static void make_cover_letter(struct rev_info *rev, int use_stdout, sha1_to_hex(head->object.sha1), committer, committer); } - if (!use_stdout && reopen_stdout(commit, rev)) + if (!use_stdout && reopen_stdout(commit, rev, quiet)) return; if (commit) { @@ -763,7 +793,7 @@ static void make_cover_letter(struct rev_info *rev, int use_stdout, free(commit); } - log_write_email_headers(rev, head, &subject_start, &extra_headers, + log_write_email_headers(rev, head, &pp.subject, &pp.after_subject, &need_8bit_cte); for (i = 0; !need_8bit_cte && i < nr; i++) @@ -771,11 +801,11 @@ static void make_cover_letter(struct rev_info *rev, int use_stdout, need_8bit_cte = 1; msg = body; - pp_user_info(NULL, CMIT_FMT_EMAIL, &sb, committer, DATE_RFC2822, - encoding); - pp_title_line(CMIT_FMT_EMAIL, &msg, &sb, subject_start, extra_headers, - encoding, need_8bit_cte); - pp_remainder(CMIT_FMT_EMAIL, &msg, &sb, 0); + pp.fmt = CMIT_FMT_EMAIL; + pp.date_mode = DATE_RFC2822; + pp_user_info(&pp, NULL, &sb, committer, encoding); + pp_title_line(&pp, &msg, &sb, encoding, need_8bit_cte); + pp_remainder(&pp, &msg, &sb, 0); printf("%s\n", sb.buf); strbuf_release(&sb); @@ -827,7 +857,7 @@ static const char *clean_message_id(const char *msg_id) m++; } if (!z) - die("insane in-reply-to: %s", msg_id); + die(_("insane in-reply-to: %s"), msg_id); if (++z == m) return a; return xmemdupz(a, z - a); @@ -900,7 +930,7 @@ static int output_directory_callback(const struct option *opt, const char *arg, { const char **dir = (const char **)opt->value; if (*dir) - die("Two output directories?"); + die(_("Two output directories?")); *dir = arg; return 0; } @@ -995,6 +1025,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) char *add_signoff = NULL; struct strbuf buf = STRBUF_INIT; int use_patch_format = 0; + int quiet = 0; const struct option builtin_format_patch_options[] = { { OPTION_CALLBACK, 'n', "numbered", &numbered, NULL, "use [PATCH n/m] even with a single patch", @@ -1050,6 +1081,8 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) PARSE_OPT_OPTARG, thread_callback }, OPT_STRING(0, "signature", &signature, "signature", "add a signature"), + OPT_BOOLEAN(0, "quiet", &quiet, + "don't print the patch filenames"), OPT_END() }; @@ -1061,8 +1094,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) rev.commit_format = CMIT_FMT_EMAIL; rev.verbose_header = 1; rev.diff = 1; - rev.combine_merges = 0; - rev.ignore_merges = 1; + rev.max_parents = 1; DIFF_OPT_SET(&rev.diffopt, RECURSIVE); rev.subject_prefix = fmt_patch_subject_prefix; memset(&s_r_opt, 0, sizeof(s_r_opt)); @@ -1089,7 +1121,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) committer = git_committer_info(IDENT_ERROR_ON_NO_NAME); endpos = strchr(committer, '>'); if (!endpos) - die("bogus committer info %s", committer); + die(_("bogus committer info %s"), committer); add_signoff = xmemdupz(committer, endpos - committer + 1); } @@ -1134,20 +1166,21 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) numbered = 0; if (numbered && keep_subject) - die ("-n and -k are mutually exclusive."); + die (_("-n and -k are mutually exclusive.")); if (keep_subject && subject_prefix) - die ("--subject-prefix and -k are mutually exclusive."); + die (_("--subject-prefix and -k are mutually exclusive.")); + rev.preserve_subject = keep_subject; argc = setup_revisions(argc, argv, &rev, &s_r_opt); if (argc > 1) - die ("unrecognized argument: %s", argv[1]); + die (_("unrecognized argument: %s"), argv[1]); if (rev.diffopt.output_format & DIFF_FORMAT_NAME) - die("--name-only does not make sense"); + die(_("--name-only does not make sense")); if (rev.diffopt.output_format & DIFF_FORMAT_NAME_STATUS) - die("--name-status does not make sense"); + die(_("--name-status does not make sense")); if (rev.diffopt.output_format & DIFF_FORMAT_CHECKDIFF) - die("--check does not make sense"); + die(_("--check does not make sense")); if (!use_patch_format && (!rev.diffopt.output_format || @@ -1165,12 +1198,14 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) if (!use_stdout) output_directory = set_outdir(prefix, output_directory); + else + setup_pager(); if (output_directory) { if (use_stdout) - die("standard output, or directory, which one?"); + die(_("standard output, or directory, which one?")); if (mkdir(output_directory, 0777) < 0 && errno != EEXIST) - die_errno("Could not create directory '%s'", + die_errno(_("Could not create directory '%s'"), output_directory); } @@ -1224,7 +1259,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) realstdout = xfdopen(xdup(1), "w"); if (prepare_revision_walk(&rev)) - die("revision walk setup failed"); + die(_("revision walk setup failed")); rev.boundary = 1; while ((commit = get_revision(&rev)) != NULL) { if (commit->object.flags & BOUNDARY) { @@ -1233,10 +1268,6 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) continue; } - /* ignore merges */ - if (commit->parents && commit->parents->next) - continue; - if (ignore_if_in_upstream && has_commit_patch_id(commit, &ids)) continue; @@ -1262,7 +1293,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) if (thread) gen_message_id(&rev, "cover"); make_cover_letter(&rev, use_stdout, numbered, numbered_files, - origin, nr, list, head); + origin, nr, list, head, quiet); total++; start_number--; } @@ -1308,8 +1339,8 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) } if (!use_stdout && reopen_stdout(numbered_files ? NULL : commit, - &rev)) - die("Failed to create output files"); + &rev, quiet)) + die(_("Failed to create output files")); shown = log_tree_commit(&rev, commit); free(commit->buffer); commit->buffer = NULL; @@ -1361,6 +1392,22 @@ static const char * const cherry_usage[] = { NULL }; +static void print_commit(char sign, struct commit *commit, int verbose, + int abbrev) +{ + if (!verbose) { + printf("%c %s\n", sign, + find_unique_abbrev(commit->object.sha1, abbrev)); + } else { + struct strbuf buf = STRBUF_INIT; + pp_commit_easy(CMIT_FMT_ONELINE, commit, &buf); + printf("%c %s %s\n", sign, + find_unique_abbrev(commit->object.sha1, abbrev), + buf.buf); + strbuf_release(&buf); + } +} + int cmd_cherry(int argc, const char **argv, const char *prefix) { struct rev_info revs; @@ -1375,7 +1422,7 @@ int cmd_cherry(int argc, const char **argv, const char *prefix) struct option options[] = { OPT__ABBREV(&abbrev), - OPT__VERBOSE(&verbose), + OPT__VERBOSE(&verbose, "be verbose"), OPT_END() }; @@ -1396,9 +1443,9 @@ int cmd_cherry(int argc, const char **argv, const char *prefix) if (!current_branch || !current_branch->merge || !current_branch->merge[0] || !current_branch->merge[0]->dst) { - fprintf(stderr, "Could not find a tracked" + fprintf(stderr, _("Could not find a tracked" " remote branch, please" - " specify <upstream> manually.\n"); + " specify <upstream> manually.\n")); usage_with_options(cherry_usage, options); } @@ -1412,9 +1459,9 @@ int cmd_cherry(int argc, const char **argv, const char *prefix) DIFF_OPT_SET(&revs.diffopt, RECURSIVE); if (add_pending_commit(head, &revs, 0)) - die("Unknown commit %s", head); + die(_("Unknown commit %s"), head); if (add_pending_commit(upstream, &revs, UNINTERESTING)) - die("Unknown commit %s", upstream); + die(_("Unknown commit %s"), upstream); /* Don't say anything if head and upstream are the same. */ if (revs.pending.nr == 2) { @@ -1426,11 +1473,11 @@ int cmd_cherry(int argc, const char **argv, const char *prefix) get_patch_ids(&revs, &ids, prefix); if (limit && add_pending_commit(limit, &revs, UNINTERESTING)) - die("Unknown commit %s", limit); + die(_("Unknown commit %s"), limit); /* reverse the list of commits */ if (prepare_revision_walk(&revs)) - die("revision walk setup failed"); + die(_("revision walk setup failed")); while ((commit = get_revision(&revs)) != NULL) { /* ignore merges */ if (commit->parents && commit->parents->next) @@ -1445,22 +1492,7 @@ int cmd_cherry(int argc, const char **argv, const char *prefix) commit = list->item; if (has_commit_patch_id(commit, &ids)) sign = '-'; - - if (verbose) { - struct strbuf buf = STRBUF_INIT; - struct pretty_print_context ctx = {0}; - pretty_print_commit(CMIT_FMT_ONELINE, commit, - &buf, &ctx); - printf("%c %s %s\n", sign, - find_unique_abbrev(commit->object.sha1, abbrev), - buf.buf); - strbuf_release(&buf); - } - else { - printf("%c %s\n", sign, - find_unique_abbrev(commit->object.sha1, abbrev)); - } - + print_commit(sign, commit, verbose, abbrev); list = list->next; } diff --git a/builtin/ls-files.c b/builtin/ls-files.c index 1b9b8a8b4a..e8a800d3ac 100644 --- a/builtin/ls-files.c +++ b/builtin/ls-files.c @@ -25,6 +25,7 @@ static int show_modified; static int show_killed; static int show_valid_bit; static int line_terminator = '\n'; +static int debug_mode; static const char *prefix; static int max_prefix_len; @@ -162,35 +163,41 @@ static void show_ce_entry(const char *tag, struct cache_entry *ce) ce_stage(ce)); } write_name(ce->name, ce_namelen(ce)); -} - -static int show_one_ru(struct string_list_item *item, void *cbdata) -{ - const char *path = item->string; - struct resolve_undo_info *ui = item->util; - int i, len; - - len = strlen(path); - if (len < max_prefix_len) - return 0; /* outside of the prefix */ - if (!match_pathspec(pathspec, path, len, max_prefix_len, ps_matched)) - return 0; /* uninterested */ - for (i = 0; i < 3; i++) { - if (!ui->mode[i]) - continue; - printf("%s%06o %s %d\t", tag_resolve_undo, ui->mode[i], - find_unique_abbrev(ui->sha1[i], abbrev), - i + 1); - write_name(path, len); + 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); } - return 0; } static void show_ru_info(void) { + struct string_list_item *item; + if (!the_index.resolve_undo) return; - for_each_string_list(the_index.resolve_undo, show_one_ru, NULL); + + for_each_string_list_item(item, the_index.resolve_undo) { + const char *path = item->string; + struct resolve_undo_info *ui = item->util; + int i, len; + + len = strlen(path); + if (len < max_prefix_len) + continue; /* outside of the prefix */ + if (!match_pathspec(pathspec, path, len, max_prefix_len, ps_matched)) + continue; /* uninterested */ + for (i = 0; i < 3; i++) { + if (!ui->mode[i]) + continue; + printf("%s%06o %s %d\t", tag_resolve_undo, ui->mode[i], + find_unique_abbrev(ui->sha1[i], abbrev), + i + 1); + write_name(path, len); + } + } } static void show_files(struct dir_struct *dir) @@ -269,41 +276,6 @@ static void prune_cache(const char *prefix) active_nr = last; } -static const char *pathspec_prefix(const char *prefix) -{ - const char **p, *n, *prev; - unsigned long max; - - if (!pathspec) { - max_prefix_len = prefix ? strlen(prefix) : 0; - return prefix; - } - - prev = NULL; - max = PATH_MAX; - for (p = pathspec; (n = *p) != NULL; p++) { - int i, len = 0; - for (i = 0; i < max; i++) { - char c = n[i]; - if (prev && prev[i] != c) - break; - if (!c || c == '*' || c == '?') - break; - if (c == '/') - len = i+1; - } - prev = n; - if (len < max) { - max = len; - if (!max) - break; - } - } - - max_prefix_len = max; - return max ? xmemdupz(prev, max) : NULL; -} - static void strip_trailing_slash_from_submodules(void) { const char **p; @@ -331,7 +303,7 @@ void overlay_tree_on_cache(const char *tree_name, const char *prefix) { struct tree *tree; unsigned char sha1[20]; - const char **match; + struct pathspec pathspec; struct cache_entry *last_stage0 = NULL; int i; @@ -353,10 +325,11 @@ void overlay_tree_on_cache(const char *tree_name, const char *prefix) static const char *(matchbuf[2]); matchbuf[0] = prefix; matchbuf[1] = NULL; - match = matchbuf; + init_pathspec(&pathspec, matchbuf); + pathspec.items[0].use_wildcard = 0; } else - match = NULL; - if (read_tree(tree, 1, match)) + init_pathspec(&pathspec, NULL); + if (read_tree(tree, 1, &pathspec)) die("unable to read tree entries %s", tree_name); for (i = 0; i < active_nr; i++) { @@ -380,11 +353,13 @@ void overlay_tree_on_cache(const char *tree_name, const char *prefix) } } -int report_path_error(const char *ps_matched, const char **pathspec, int prefix_len) +int report_path_error(const char *ps_matched, const char **pathspec, const char *prefix) { /* * Make sure all pathspec matched; otherwise it is an error. */ + struct strbuf sb = STRBUF_INIT; + const char *name; int num, errors = 0; for (num = 0; pathspec[num]; num++) { int other, found_dup; @@ -409,15 +384,17 @@ int report_path_error(const char *ps_matched, const char **pathspec, int prefix_ if (found_dup) continue; + name = quote_path_relative(pathspec[num], -1, &sb, prefix); error("pathspec '%s' did not match any file(s) known to git.", - pathspec[num] + prefix_len); + name); errors++; } + strbuf_release(&sb); return errors; } static const char * const ls_files_usage[] = { - "git ls-files [options] [<file>]*", + "git ls-files [options] [<file>...]", NULL }; @@ -519,9 +496,13 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix) OPT_STRING(0, "with-tree", &with_tree, "tree-ish", "pretend that paths removed since <tree-ish> are still present"), OPT__ABBREV(&abbrev), + OPT_BOOLEAN(0, "debug", &debug_mode, "show debugging data"), OPT_END() }; + if (argc == 2 && !strcmp(argv[1], "-h")) + usage_with_options(ls_files_usage, builtin_ls_files_options); + memset(&dir, 0, sizeof(dir)); prefix = cmd_prefix; if (prefix) @@ -564,7 +545,8 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix) strip_trailing_slash_from_submodules(); /* Find common prefix for all pathspec's */ - max_prefix = pathspec_prefix(prefix); + max_prefix = pathspec_prefix(prefix, pathspec); + max_prefix_len = max_prefix ? strlen(max_prefix) : 0; /* Treat unmatching pathspec elements as errors */ if (pathspec && error_unmatch) { @@ -599,7 +581,7 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix) if (ps_matched) { int bad; - bad = report_path_error(ps_matched, pathspec, prefix_len); + bad = report_path_error(ps_matched, pathspec, prefix); if (bad) fprintf(stderr, "Did you forget to 'git add'?\n"); diff --git a/builtin/ls-remote.c b/builtin/ls-remote.c index 34480cfad6..10223092a9 100644 --- a/builtin/ls-remote.c +++ b/builtin/ls-remote.c @@ -5,7 +5,7 @@ static const char ls_remote_usage[] = "git ls-remote [--heads] [--tags] [-u <exec> | --upload-pack <exec>]\n" -" [-q|--quiet] [<repository> [<refs>...]]"; +" [-q|--quiet] [--exit-code] [<repository> [<refs>...]]"; /* * Is there one among the list of patterns that match the tail part @@ -32,9 +32,10 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix) { int i; const char *dest = NULL; - int nongit; unsigned flags = 0; + int get_url = 0; int quiet = 0; + int status = 0; const char *uploadpack = NULL; const char **pattern = NULL; @@ -42,8 +43,6 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix) struct transport *transport; const struct ref *ref; - setup_git_directory_gently(&nongit); - for (i = 1; i < argc; i++) { const char *arg = argv[i]; @@ -72,6 +71,15 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix) quiet = 1; continue; } + if (!strcmp("--get-url", arg)) { + get_url = 1; + continue; + } + if (!strcmp("--exit-code", arg)) { + /* return this code if no refs are reported */ + status = 2; + continue; + } usage(ls_remote_usage); } dest = arg; @@ -97,6 +105,12 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix) } if (!remote->url_nr) die("remote %s has no configured URL", dest); + + if (get_url) { + printf("%s\n", *remote->url); + return 0; + } + transport = transport_get(remote, NULL); if (uploadpack != NULL) transport_set_option(transport, TRANS_OPT_UPLOADPACK, uploadpack); @@ -113,6 +127,7 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix) if (!tail_match(pattern, ref->name)) continue; printf("%s %s\n", sha1_to_hex(ref->old_sha1), ref->name); + status = 0; /* we found something */ } - return 0; + return status; } diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c index dc86b0d9a9..6b666e1e87 100644 --- a/builtin/ls-tree.c +++ b/builtin/ls-tree.c @@ -19,12 +19,12 @@ static int line_termination = '\n'; #define LS_SHOW_SIZE 16 static int abbrev; static int ls_options; -static const char **pathspec; +static struct pathspec pathspec; static int chomp_prefix; static const char *ls_tree_prefix; static const char * const ls_tree_usage[] = { - "git ls-tree [<options>] <tree-ish> [path...]", + "git ls-tree [<options>] <tree-ish> [<path>...]", NULL }; @@ -35,7 +35,7 @@ static int show_recursive(const char *base, int baselen, const char *pathname) if (ls_options & LS_RECURSIVE) return 1; - s = pathspec; + s = pathspec.raw; if (!s) return 0; @@ -52,6 +52,8 @@ static int show_recursive(const char *base, int baselen, const char *pathname) speclen = strlen(spec); if (speclen <= len) continue; + if (spec[len] && spec[len] != '/') + continue; if (memcmp(pathname, spec, len)) continue; return 1; @@ -118,7 +120,7 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix) { unsigned char sha1[20]; struct tree *tree; - int full_tree = 0; + int i, full_tree = 0; const struct option ls_tree_options[] = { OPT_BIT('d', NULL, &ls_options, "only show trees", LS_TREE_ONLY), @@ -164,11 +166,12 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix) if (get_sha1(argv[0], sha1)) die("Not a valid object name %s", argv[0]); - pathspec = get_pathspec(prefix, argv + 1); + init_pathspec(&pathspec, get_pathspec(prefix, argv + 1)); + for (i = 0; i < pathspec.nr; i++) + pathspec.items[i].use_wildcard = 0; + pathspec.has_wildcard = 0; tree = parse_tree_indirect(sha1); if (!tree) die("not a tree object"); - read_tree_recursive(tree, "", 0, 0, pathspec, show_tree, NULL); - - return 0; + return !!read_tree_recursive(tree, "", 0, 0, &pathspec, show_tree, NULL); } diff --git a/builtin/mailinfo.c b/builtin/mailinfo.c index 2320d981ce..bfb32b7233 100644 --- a/builtin/mailinfo.c +++ b/builtin/mailinfo.c @@ -400,7 +400,7 @@ static int read_one_header_line(struct strbuf *line, FILE *in) break; if (strbuf_getline(&continuation, in, '\n')) break; - continuation.buf[0] = '\n'; + continuation.buf[0] = ' '; strbuf_rtrim(&continuation); strbuf_addbuf(line, &continuation); } @@ -1032,7 +1032,7 @@ int cmd_mailinfo(int argc, const char **argv, const char *prefix) */ git_config(git_mailinfo_config, NULL); - def_charset = (git_commit_encoding ? git_commit_encoding : "UTF-8"); + def_charset = get_commit_output_encoding(); metainfo_charset = def_charset; while (1 < argc && argv[1][0] == '-') { diff --git a/builtin/mailsplit.c b/builtin/mailsplit.c index e4560da191..2d4327801e 100644 --- a/builtin/mailsplit.c +++ b/builtin/mailsplit.c @@ -10,7 +10,7 @@ #include "strbuf.h" static const char git_mailsplit_usage[] = -"git mailsplit [-d<prec>] [-f<n>] [-b] [--keep-cr] -o<directory> [<mbox>|<Maildir>...]"; +"git mailsplit [-d<prec>] [-f<n>] [-b] [--keep-cr] -o<directory> [(<mbox>|<Maildir>)...]"; static int is_from_line(const char *line, int len) { @@ -137,7 +137,7 @@ static int split_maildir(const char *maildir, const char *dir, char name[PATH_MAX]; int ret = -1; int i; - struct string_list list = {NULL, 0, 0, 1}; + struct string_list list = STRING_LIST_INIT_DUP; if (populate_maildir_list(&list, maildir) < 0) goto out; diff --git a/builtin/merge-base.c b/builtin/merge-base.c index 54e7ec2237..4f30f1b0c8 100644 --- a/builtin/merge-base.c +++ b/builtin/merge-base.c @@ -24,6 +24,8 @@ static int show_merge_base(struct commit **rev, int rev_nr, int show_all) static const char * const merge_base_usage[] = { "git merge-base [-a|--all] <commit> <commit>...", + "git merge-base [-a|--all] --octopus <commit>...", + "git merge-base --independent <commit>...", NULL }; @@ -41,21 +43,58 @@ static struct commit *get_commit_reference(const char *arg) return r; } +static int handle_octopus(int count, const char **args, int reduce, int show_all) +{ + struct commit_list *revs = NULL; + struct commit_list *result; + int i; + + if (reduce) + show_all = 1; + + for (i = count - 1; i >= 0; i--) + commit_list_insert(get_commit_reference(args[i]), &revs); + + result = reduce ? reduce_heads(revs) : get_octopus_merge_bases(revs); + + if (!result) + return 1; + + while (result) { + printf("%s\n", sha1_to_hex(result->item->object.sha1)); + if (!show_all) + return 0; + result = result->next; + } + + return 0; +} + int cmd_merge_base(int argc, const char **argv, const char *prefix) { struct commit **rev; int rev_nr = 0; int show_all = 0; + int octopus = 0; + int reduce = 0; struct option options[] = { - OPT_BOOLEAN('a', "all", &show_all, "outputs all common ancestors"), + 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_END() }; git_config(git_default_config, NULL); argc = parse_options(argc, argv, prefix, options, merge_base_usage, 0); - if (argc < 2) + if (!octopus && !reduce && argc < 2) usage_with_options(merge_base_usage, options); + if (reduce && (show_all || octopus)) + die("--independent cannot be used with other options"); + + if (octopus || reduce) + return handle_octopus(argc, argv, reduce, show_all); + rev = xmalloc(argc * sizeof(*rev)); while (argc-- > 0) rev[rev_nr++] = get_commit_reference(*argv++); diff --git a/builtin/merge-file.c b/builtin/merge-file.c index b8e9e5ba01..237abd3c0b 100644 --- a/builtin/merge-file.c +++ b/builtin/merge-file.c @@ -28,7 +28,7 @@ int cmd_merge_file(int argc, const char **argv, const char *prefix) xmparam_t xmp = {{0}}; int ret = 0, i = 0, to_stdout = 0; int quiet = 0; - int nongit; + 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), @@ -40,7 +40,7 @@ int cmd_merge_file(int argc, const char **argv, const char *prefix) XDL_MERGE_FAVOR_UNION), OPT_INTEGER(0, "marker-size", &xmp.marker_size, "for conflicts, use this marker size"), - OPT__QUIET(&quiet), + OPT__QUIET(&quiet, "do not warn about conflicts"), OPT_CALLBACK('L', NULL, names, "name", "set labels for file1/orig_file/file2", &label_cb), OPT_END(), @@ -50,8 +50,7 @@ int cmd_merge_file(int argc, const char **argv, const char *prefix) xmp.style = 0; xmp.favor = 0; - prefix = setup_git_directory_gently(&nongit); - if (!nongit) { + if (startup_info->have_repository) { /* Read the configuration file */ git_config(git_xmerge_config, NULL); if (0 <= git_xmerge_style) @@ -67,10 +66,14 @@ int cmd_merge_file(int argc, const char **argv, const char *prefix) "%s\n", strerror(errno)); } + if (prefix) + prefixlen = strlen(prefix); + for (i = 0; i < 3; i++) { + const char *fname = prefix_filename(prefix, prefixlen, argv[i]); if (!names[i]) names[i] = argv[i]; - if (read_mmfile(mmfs + i, argv[i])) + if (read_mmfile(mmfs + i, fname)) return -1; if (buffer_is_binary(mmfs[i].ptr, mmfs[i].size)) return error("Cannot merge binary files: %s\n", diff --git a/builtin/merge-index.c b/builtin/merge-index.c index 2c4cf5e559..2338832587 100644 --- a/builtin/merge-index.c +++ b/builtin/merge-index.c @@ -1,6 +1,5 @@ -#include "cache.h" +#include "builtin.h" #include "run-command.h" -#include "exec_cmd.h" static const char *pgm; static int one_shot, quiet; diff --git a/builtin/merge-recursive.c b/builtin/merge-recursive.c index d8875d5892..3a64f5d0bd 100644 --- a/builtin/merge-recursive.c +++ b/builtin/merge-recursive.c @@ -1,7 +1,11 @@ -#include "cache.h" +#include "builtin.h" #include "commit.h" #include "tag.h" #include "merge-recursive.h" +#include "xdiff-interface.h" + +static const char builtin_merge_recursive_usage[] = + "git %s <base>... -- <head> <remote> ..."; static const char *better_branch_name(const char *branch) { @@ -29,7 +33,7 @@ int cmd_merge_recursive(int argc, const char **argv, const char *prefix) o.subtree_shift = ""; if (argc < 4) - usagef("%s <base>... -- <head> <remote> ...", argv[0]); + usagef(builtin_merge_recursive_usage, argv[0]); for (i = 1; i < argc; ++i) { const char *arg = argv[i]; @@ -37,15 +41,7 @@ int cmd_merge_recursive(int argc, const char **argv, const char *prefix) if (!prefixcmp(arg, "--")) { if (!arg[2]) break; - if (!strcmp(arg+2, "ours")) - o.recursive_variant = MERGE_RECURSIVE_OURS; - else if (!strcmp(arg+2, "theirs")) - o.recursive_variant = MERGE_RECURSIVE_THEIRS; - else if (!strcmp(arg+2, "subtree")) - o.subtree_shift = ""; - else if (!prefixcmp(arg+2, "subtree=")) - o.subtree_shift = arg + 10; - else + if (parse_merge_opt(&o, arg + 2)) die("Unknown option %s", arg); continue; } diff --git a/builtin/merge-tree.c b/builtin/merge-tree.c index 9b25ddc979..897a563bc6 100644 --- a/builtin/merge-tree.c +++ b/builtin/merge-tree.c @@ -1,8 +1,9 @@ -#include "cache.h" +#include "builtin.h" #include "tree-walk.h" #include "xdiff-interface.h" #include "blob.h" #include "exec_cmd.h" +#include "merge-file.h" static const char merge_tree_usage[] = "git merge-tree <base-tree> <branch1> <branch2>"; static int resolve_directories = 1; @@ -54,8 +55,6 @@ static const char *explanation(struct merge_list *entry) return "removed in remote"; } -extern void *merge_file(const char *, struct blob *, struct blob *, struct blob *, unsigned long *); - static void *result(struct merge_list *entry, unsigned long *size) { enum object_type type; diff --git a/builtin/merge.c b/builtin/merge.c index 37ce4f589f..ab4077f272 100644 --- a/builtin/merge.c +++ b/builtin/merge.c @@ -25,6 +25,7 @@ #include "help.h" #include "merge-recursive.h" #include "resolve-undo.h" +#include "remote.h" #define DEFAULT_TWOHEAD (1<<0) #define DEFAULT_OCTOPUS (1<<1) @@ -37,12 +38,13 @@ struct strategy { }; static const char * const builtin_merge_usage[] = { - "git merge [options] <remote>...", - "git merge [options] <msg> HEAD <remote>", + "git merge [options] [<commit>...]", + "git merge [options] <msg> HEAD <commit>", + "git merge --abort", NULL }; -static int show_diffstat = 1, option_log, squash; +static int show_diffstat = 1, shortlog_len, squash; static int option_commit = 1, allow_fast_forward = 1; static int fast_forward_only; static int allow_trivial = 1, have_message; @@ -54,8 +56,13 @@ static size_t use_strategies_nr, use_strategies_alloc; static const char **xopts; static size_t xopts_nr, xopts_alloc; static const char *branch; +static char *branch_mergeoptions; +static int option_renormalize; static int verbosity; static int allow_rerere_auto; +static int abort_current_merge; +static int show_progress = -1; +static int default_to_upstream; static struct strategy all_strategy[] = { { "recursive", DEFAULT_TWOHEAD | NO_TRIVIAL }, @@ -78,7 +85,7 @@ static int option_parse_message(const struct option *opt, strbuf_addf(buf, "%s%s", buf->len ? "\n\n" : "", arg); have_message = 1; } else - return error("switch `m' requires a value"); + return error(_("switch `m' requires a value")); return 0; } @@ -115,13 +122,13 @@ static struct strategy *get_strategy(const char *name) exclude_cmds(&main_cmds, ¬_strategies); } if (!is_in_cmdlist(&main_cmds, name) && !is_in_cmdlist(&other_cmds, name)) { - fprintf(stderr, "Could not find merge strategy '%s'.\n", name); - fprintf(stderr, "Available strategies are:"); + fprintf(stderr, _("Could not find merge strategy '%s'.\n"), name); + fprintf(stderr, _("Available strategies are:")); for (i = 0; i < main_cmds.cnt; i++) fprintf(stderr, " %s", main_cmds.names[i]->name); fprintf(stderr, ".\n"); if (other_cmds.cnt) { - fprintf(stderr, "Available custom strategies are:"); + fprintf(stderr, _("Available custom strategies are:")); for (i = 0; i < other_cmds.cnt; i++) fprintf(stderr, " %s", other_cmds.names[i]->name); fprintf(stderr, ".\n"); @@ -131,6 +138,7 @@ static struct strategy *get_strategy(const char *name) ret = xcalloc(1, sizeof(struct strategy)); ret->name = xstrdup(name); + ret->attr = NO_TRIVIAL; return ret; } @@ -175,8 +183,9 @@ static struct option builtin_merge_options[] = { OPT_BOOLEAN(0, "stat", &show_diffstat, "show a diffstat at the end of the merge"), OPT_BOOLEAN(0, "summary", &show_diffstat, "(synonym to --stat)"), - OPT_BOOLEAN(0, "log", &option_log, - "add list of one-line log to merge commit message"), + { OPTION_INTEGER, 0, "log", &shortlog_len, "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"), OPT_BOOLEAN(0, "commit", &option_commit, @@ -191,9 +200,12 @@ static struct option builtin_merge_options[] = { OPT_CALLBACK('X', "strategy-option", &xopts, "option=value", "option for selected merge strategy", option_parse_x), OPT_CALLBACK('m', "message", &merge_msg, "message", - "message to be used for the merge commit (if any)", + "merge commit message (for a non-fast-forward merge)", option_parse_message), OPT__VERBOSITY(&verbosity), + OPT_BOOLEAN(0, "abort", &abort_current_merge, + "abort the current in-progress merge"), + OPT_SET_INT(0, "progress", &show_progress, "force progress reporting", 1), OPT_END() }; @@ -218,17 +230,35 @@ static void save_state(void) cp.git_cmd = 1; if (start_command(&cp)) - die("could not run stash."); + die(_("could not run stash.")); len = strbuf_read(&buffer, cp.out, 1024); close(cp.out); if (finish_command(&cp) || len < 0) - die("stash failed"); + die(_("stash failed")); else if (!len) return; strbuf_setlen(&buffer, buffer.len-1); if (get_sha1(buffer.buf, stash)) - die("not a valid object: %s", buffer.buf); + die(_("not a valid object: %s"), buffer.buf); +} + +static void read_empty(unsigned const char *sha1, int verbose) +{ + int i = 0; + const char *args[7]; + + args[i++] = "read-tree"; + if (verbose) + args[i++] = "-v"; + args[i++] = "-m"; + args[i++] = "-u"; + args[i++] = EMPTY_TREE_SHA1_HEX; + args[i++] = sha1_to_hex(sha1); + args[i] = NULL; + + if (run_command_v_opt(args, RUN_GIT_CMD)) + die(_("read-tree failed")); } static void reset_hard(unsigned const char *sha1, int verbose) @@ -245,7 +275,7 @@ static void reset_hard(unsigned const char *sha1, int verbose) args[i] = NULL; if (run_command_v_opt(args, RUN_GIT_CMD)) - die("read-tree failed"); + die(_("read-tree failed")); } static void restore_state(void) @@ -274,7 +304,7 @@ static void restore_state(void) static void finish_up_to_date(const char *msg) { if (verbosity >= 0) - printf("%s%s\n", squash ? " (nothing to squash)" : "", msg); + printf("%s%s\n", squash ? _(" (nothing to squash)") : "", msg); drop_save(); } @@ -287,10 +317,10 @@ static void squash_message(void) int fd; struct pretty_print_context ctx = {0}; - printf("Squash commit -- not updating HEAD\n"); + printf(_("Squash commit -- not updating HEAD\n")); fd = open(git_path("SQUASH_MSG"), O_WRONLY | O_CREAT, 0666); if (fd < 0) - die_errno("Could not write to '%s'", git_path("SQUASH_MSG")); + die_errno(_("Could not write to '%s'"), git_path("SQUASH_MSG")); init_revisions(&rev, NULL); rev.ignore_merges = 1; @@ -305,22 +335,23 @@ static void squash_message(void) setup_revisions(0, NULL, &rev, NULL); if (prepare_revision_walk(&rev)) - die("revision walk setup failed"); + die(_("revision walk setup failed")); ctx.abbrev = rev.abbrev; ctx.date_mode = rev.date_mode; + ctx.fmt = rev.commit_format; strbuf_addstr(&out, "Squashed commit of the following:\n"); while ((commit = get_revision(&rev)) != NULL) { strbuf_addch(&out, '\n'); strbuf_addf(&out, "commit %s\n", sha1_to_hex(commit->object.sha1)); - pretty_print_commit(rev.commit_format, commit, &out, &ctx); + pretty_print_commit(&ctx, commit, &out); } if (write(fd, out.buf, out.len) < 0) - die_errno("Writing SQUASH_MSG"); + die_errno(_("Writing SQUASH_MSG")); if (close(fd)) - die_errno("Finishing SQUASH_MSG"); + die_errno(_("Finishing SQUASH_MSG")); strbuf_release(&out); } @@ -340,7 +371,7 @@ static void finish(const unsigned char *new_head, const char *msg) squash_message(); } else { if (verbosity >= 0 && !merge_msg.len) - printf("No merge message -- not updating HEAD\n"); + printf(_("No merge message -- not updating HEAD\n")); else { const char *argv_gc_auto[] = { "gc", "--auto", NULL }; update_ref(reflog_message.buf, "HEAD", @@ -359,10 +390,8 @@ static void finish(const unsigned char *new_head, const char *msg) opts.output_format |= DIFF_FORMAT_SUMMARY | DIFF_FORMAT_DIFFSTAT; opts.detect_rename = DIFF_DETECT_RENAME; - if (diff_use_color_default > 0) - DIFF_OPT_SET(&opts, COLOR_DIFF); if (diff_setup_done(&opts) < 0) - die("diff_setup_done failed"); + die(_("diff_setup_done failed")); diff_tree_sha1(head, new_head, "", &opts); diffcore_std(&opts); diff_flush(&opts); @@ -391,7 +420,7 @@ static void merge_name(const char *remote, struct strbuf *msg) memset(branch_head, 0, sizeof(branch_head)); remote_head = peel_to_type(remote, 0, NULL, OBJ_COMMIT); if (!remote_head) - die("'%s' does not point to a commit", remote); + die(_("'%s' does not point to a commit"), remote); if (dwim_ref(remote, strlen(remote), branch_head, &found_ref) > 0) { if (!prefixcmp(found_ref, "refs/heads/")) { @@ -400,7 +429,7 @@ static void merge_name(const char *remote, struct strbuf *msg) goto cleanup; } if (!prefixcmp(found_ref, "refs/remotes/")) { - strbuf_addf(msg, "%s\t\tremote branch '%s' of .\n", + strbuf_addf(msg, "%s\t\tremote-tracking branch '%s' of .\n", sha1_to_hex(branch_head), remote); goto cleanup; } @@ -437,7 +466,7 @@ static void merge_name(const char *remote, struct strbuf *msg) strbuf_addstr(&truname, "refs/heads/"); strbuf_addstr(&truname, remote); strbuf_setlen(&truname, truname.len - len); - if (resolve_ref(truname.buf, buf_sha, 0, NULL)) { + if (resolve_ref(truname.buf, buf_sha, 1, NULL)) { strbuf_addf(msg, "%s\t\tbranch '%s'%s of .\n", sha1_to_hex(remote_head->sha1), @@ -456,7 +485,7 @@ static void merge_name(const char *remote, struct strbuf *msg) fp = fopen(git_path("FETCH_HEAD"), "r"); if (!fp) - die_errno("could not open '%s' for reading", + die_errno(_("could not open '%s' for reading"), git_path("FETCH_HEAD")); strbuf_getline(&line, fp, '\n'); fclose(fp); @@ -474,25 +503,34 @@ cleanup: strbuf_release(&bname); } +static void parse_branch_merge_options(char *bmo) +{ + const char **argv; + int argc; + + if (!bmo) + return; + argc = split_cmdline(bmo, &argv); + if (argc < 0) + die(_("Bad branch.%s.mergeoptions string: %s"), branch, + split_cmdline_strerror(argc)); + argv = xrealloc(argv, sizeof(*argv) * (argc + 2)); + memmove(argv + 1, argv, sizeof(*argv) * (argc + 1)); + argc++; + argv[0] = "branch.*.mergeoptions"; + parse_options(argc, argv, NULL, builtin_merge_options, + builtin_merge_usage, 0); + free(argv); +} + static int git_merge_config(const char *k, const char *v, void *cb) { if (branch && !prefixcmp(k, "branch.") && !prefixcmp(k + 7, branch) && !strcmp(k + 7 + strlen(branch), ".mergeoptions")) { - const char **argv; - int argc; - char *buf; - - buf = xstrdup(v); - argc = split_cmdline(buf, &argv); - if (argc < 0) - die("Bad branch.%s.mergeoptions string", branch); - argv = xrealloc(argv, sizeof(*argv) * (argc + 2)); - memmove(argv + 1, argv, sizeof(*argv) * (argc + 1)); - argc++; - parse_options(argc, argv, NULL, builtin_merge_options, - builtin_merge_usage, 0); - free(buf); + free(branch_mergeoptions); + branch_mergeoptions = xstrdup(v); + return 0; } if (!strcmp(k, "merge.diffstat") || !strcmp(k, "merge.stat")) @@ -501,8 +539,29 @@ static int git_merge_config(const char *k, const char *v, void *cb) return git_config_string(&pull_twohead, k, v); else if (!strcmp(k, "pull.octopus")) return git_config_string(&pull_octopus, k, v); - else if (!strcmp(k, "merge.log") || !strcmp(k, "merge.summary")) - option_log = git_config_bool(k, v); + else if (!strcmp(k, "merge.renormalize")) + option_renormalize = git_config_bool(k, v); + else if (!strcmp(k, "merge.log") || !strcmp(k, "merge.summary")) { + int is_bool; + shortlog_len = git_config_bool_or_int(k, v, &is_bool); + if (!is_bool && shortlog_len < 0) + return error(_("%s: negative length %s"), k, v); + if (is_bool && shortlog_len) + shortlog_len = DEFAULT_MERGE_LOG_LEN; + return 0; + } else if (!strcmp(k, "merge.ff")) { + int boolval = git_config_maybe_bool(k, v); + if (0 <= boolval) { + allow_fast_forward = boolval; + } else if (v && !strcmp(v, "only")) { + allow_fast_forward = 1; + fast_forward_only = 1; + } /* do not barf on values from future versions of git */ + return 0; + } else if (!strcmp(k, "merge.defaulttoupstream")) { + default_to_upstream = git_config_bool(k, v); + return 0; + } return git_diff_ui_config(k, v, cb); } @@ -545,10 +604,19 @@ static int read_tree_trivial(unsigned char *common, unsigned char *head, static void write_tree_trivial(unsigned char *sha1) { if (write_cache_as_tree(sha1, 0, NULL)) - die("git write-tree failed to write a tree"); + die(_("git write-tree failed to write a tree")); } -int try_merge_command(const char *strategy, struct commit_list *common, +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; @@ -567,11 +635,11 @@ int try_merge_command(const char *strategy, struct commit_list *common, args[i++] = s; } for (j = common; j; j = j->next) - args[i++] = xstrdup(sha1_to_hex(j->item->object.sha1)); + args[i++] = xstrdup(merge_argument(j->item)); args[i++] = "--"; args[i++] = head_arg; for (j = remotes; j; j = j->next) - args[i++] = xstrdup(sha1_to_hex(j->item->object.sha1)); + args[i++] = xstrdup(merge_argument(j->item)); args[i] = NULL; ret = run_command_v_opt(args, RUN_GIT_CMD); strbuf_release(&buf); @@ -586,7 +654,7 @@ int try_merge_command(const char *strategy, struct commit_list *common, free(args); discard_cache(); if (read_cache() < 0) - die("failed to read the cache"); + die(_("failed to read the cache")); resolve_undo_clear(); return ret; @@ -603,7 +671,7 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common, if (active_cache_changed && (write_cache(index_fd, active_cache, active_nr) || commit_locked_index(lock))) - return error("Unable to write index."); + return error(_("Unable to write index.")); rollback_lock_file(lock); if (!strcmp(strategy, "recursive") || !strcmp(strategy, "subtree")) { @@ -616,7 +684,7 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common, struct commit_list *j; if (remoteheads->next) { - error("Not handling anything other than two heads merge."); + error(_("Not handling anything other than two heads merge.")); return 2; } @@ -624,18 +692,13 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common, if (!strcmp(strategy, "subtree")) o.subtree_shift = ""; - for (x = 0; x < xopts_nr; x++) { - if (!strcmp(xopts[x], "ours")) - o.recursive_variant = MERGE_RECURSIVE_OURS; - else if (!strcmp(xopts[x], "theirs")) - o.recursive_variant = MERGE_RECURSIVE_THEIRS; - else if (!strcmp(xopts[x], "subtree")) - o.subtree_shift = ""; - else if (!prefixcmp(xopts[x], "subtree=")) - o.subtree_shift = xopts[x]+8; - else - die("Unknown option for merge-recursive: -X%s", xopts[x]); - } + o.renormalize = option_renormalize; + o.show_rename_progress = + show_progress == -1 ? isatty(2) : show_progress; + + for (x = 0; x < xopts_nr; x++) + if (parse_merge_opt(&o, xopts[x])) + die(_("Unknown option for merge-recursive: -X%s"), xopts[x]); o.branch1 = head_arg; o.branch2 = remoteheads->item->util; @@ -649,11 +712,12 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common, if (active_cache_changed && (write_cache(index_fd, active_cache, active_nr) || commit_locked_index(lock))) - die ("unable to write %s", get_index_file()); + die (_("unable to write %s"), get_index_file()); rollback_lock_file(lock); return clean ? 0 : 1; } else { - return try_merge_command(strategy, common, head_arg, remoteheads); + return try_merge_command(strategy, xopts_nr, xopts, + common, head_arg, remoteheads); } } @@ -704,7 +768,7 @@ int checkout_fast_forward(const unsigned char *head, const unsigned char *remote opts.verbose_update = 1; opts.merge = 1; opts.fn = twoway_merge; - opts.msgs = get_porcelain_error_msgs(); + setup_unpack_trees_porcelain(&opts, "merge"); trees[nr_trees] = parse_tree_indirect(head); if (!trees[nr_trees++]) @@ -720,7 +784,7 @@ int checkout_fast_forward(const unsigned char *head, const unsigned char *remote return -1; if (write_cache(fd, active_cache, active_nr) || commit_locked_index(lock_file)) - die("unable to write new index file"); + die(_("unable to write new index file")); return 0; } @@ -768,17 +832,44 @@ static void add_strategies(const char *string, unsigned attr) } +static void write_merge_msg(void) +{ + int fd = open(git_path("MERGE_MSG"), O_WRONLY | O_CREAT, 0666); + if (fd < 0) + die_errno(_("Could not open '%s' for writing"), + git_path("MERGE_MSG")); + if (write_in_full(fd, merge_msg.buf, merge_msg.len) != merge_msg.len) + die_errno(_("Could not write to '%s'"), git_path("MERGE_MSG")); + close(fd); +} + +static void read_merge_msg(void) +{ + strbuf_reset(&merge_msg); + if (strbuf_read_file(&merge_msg, git_path("MERGE_MSG"), 0) < 0) + die_errno(_("Could not read from '%s'"), git_path("MERGE_MSG")); +} + +static void run_prepare_commit_msg(void) +{ + write_merge_msg(); + run_hook(get_index_file(), "prepare-commit-msg", + git_path("MERGE_MSG"), "merge", NULL, NULL); + read_merge_msg(); +} + static int merge_trivial(void) { unsigned char result_tree[20], result_commit[20]; struct commit_list *parent = xmalloc(sizeof(*parent)); write_tree_trivial(result_tree); - printf("Wonderful.\n"); + printf(_("Wonderful.\n")); parent->item = lookup_commit(head); parent->next = xmalloc(sizeof(*parent->next)); parent->next->item = remoteheads->item; parent->next->next = NULL; + run_prepare_commit_msg(); commit_tree(merge_msg.buf, result_tree, parent, result_commit, NULL); finish(result_commit, "In-index merge"); drop_save(); @@ -808,22 +899,23 @@ static int finish_automerge(struct commit_list *common, } free_commit_list(remoteheads); strbuf_addch(&merge_msg, '\n'); + run_prepare_commit_msg(); commit_tree(merge_msg.buf, result_tree, parents, result_commit, NULL); - strbuf_addf(&buf, "Merge made by %s.", wt_strategy); + strbuf_addf(&buf, "Merge made by the '%s' strategy.", wt_strategy); finish(result_commit, buf.buf); strbuf_release(&buf); drop_save(); return 0; } -static int suggest_conflicts(void) +static int suggest_conflicts(int renormalizing) { FILE *fp; int pos; fp = fopen(git_path("MERGE_MSG"), "a"); if (!fp) - die_errno("Could not open '%s' for writing", + die_errno(_("Could not open '%s' for writing"), git_path("MERGE_MSG")); fprintf(fp, "\nConflicts:\n"); for (pos = 0; pos < active_nr; pos++) { @@ -839,8 +931,8 @@ static int suggest_conflicts(void) } fclose(fp); rerere(allow_rerere_auto); - printf("Automatic merge failed; " - "fix conflicts and then commit the result.\n"); + printf(_("Automatic merge failed; " + "fix conflicts and then commit the result.\n")); return 1; } @@ -854,7 +946,7 @@ static struct commit *is_old_style_invocation(int argc, const char **argv) return NULL; second_token = lookup_commit_reference_gently(second_sha1, 0); if (!second_token) - die("'%s' is not a commit", argv[1]); + die(_("'%s' is not a commit"), argv[1]); if (hashcmp(second_token->object.sha1, head)) return NULL; } @@ -884,6 +976,35 @@ static int evaluate_result(void) return cnt; } +/* + * Pretend as if the user told us to merge with the tracking + * branch we have for the upstream of the current branch + */ +static int setup_with_upstream(const char ***argv) +{ + struct branch *branch = branch_get(NULL); + int i; + const char **args; + + if (!branch) + die(_("No current branch.")); + if (!branch->remote) + die(_("No remote for the current branch.")); + if (!branch->merge_nr) + die(_("No default upstream defined for the current branch.")); + + args = xcalloc(branch->merge_nr + 1, sizeof(char *)); + for (i = 0; i < branch->merge_nr; i++) { + if (!branch->merge[i]->dst) + die(_("No remote tracking branch for %s from %s"), + branch->merge[i]->src, branch->remote_name); + args[i] = branch->merge[i]->dst; + } + args[i] = NULL; + *argv = args; + return i; +} + int cmd_merge(int argc, const char **argv, const char *prefix) { unsigned char result_tree[20]; @@ -895,22 +1016,9 @@ int cmd_merge(int argc, const char **argv, const char *prefix) const char *best_strategy = NULL, *wt_strategy = NULL; struct commit_list **remotes = &remoteheads; - if (read_cache_unmerged()) { - die_resolve_conflict("merge"); - } - if (file_exists(git_path("MERGE_HEAD"))) { - /* - * There is no unmerged entry, don't advise 'git - * add/rm <file>', just 'git commit'. - */ - if (advice_resolve_conflict) - die("You have not concluded your merge (MERGE_HEAD exists).\n" - "Please, commit your changes before you can merge."); - else - die("You have not concluded your merge (MERGE_HEAD exists)."); - } + if (argc == 2 && !strcmp(argv[1], "-h")) + usage_with_options(builtin_merge_usage, builtin_merge_options); - resolve_undo_clear(); /* * Check if we are _not_ on a detached HEAD, i.e. if there is a * current branch. @@ -923,24 +1031,66 @@ int cmd_merge(int argc, const char **argv, const char *prefix) git_config(git_merge_config, NULL); - /* for color.ui */ - if (diff_use_color_default == -1) - diff_use_color_default = git_use_color_default; - + if (branch_mergeoptions) + parse_branch_merge_options(branch_mergeoptions); argc = parse_options(argc, argv, prefix, builtin_merge_options, builtin_merge_usage, 0); + + if (verbosity < 0 && show_progress == -1) + show_progress = 0; + + if (abort_current_merge) { + int nargc = 2; + const char *nargv[] = {"reset", "--merge", NULL}; + + if (!file_exists(git_path("MERGE_HEAD"))) + die(_("There is no merge to abort (MERGE_HEAD missing).")); + + /* Invoke 'git reset --merge' */ + return cmd_reset(nargc, nargv, prefix); + } + + if (read_cache_unmerged()) + die_resolve_conflict("merge"); + + if (file_exists(git_path("MERGE_HEAD"))) { + /* + * There is no unmerged entry, don't advise 'git + * add/rm <file>', just 'git commit'. + */ + if (advice_resolve_conflict) + die(_("You have not concluded your merge (MERGE_HEAD exists).\n" + "Please, commit your changes before you can merge.")); + else + die(_("You have not concluded your merge (MERGE_HEAD exists).")); + } + if (file_exists(git_path("CHERRY_PICK_HEAD"))) { + if (advice_resolve_conflict) + die(_("You have not concluded your cherry-pick (CHERRY_PICK_HEAD exists).\n" + "Please, commit your changes before you can merge.")); + else + die(_("You have not concluded your cherry-pick (CHERRY_PICK_HEAD exists).")); + } + resolve_undo_clear(); + if (verbosity < 0) show_diffstat = 0; if (squash) { if (!allow_fast_forward) - die("You cannot combine --squash with --no-ff."); + die(_("You cannot combine --squash with --no-ff.")); option_commit = 0; } if (!allow_fast_forward && fast_forward_only) - die("You cannot combine --no-ff with --ff-only."); + die(_("You cannot combine --no-ff with --ff-only.")); + if (!abort_current_merge) { + if (!argc && default_to_upstream) + argc = setup_with_upstream(&argv); + else if (argc == 1 && !strcmp(argv[0], "-")) + argv[0] = "@{-1}"; + } if (!argc) usage_with_options(builtin_merge_usage, builtin_merge_options); @@ -967,19 +1117,19 @@ int cmd_merge(int argc, const char **argv, const char *prefix) * We do the same for "git pull". */ if (argc != 1) - die("Can merge only exactly one commit into " - "empty head"); + die(_("Can merge only exactly one commit into " + "empty head")); if (squash) - die("Squash commit into empty head not supported yet"); + die(_("Squash commit into empty head not supported yet")); if (!allow_fast_forward) - die("Non-fast-forward commit does not make sense into " - "an empty head"); + die(_("Non-fast-forward commit does not make sense into " + "an empty head")); remote_head = peel_to_type(argv[0], 0, NULL, OBJ_COMMIT); if (!remote_head) - die("%s - not something we can merge", argv[0]); + die(_("%s - not something we can merge"), argv[0]); + read_empty(remote_head->sha1, 0); update_ref("initial pull", "HEAD", remote_head->sha1, NULL, 0, DIE_ON_ERR); - reset_hard(remote_head->sha1, 0); return 0; } else { struct strbuf merge_names = STRBUF_INIT; @@ -998,14 +1148,12 @@ int cmd_merge(int argc, const char **argv, const char *prefix) for (i = 0; i < argc; i++) merge_name(argv[i], &merge_names); - if (have_message && option_log) - fmt_merge_msg_shortlog(&merge_names, &merge_msg); - else if (!have_message) - fmt_merge_msg(option_log, &merge_names, &merge_msg); - - - if (!(have_message && !option_log) && merge_msg.len) - strbuf_setlen(&merge_msg, merge_msg.len-1); + if (!have_message || shortlog_len) { + fmt_merge_msg(&merge_names, &merge_msg, !have_message, + shortlog_len); + if (merge_msg.len) + strbuf_setlen(&merge_msg, merge_msg.len - 1); + } } if (head_invalid || !argc) @@ -1024,7 +1172,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) o = peel_to_type(argv[i], 0, NULL, OBJ_COMMIT); if (!o) - die("%s - not something we can merge", argv[i]); + die(_("%s - not something we can merge"), argv[i]); commit = lookup_commit(o->sha1); commit->util = (void *)argv[i]; remotes = &commit_list_insert(commit, remotes)->next; @@ -1082,7 +1230,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) strcpy(hex, find_unique_abbrev(head, DEFAULT_ABBREV)); if (verbosity >= 0) - printf("Updating %s..%s\n", + printf(_("Updating %s..%s\n"), hex, find_unique_abbrev(remoteheads->item->object.sha1, DEFAULT_ABBREV)); @@ -1116,11 +1264,11 @@ int cmd_merge(int argc, const char **argv, const char *prefix) if (allow_trivial && !fast_forward_only) { /* See if it is really trivial. */ git_committer_info(IDENT_ERROR_ON_NO_NAME); - printf("Trying really trivial in-index merge...\n"); + printf(_("Trying really trivial in-index merge...\n")); if (!read_tree_trivial(common->item->object.sha1, head, remoteheads->item->object.sha1)) return merge_trivial(); - printf("Nope.\n"); + printf(_("Nope.\n")); } } else { /* @@ -1153,7 +1301,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) } if (fast_forward_only) - die("Not possible to fast-forward, aborting."); + die(_("Not possible to fast-forward, aborting.")); /* We are going to make a new commit. */ git_committer_info(IDENT_ERROR_ON_NO_NAME); @@ -1179,11 +1327,11 @@ int cmd_merge(int argc, const char **argv, const char *prefix) for (i = 0; i < use_strategies_nr; i++) { int ret; if (i) { - printf("Rewinding the tree to pristine...\n"); + printf(_("Rewinding the tree to pristine...\n")); restore_state(); } if (use_strategies_nr != 1) - printf("Trying merge strategy %s...\n", + printf(_("Trying merge strategy %s...\n"), use_strategies[i]->name); /* * Remember which strategy left the state in the working @@ -1244,17 +1392,17 @@ int cmd_merge(int argc, const char **argv, const char *prefix) restore_state(); if (use_strategies_nr > 1) fprintf(stderr, - "No merge strategy handled the merge.\n"); + _("No merge strategy handled the merge.\n")); else - fprintf(stderr, "Merge with strategy %s failed.\n", + fprintf(stderr, _("Merge with strategy %s failed.\n"), use_strategies[0]->name); return 2; } else if (best_strategy == wt_strategy) ; /* We already have its result in the working tree. */ else { - printf("Rewinding the tree to pristine...\n"); + printf(_("Rewinding the tree to pristine...\n")); restore_state(); - printf("Using the %s to prepare resolving by hand.\n", + printf(_("Using the %s to prepare resolving by hand.\n"), best_strategy); try_merge_strategy(best_strategy, common, head_arg); } @@ -1270,36 +1418,29 @@ int cmd_merge(int argc, const char **argv, const char *prefix) sha1_to_hex(j->item->object.sha1)); fd = open(git_path("MERGE_HEAD"), O_WRONLY | O_CREAT, 0666); if (fd < 0) - die_errno("Could not open '%s' for writing", + die_errno(_("Could not open '%s' for writing"), git_path("MERGE_HEAD")); if (write_in_full(fd, buf.buf, buf.len) != buf.len) - die_errno("Could not write to '%s'", git_path("MERGE_HEAD")); + die_errno(_("Could not write to '%s'"), git_path("MERGE_HEAD")); close(fd); strbuf_addch(&merge_msg, '\n'); - fd = open(git_path("MERGE_MSG"), O_WRONLY | O_CREAT, 0666); - if (fd < 0) - die_errno("Could not open '%s' for writing", - git_path("MERGE_MSG")); - if (write_in_full(fd, merge_msg.buf, merge_msg.len) != - merge_msg.len) - die_errno("Could not write to '%s'", git_path("MERGE_MSG")); - close(fd); + write_merge_msg(); fd = open(git_path("MERGE_MODE"), O_WRONLY | O_CREAT | O_TRUNC, 0666); if (fd < 0) - die_errno("Could not open '%s' for writing", + die_errno(_("Could not open '%s' for writing"), git_path("MERGE_MODE")); strbuf_reset(&buf); if (!allow_fast_forward) strbuf_addf(&buf, "no-ff"); if (write_in_full(fd, buf.buf, buf.len) != buf.len) - die_errno("Could not write to '%s'", git_path("MERGE_MODE")); + die_errno(_("Could not write to '%s'"), git_path("MERGE_MODE")); close(fd); } if (merge_was_ok) { - fprintf(stderr, "Automatic merge went well; " - "stopped before committing as requested\n"); + fprintf(stderr, _("Automatic merge went well; " + "stopped before committing as requested\n")); return 0; } else - return suggest_conflicts(); + return suggest_conflicts(option_renormalize); } diff --git a/builtin/mktag.c b/builtin/mktag.c index 1cb0f3f2a7..640ab64f41 100644 --- a/builtin/mktag.c +++ b/builtin/mktag.c @@ -1,6 +1,5 @@ -#include "cache.h" +#include "builtin.h" #include "tag.h" -#include "exec_cmd.h" /* * A signature file has a very simple fixed format: four lines @@ -24,8 +23,8 @@ static int verify_object(const unsigned char *sha1, const char *expected_type) int ret = -1; enum object_type type; unsigned long size; - const unsigned char *repl; - void *buffer = read_sha1_file_repl(sha1, &type, &size, &repl); + void *buffer = read_sha1_file(sha1, &type, &size); + const unsigned char *repl = lookup_replace_object(sha1); if (buffer) { if (type == type_from_string(expected_type)) @@ -35,12 +34,6 @@ static int verify_object(const unsigned char *sha1, const char *expected_type) return ret; } -#ifdef NO_C99_FORMAT -#define PD_FMT "%d" -#else -#define PD_FMT "%td" -#endif - static int verify_tag(char *buffer, unsigned long size) { int typelen; @@ -70,15 +63,18 @@ static int verify_tag(char *buffer, unsigned long size) /* Verify tag-line */ tag_line = strchr(type_line, '\n'); if (!tag_line) - return error("char" PD_FMT ": could not find next \"\\n\"", type_line - buffer); + return error("char%"PRIuMAX": could not find next \"\\n\"", + (uintmax_t) (type_line - buffer)); tag_line++; if (memcmp(tag_line, "tag ", 4) || tag_line[4] == '\n') - return error("char" PD_FMT ": no \"tag \" found", tag_line - buffer); + return error("char%"PRIuMAX": no \"tag \" found", + (uintmax_t) (tag_line - buffer)); /* Get the actual type */ typelen = tag_line - type_line - strlen("type \n"); if (typelen >= sizeof(type)) - return error("char" PD_FMT ": type too long", type_line+5 - buffer); + return error("char%"PRIuMAX": type too long", + (uintmax_t) (type_line+5 - buffer)); memcpy(type, type_line+5, typelen); type[typelen] = 0; @@ -95,15 +91,16 @@ static int verify_tag(char *buffer, unsigned long size) break; if (c > ' ') continue; - return error("char" PD_FMT ": could not verify tag name", tag_line - buffer); + return error("char%"PRIuMAX": could not verify tag name", + (uintmax_t) (tag_line - buffer)); } /* Verify the tagger line */ tagger_line = tag_line; if (memcmp(tagger_line, "tagger ", 7)) - return error("char" PD_FMT ": could not find \"tagger \"", - tagger_line - buffer); + return error("char%"PRIuMAX": could not find \"tagger \"", + (uintmax_t) (tagger_line - buffer)); /* * Check for correct form for name and email @@ -115,44 +112,42 @@ static int verify_tag(char *buffer, unsigned long size) if (!(lb = strstr(tagger_line, " <")) || !(rb = strstr(lb+2, "> ")) || strpbrk(tagger_line, "<>\n") != lb+1 || strpbrk(lb+2, "><\n ") != rb) - return error("char" PD_FMT ": malformed tagger field", - tagger_line - buffer); + return error("char%"PRIuMAX": malformed tagger field", + (uintmax_t) (tagger_line - buffer)); /* Check for author name, at least one character, space is acceptable */ if (lb == tagger_line) - return error("char" PD_FMT ": missing tagger name", - tagger_line - buffer); + return error("char%"PRIuMAX": missing tagger name", + (uintmax_t) (tagger_line - buffer)); /* timestamp, 1 or more digits followed by space */ tagger_line = rb + 2; if (!(len = strspn(tagger_line, "0123456789"))) - return error("char" PD_FMT ": missing tag timestamp", - tagger_line - buffer); + return error("char%"PRIuMAX": missing tag timestamp", + (uintmax_t) (tagger_line - buffer)); tagger_line += len; if (*tagger_line != ' ') - return error("char" PD_FMT ": malformed tag timestamp", - tagger_line - buffer); + return error("char%"PRIuMAX": malformed tag timestamp", + (uintmax_t) (tagger_line - buffer)); tagger_line++; /* timezone, 5 digits [+-]hhmm, max. 1400 */ if (!((tagger_line[0] == '+' || tagger_line[0] == '-') && strspn(tagger_line+1, "0123456789") == 4 && tagger_line[5] == '\n' && atoi(tagger_line+1) <= 1400)) - return error("char" PD_FMT ": malformed tag timezone", - tagger_line - buffer); + return error("char%"PRIuMAX": malformed tag timezone", + (uintmax_t) (tagger_line - buffer)); tagger_line += 6; /* Verify the blank line separating the header from the body */ if (*tagger_line != '\n') - return error("char" PD_FMT ": trailing garbage in tag header", - tagger_line - buffer); + return error("char%"PRIuMAX": trailing garbage in tag header", + (uintmax_t) (tagger_line - buffer)); /* The actual stuff afterwards we don't care about.. */ return 0; } -#undef PD_FMT - int cmd_mktag(int argc, const char **argv, const char *prefix) { struct strbuf buf = STRBUF_INIT; diff --git a/builtin/mv.c b/builtin/mv.c index 38574b89f7..40f33ca4d0 100644 --- a/builtin/mv.c +++ b/builtin/mv.c @@ -55,15 +55,15 @@ int cmd_mv(int argc, const char **argv, const char *prefix) int i, newfd; int verbose = 0, show_only = 0, force = 0, ignore_errors = 0; struct option builtin_mv_options[] = { - OPT__DRY_RUN(&show_only), - OPT_BOOLEAN('f', "force", &force, "force move/rename even if target exists"), + OPT__DRY_RUN(&show_only, "dry run"), + OPT__FORCE(&force, "force move/rename even if target exists"), OPT_BOOLEAN('k', NULL, &ignore_errors, "skip move/rename errors"), OPT_END(), }; const char **source, **destination, **dest_path; enum update_mode { BOTH = 0, WORKING_DIRECTORY, INDEX } *modes; struct stat st; - struct string_list src_for_dst = {NULL, 0, 0, 0}; + struct string_list src_for_dst = STRING_LIST_INIT_NODUP; git_config(git_default_config, NULL); @@ -74,7 +74,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix) newfd = hold_locked_index(&lock_file, 1); if (read_cache() < 0) - die("index file corrupt"); + die(_("index file corrupt")); source = copy_pathspec(prefix, argv, argc, 0); modes = xcalloc(argc, sizeof(enum update_mode)); @@ -100,17 +100,17 @@ int cmd_mv(int argc, const char **argv, const char *prefix) const char *bad = NULL; if (show_only) - printf("Checking rename of '%s' to '%s'\n", src, dst); + printf(_("Checking rename of '%s' to '%s'\n"), src, dst); length = strlen(src); if (lstat(src, &st) < 0) - bad = "bad source"; + bad = _("bad source"); else if (!strncmp(src, dst, length) && (dst[length] == 0 || dst[length] == '/')) { - bad = "can not move directory into itself"; + bad = _("can not move directory into itself"); } else if ((src_is_dir = S_ISDIR(st.st_mode)) && lstat(dst, &st) == 0) - bad = "cannot move directory over file"; + bad = _("cannot move directory over file"); else if (src_is_dir) { const char *src_w_slash = add_slash(src); int len_w_slash = length + 1; @@ -120,7 +120,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix) first = cache_name_pos(src_w_slash, len_w_slash); if (first >= 0) - die ("Huh? %.*s is in index?", + die (_("Huh? %.*s is in index?"), len_w_slash, src_w_slash); first = -1 - first; @@ -132,7 +132,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix) free((char *)src_w_slash); if (last - first < 1) - bad = "source directory is empty"; + bad = _("source directory is empty"); else { int j, dst_len; @@ -163,22 +163,22 @@ int cmd_mv(int argc, const char **argv, const char *prefix) argc += last - first; } } else if (cache_name_pos(src, length) < 0) - bad = "not under version control"; + bad = _("not under version control"); else if (lstat(dst, &st) == 0) { - bad = "destination exists"; + bad = _("destination exists"); if (force) { /* * only files can overwrite each other: * check both source and destination */ if (S_ISREG(st.st_mode) || S_ISLNK(st.st_mode)) { - warning("%s; will overwrite!", bad); + warning(_("%s; will overwrite!"), bad); bad = NULL; } else - bad = "Cannot overwrite"; + bad = _("Cannot overwrite"); } } else if (string_list_has_string(&src_for_dst, dst)) - bad = "multiple sources for the same target"; + bad = _("multiple sources for the same target"); else string_list_insert(&src_for_dst, dst); @@ -193,7 +193,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix) i--; } } else - die ("%s, source=%s, destination=%s", + die (_("%s, source=%s, destination=%s"), bad, src, dst); } } @@ -203,10 +203,10 @@ int cmd_mv(int argc, const char **argv, const char *prefix) enum update_mode mode = modes[i]; int pos; if (show_only || verbose) - printf("Renaming %s to %s\n", src, dst); + printf(_("Renaming %s to %s\n"), src, dst); if (!show_only && mode != INDEX && rename(src, dst) < 0 && !ignore_errors) - die_errno ("renaming '%s' failed", src); + die_errno (_("renaming '%s' failed"), src); if (mode == WORKING_DIRECTORY) continue; @@ -220,7 +220,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix) if (active_cache_changed) { if (write_cache(newfd, active_cache, active_nr) || commit_locked_index(&lock_file)) - die("Unable to write new index file"); + die(_("Unable to write new index file")); } return 0; diff --git a/builtin/name-rev.c b/builtin/name-rev.c index 06a38ac8c1..31f5c1c971 100644 --- a/builtin/name-rev.c +++ b/builtin/name-rev.c @@ -220,7 +220,7 @@ static void name_rev_line(char *p, struct name_ref_data *data) int cmd_name_rev(int argc, const char **argv, const char *prefix) { - struct object_array revs = { 0, 0, NULL }; + struct object_array revs = OBJECT_ARRAY_INIT; int all = 0, transform_stdin = 0, allow_undefined = 1, always = 0; struct name_ref_data data = { 0, 0, NULL }; struct option opts[] = { diff --git a/builtin/notes.c b/builtin/notes.c index 190005f3cd..f8e437db01 100644 --- a/builtin/notes.c +++ b/builtin/notes.c @@ -17,6 +17,7 @@ #include "run-command.h" #include "parse-options.h" #include "string-list.h" +#include "notes-merge.h" static const char * const git_notes_usage[] = { "git notes [--ref <notes_ref>] [list [<object>]]", @@ -25,8 +26,12 @@ static const char * const git_notes_usage[] = { "git notes [--ref <notes_ref>] append [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]", "git notes [--ref <notes_ref>] edit [<object>]", "git notes [--ref <notes_ref>] show [<object>]", - "git notes [--ref <notes_ref>] remove [<object>]", + "git notes [--ref <notes_ref>] merge [-v | -q] [-s <strategy> ] <notes_ref>", + "git notes merge --commit [-v | -q]", + "git notes merge --abort [-v | -q]", + "git notes [--ref <notes_ref>] remove [<object>...]", "git notes [--ref <notes_ref>] prune [-n | -v]", + "git notes [--ref <notes_ref>] get-ref", NULL }; @@ -61,6 +66,13 @@ static const char * const git_notes_show_usage[] = { NULL }; +static const char * const git_notes_merge_usage[] = { + "git notes merge [<options>] <notes_ref>", + "git notes merge --commit [<options>]", + "git notes merge --abort [<options>]", + NULL +}; + static const char * const git_notes_remove_usage[] = { "git notes remove [<object>]", NULL @@ -71,6 +83,11 @@ static const char * const git_notes_prune_usage[] = { NULL }; +static const char * const git_notes_get_ref_usage[] = { + "git notes get-ref", + NULL +}; + static const char note_template[] = "\n" "#\n" @@ -119,13 +136,13 @@ static void write_commented_object(int fd, const unsigned char *object) show.err = 0; show.git_cmd = 1; if (start_command(&show)) - die("unable to start 'show' for object '%s'", + die(_("unable to start 'show' for object '%s'"), sha1_to_hex(object)); /* Open the output as FILE* so strbuf_getline() can be used. */ show_out = xfdopen(show.out, "r"); if (show_out == NULL) - die_errno("can't fdopen 'show' output fd"); + die_errno(_("can't fdopen 'show' output fd")); /* Prepend "# " to each output line and write result to 'fd' */ while (strbuf_getline(&buf, show_out, '\n') != EOF) { @@ -135,10 +152,10 @@ static void write_commented_object(int fd, const unsigned char *object) } strbuf_release(&buf); if (fclose(show_out)) - die_errno("failed to close pipe to 'show' for object '%s'", + die_errno(_("failed to close pipe to 'show' for object '%s'"), sha1_to_hex(object)); if (finish_command(&show)) - die("failed to finish 'show' for object '%s'", + die(_("failed to finish 'show' for object '%s'"), sha1_to_hex(object)); } @@ -155,7 +172,7 @@ static void create_note(const unsigned char *object, struct msg_arg *msg, path = git_pathdup("NOTES_EDITMSG"); fd = open(path, O_CREAT | O_TRUNC | O_WRONLY, 0600); if (fd < 0) - die_errno("could not create file '%s'", path); + die_errno(_("could not create file '%s'"), path); if (msg->given) write_or_die(fd, msg->buf.buf, msg->buf.len); @@ -169,8 +186,8 @@ static void create_note(const unsigned char *object, struct msg_arg *msg, strbuf_reset(&(msg->buf)); if (launch_editor(path, &(msg->buf), NULL)) { - die("Please supply the note contents using either -m" \ - " or -F option"); + die(_("Please supply the note contents using either -m" \ + " or -F option")); } stripspace(&(msg->buf), 1); } @@ -190,14 +207,14 @@ static void create_note(const unsigned char *object, struct msg_arg *msg, } if (!msg->buf.len) { - fprintf(stderr, "Removing note for object %s\n", + fprintf(stderr, _("Removing note for object %s\n"), sha1_to_hex(object)); hashclr(result); } else { if (write_sha1_file(msg->buf.buf, msg->buf.len, blob_type, result)) { - error("unable to write note object"); + error(_("unable to write note object")); if (path) - error("The note contents has been left in %s", + error(_("The note contents has been left in %s"), path); exit(128); } @@ -231,9 +248,9 @@ static int parse_file_arg(const struct option *opt, const char *arg, int unset) strbuf_addch(&(msg->buf), '\n'); if (!strcmp(arg, "-")) { if (strbuf_read(&(msg->buf), 0, 1024) < 0) - die_errno("cannot read '%s'", arg); + die_errno(_("cannot read '%s'"), arg); } else if (strbuf_read_file(&(msg->buf), arg, 1024) < 0) - die_errno("could not open or read '%s'", arg); + die_errno(_("could not open or read '%s'"), arg); stripspace(&(msg->buf), 0); msg->given = 1; @@ -252,10 +269,10 @@ static int parse_reuse_arg(const struct option *opt, const char *arg, int unset) strbuf_addch(&(msg->buf), '\n'); if (get_sha1(arg, object)) - die("Failed to resolve '%s' as a valid ref.", arg); + die(_("Failed to resolve '%s' as a valid ref."), arg); if (!(buf = read_sha1_file(object, &type, &len)) || !len) { free(buf); - die("Failed to read object '%s'.", arg);; + die(_("Failed to read object '%s'."), arg);; } strbuf_add(&(msg->buf), buf, len); free(buf); @@ -271,18 +288,17 @@ static int parse_reedit_arg(const struct option *opt, const char *arg, int unset return parse_reuse_arg(opt, arg, unset); } -int commit_notes(struct notes_tree *t, const char *msg) +void commit_notes(struct notes_tree *t, const char *msg) { - struct commit_list *parent; - unsigned char tree_sha1[20], prev_commit[20], new_commit[20]; struct strbuf buf = STRBUF_INIT; + unsigned char commit_sha1[20]; if (!t) t = &default_notes_tree; if (!t->initialized || !t->ref || !*t->ref) - die("Cannot commit uninitialized/unreferenced notes tree"); + die(_("Cannot commit uninitialized/unreferenced notes tree")); if (!t->dirty) - return 0; /* don't have to commit an unchanged tree */ + return; /* don't have to commit an unchanged tree */ /* Prepare commit message and reflog message */ strbuf_addstr(&buf, "notes: "); /* commit message starts at index 7 */ @@ -290,27 +306,10 @@ int commit_notes(struct notes_tree *t, const char *msg) if (buf.buf[buf.len - 1] != '\n') strbuf_addch(&buf, '\n'); /* Make sure msg ends with newline */ - /* Convert notes tree to tree object */ - if (write_notes_tree(t, tree_sha1)) - die("Failed to write current notes tree to database"); - - /* Create new commit for the tree object */ - if (!read_ref(t->ref, prev_commit)) { /* retrieve parent commit */ - parent = xmalloc(sizeof(*parent)); - parent->item = lookup_commit(prev_commit); - parent->next = NULL; - } else { - hashclr(prev_commit); - parent = NULL; - } - if (commit_tree(buf.buf + 7, tree_sha1, parent, new_commit, NULL)) - die("Failed to commit notes tree to database"); - - /* Update notes ref with new commit */ - update_ref(buf.buf, t->ref, new_commit, prev_commit, 0, DIE_ON_ERR); + create_notes_commit(t, NULL, buf.buf + 7, commit_sha1); + update_ref(buf.buf, t->ref, commit_sha1, NULL, 0, DIE_ON_ERR); strbuf_release(&buf); - return 0; } combine_notes_fn parse_combine_notes_fn(const char *v) @@ -321,6 +320,8 @@ combine_notes_fn parse_combine_notes_fn(const char *v) return combine_notes_ignore; else if (!strcasecmp(v, "concatenate")) return combine_notes_concatenate; + else if (!strcasecmp(v, "cat_sort_uniq")) + return combine_notes_cat_sort_uniq; else return NULL; } @@ -336,7 +337,7 @@ static int notes_rewrite_config(const char *k, const char *v, void *cb) config_error_nonbool(k); c->combine = parse_combine_notes_fn(v); if (!c->combine) { - error("Bad notes.rewriteMode value: '%s'", v); + error(_("Bad notes.rewriteMode value: '%s'"), v); return 1; } return 0; @@ -346,8 +347,8 @@ static int notes_rewrite_config(const char *k, const char *v, void *cb) if (!prefixcmp(v, "refs/notes/")) string_list_add_refs_by_glob(c->refs, v); else - warning("Refusing to rewrite notes in %s" - " (outside of refs/notes/)", v); + warning(_("Refusing to rewrite notes in %s" + " (outside of refs/notes/)"), v); return 0; } @@ -371,8 +372,10 @@ struct notes_rewrite_cfg *init_copy_notes_for_rewrite(const char *cmd) c->mode_from_env = 1; c->combine = parse_combine_notes_fn(rewrite_mode_env); if (!c->combine) - error("Bad " GIT_NOTES_REWRITE_MODE_ENVIRONMENT - " value: '%s'", rewrite_mode_env); + /* TRANSLATORS: The first %s is the name of the + environment variable, the second %s is its value */ + error(_("Bad %s value: '%s'"), GIT_NOTES_REWRITE_MODE_ENVIRONMENT, + rewrite_mode_env); } if (rewrite_refs_env) { c->refs_from_env = 1; @@ -412,7 +415,7 @@ void finish_copy_notes_for_rewrite(struct notes_rewrite_cfg *c) free(c); } -int notes_copy_from_stdin(int force, const char *rewrite_cmd) +static int notes_copy_from_stdin(int force, const char *rewrite_cmd) { struct strbuf buf = STRBUF_INIT; struct notes_rewrite_cfg *c = NULL; @@ -435,13 +438,13 @@ int notes_copy_from_stdin(int force, const char *rewrite_cmd) split = strbuf_split(&buf, ' '); if (!split[0] || !split[1]) - die("Malformed input line: '%s'.", buf.buf); + die(_("Malformed input line: '%s'."), buf.buf); strbuf_rtrim(split[0]); strbuf_rtrim(split[1]); if (get_sha1(split[0]->buf, from_obj)) - die("Failed to resolve '%s' as a valid ref.", split[0]->buf); + die(_("Failed to resolve '%s' as a valid ref."), split[0]->buf); if (get_sha1(split[1]->buf, to_obj)) - die("Failed to resolve '%s' as a valid ref.", split[1]->buf); + die(_("Failed to resolve '%s' as a valid ref."), split[1]->buf); if (rewrite_cmd) err = copy_note_for_rewrite(c, from_obj, to_obj); @@ -450,7 +453,7 @@ int notes_copy_from_stdin(int force, const char *rewrite_cmd) combine_notes_overwrite); if (err) { - error("Failed to copy notes from '%s' to '%s'", + error(_("Failed to copy notes from '%s' to '%s'"), split[0]->buf, split[1]->buf); ret = 1; } @@ -494,20 +497,20 @@ static int list(int argc, const char **argv, const char *prefix) git_notes_list_usage, 0); if (1 < argc) { - error("too many parameters"); + error(_("too many parameters")); usage_with_options(git_notes_list_usage, options); } t = init_notes_check("list"); if (argc) { if (get_sha1(argv[0], object)) - die("Failed to resolve '%s' as a valid ref.", argv[0]); + die(_("Failed to resolve '%s' as a valid ref."), argv[0]); note = get_note(t, object); if (note) { puts(sha1_to_hex(note)); retval = 0; } else - retval = error("No note found for object %s.", + retval = error(_("No note found for object %s."), sha1_to_hex(object)); } else retval = for_each_note(t, 0, list_each_note, NULL); @@ -516,6 +519,8 @@ static int list(int argc, const char **argv, const char *prefix) return retval; } +static int append_edit(int argc, const char **argv, const char *prefix); + static int add(int argc, const char **argv, const char *prefix) { int retval = 0, force = 0; @@ -526,46 +531,58 @@ static int add(int argc, const char **argv, const char *prefix) const unsigned char *note; struct msg_arg msg = { 0, 0, STRBUF_INIT }; struct option options[] = { - { OPTION_CALLBACK, 'm', "message", &msg, "MSG", + { OPTION_CALLBACK, 'm', "message", &msg, "msg", "note contents as a string", PARSE_OPT_NONEG, parse_msg_arg}, - { OPTION_CALLBACK, 'F', "file", &msg, "FILE", + { OPTION_CALLBACK, 'F', "file", &msg, "file", "note contents in a file", PARSE_OPT_NONEG, parse_file_arg}, - { OPTION_CALLBACK, 'c', "reedit-message", &msg, "OBJECT", + { OPTION_CALLBACK, 'c', "reedit-message", &msg, "object", "reuse and edit specified note object", PARSE_OPT_NONEG, parse_reedit_arg}, - { OPTION_CALLBACK, 'C', "reuse-message", &msg, "OBJECT", + { OPTION_CALLBACK, 'C', "reuse-message", &msg, "object", "reuse specified note object", PARSE_OPT_NONEG, parse_reuse_arg}, - OPT_BOOLEAN('f', "force", &force, "replace existing notes"), + OPT__FORCE(&force, "replace existing notes"), OPT_END() }; argc = parse_options(argc, argv, prefix, options, git_notes_add_usage, - 0); + PARSE_OPT_KEEP_ARGV0); - if (1 < argc) { - error("too many parameters"); + if (2 < argc) { + error(_("too many parameters")); usage_with_options(git_notes_add_usage, options); } - object_ref = argc ? argv[0] : "HEAD"; + object_ref = argc > 1 ? argv[1] : "HEAD"; if (get_sha1(object_ref, object)) - die("Failed to resolve '%s' as a valid ref.", object_ref); + die(_("Failed to resolve '%s' as a valid ref."), object_ref); t = init_notes_check("add"); note = get_note(t, object); if (note) { if (!force) { - retval = error("Cannot add notes. Found existing notes " + if (!msg.given) { + /* + * Redirect to "edit" subcommand. + * + * We only end up here if none of -m/-F/-c/-C + * or -f are given. The original args are + * therefore still in argv[0-1]. + */ + argv[0] = "edit"; + free_notes(t); + return append_edit(argc, argv, prefix); + } + retval = error(_("Cannot add notes. Found existing notes " "for object %s. Use '-f' to overwrite " - "existing notes", sha1_to_hex(object)); + "existing notes"), sha1_to_hex(object)); goto out; } - fprintf(stderr, "Overwriting existing notes for object %s\n", + fprintf(stderr, _("Overwriting existing notes for object %s\n"), sha1_to_hex(object)); } @@ -573,8 +590,8 @@ static int add(int argc, const char **argv, const char *prefix) if (is_null_sha1(new_note)) remove_note(t, object); - else - add_note(t, object, new_note, combine_notes_overwrite); + else if (add_note(t, object, new_note, combine_notes_overwrite)) + die("BUG: combine_notes_overwrite failed"); snprintf(logmsg, sizeof(logmsg), "Notes %s by 'git notes %s'", is_null_sha1(new_note) ? "removed" : "added", "add"); @@ -594,7 +611,7 @@ static int copy(int argc, const char **argv, const char *prefix) struct notes_tree *t; const char *rewrite_cmd = NULL; struct option options[] = { - OPT_BOOLEAN('f', "force", &force, "replace existing notes"), + OPT__FORCE(&force, "replace existing notes"), OPT_BOOLEAN(0, "stdin", &from_stdin, "read objects from stdin"), OPT_STRING(0, "for-rewrite", &rewrite_cmd, "command", "load rewriting config for <command> (implies " @@ -607,7 +624,7 @@ static int copy(int argc, const char **argv, const char *prefix) if (from_stdin || rewrite_cmd) { if (argc) { - error("too many parameters"); + error(_("too many parameters")); usage_with_options(git_notes_copy_usage, options); } else { return notes_copy_from_stdin(force, rewrite_cmd); @@ -615,45 +632,46 @@ static int copy(int argc, const char **argv, const char *prefix) } if (argc < 2) { - error("too few parameters"); + error(_("too few parameters")); usage_with_options(git_notes_copy_usage, options); } if (2 < argc) { - error("too many parameters"); + error(_("too many parameters")); usage_with_options(git_notes_copy_usage, options); } if (get_sha1(argv[0], from_obj)) - die("Failed to resolve '%s' as a valid ref.", argv[0]); + die(_("Failed to resolve '%s' as a valid ref."), argv[0]); object_ref = 1 < argc ? argv[1] : "HEAD"; if (get_sha1(object_ref, object)) - die("Failed to resolve '%s' as a valid ref.", object_ref); + die(_("Failed to resolve '%s' as a valid ref."), object_ref); t = init_notes_check("copy"); note = get_note(t, object); if (note) { if (!force) { - retval = error("Cannot copy notes. Found existing " + retval = error(_("Cannot copy notes. Found existing " "notes for object %s. Use '-f' to " - "overwrite existing notes", + "overwrite existing notes"), sha1_to_hex(object)); goto out; } - fprintf(stderr, "Overwriting existing notes for object %s\n", + fprintf(stderr, _("Overwriting existing notes for object %s\n"), sha1_to_hex(object)); } from_note = get_note(t, from_obj); if (!from_note) { - retval = error("Missing notes on source object %s. Cannot " - "copy.", sha1_to_hex(from_obj)); + retval = error(_("Missing notes on source object %s. Cannot " + "copy."), sha1_to_hex(from_obj)); goto out; } - add_note(t, object, from_note, combine_notes_overwrite); + if (add_note(t, object, from_note, combine_notes_overwrite)) + die("BUG: combine_notes_overwrite failed"); commit_notes(t, "Notes added by 'git notes copy'"); out: free_notes(t); @@ -670,16 +688,16 @@ static int append_edit(int argc, const char **argv, const char *prefix) const char * const *usage; struct msg_arg msg = { 0, 0, STRBUF_INIT }; struct option options[] = { - { OPTION_CALLBACK, 'm', "message", &msg, "MSG", + { OPTION_CALLBACK, 'm', "message", &msg, "msg", "note contents as a string", PARSE_OPT_NONEG, parse_msg_arg}, - { OPTION_CALLBACK, 'F', "file", &msg, "FILE", + { OPTION_CALLBACK, 'F', "file", &msg, "file", "note contents in a file", PARSE_OPT_NONEG, parse_file_arg}, - { OPTION_CALLBACK, 'c', "reedit-message", &msg, "OBJECT", + { OPTION_CALLBACK, 'c', "reedit-message", &msg, "object", "reuse and edit specified note object", PARSE_OPT_NONEG, parse_reedit_arg}, - { OPTION_CALLBACK, 'C', "reuse-message", &msg, "OBJECT", + { OPTION_CALLBACK, 'C', "reuse-message", &msg, "object", "reuse specified note object", PARSE_OPT_NONEG, parse_reuse_arg}, OPT_END() @@ -691,19 +709,19 @@ static int append_edit(int argc, const char **argv, const char *prefix) PARSE_OPT_KEEP_ARGV0); if (2 < argc) { - error("too many parameters"); + error(_("too many parameters")); usage_with_options(usage, options); } if (msg.given && edit) - fprintf(stderr, "The -m/-F/-c/-C options have been deprecated " + fprintf(stderr, _("The -m/-F/-c/-C options have been deprecated " "for the 'edit' subcommand.\n" - "Please use 'git notes add -f -m/-F/-c/-C' instead.\n"); + "Please use 'git notes add -f -m/-F/-c/-C' instead.\n")); object_ref = 1 < argc ? argv[1] : "HEAD"; if (get_sha1(object_ref, object)) - die("Failed to resolve '%s' as a valid ref.", object_ref); + die(_("Failed to resolve '%s' as a valid ref."), object_ref); t = init_notes_check(argv[0]); note = get_note(t, object); @@ -712,8 +730,8 @@ static int append_edit(int argc, const char **argv, const char *prefix) if (is_null_sha1(new_note)) remove_note(t, object); - else - add_note(t, object, new_note, combine_notes_overwrite); + else if (add_note(t, object, new_note, combine_notes_overwrite)) + die("BUG: combine_notes_overwrite failed"); snprintf(logmsg, sizeof(logmsg), "Notes %s by 'git notes %s'", is_null_sha1(new_note) ? "removed" : "added", argv[0]); @@ -738,20 +756,20 @@ static int show(int argc, const char **argv, const char *prefix) 0); if (1 < argc) { - error("too many parameters"); + error(_("too many parameters")); usage_with_options(git_notes_show_usage, options); } object_ref = argc ? argv[0] : "HEAD"; if (get_sha1(object_ref, object)) - die("Failed to resolve '%s' as a valid ref.", object_ref); + die(_("Failed to resolve '%s' as a valid ref."), object_ref); t = init_notes_check("show"); note = get_note(t, object); if (!note) - retval = error("No note found for object %s.", + retval = error(_("No note found for object %s."), sha1_to_hex(object)); else { const char *show_args[3] = {"show", sha1_to_hex(note), NULL}; @@ -761,36 +779,236 @@ static int show(int argc, const char **argv, const char *prefix) return retval; } -static int remove_cmd(int argc, const char **argv, const char *prefix) +static int merge_abort(struct notes_merge_options *o) +{ + int ret = 0; + + /* + * Remove .git/NOTES_MERGE_PARTIAL and .git/NOTES_MERGE_REF, and call + * notes_merge_abort() to remove .git/NOTES_MERGE_WORKTREE. + */ + + if (delete_ref("NOTES_MERGE_PARTIAL", NULL, 0)) + ret += error("Failed to delete ref NOTES_MERGE_PARTIAL"); + if (delete_ref("NOTES_MERGE_REF", NULL, REF_NODEREF)) + ret += error("Failed to delete ref NOTES_MERGE_REF"); + if (notes_merge_abort(o)) + ret += error("Failed to remove 'git notes merge' worktree"); + return ret; +} + +static int merge_commit(struct notes_merge_options *o) +{ + struct strbuf msg = STRBUF_INIT; + unsigned char sha1[20], parent_sha1[20]; + struct notes_tree *t; + struct commit *partial; + struct pretty_print_context pretty_ctx; + + /* + * Read partial merge result from .git/NOTES_MERGE_PARTIAL, + * and target notes ref from .git/NOTES_MERGE_REF. + */ + + if (get_sha1("NOTES_MERGE_PARTIAL", sha1)) + die("Failed to read ref NOTES_MERGE_PARTIAL"); + else if (!(partial = lookup_commit_reference(sha1))) + die("Could not find commit from NOTES_MERGE_PARTIAL."); + else if (parse_commit(partial)) + die("Could not parse commit from NOTES_MERGE_PARTIAL."); + + if (partial->parents) + hashcpy(parent_sha1, partial->parents->item->object.sha1); + else + hashclr(parent_sha1); + + t = xcalloc(1, sizeof(struct notes_tree)); + init_notes(t, "NOTES_MERGE_PARTIAL", combine_notes_overwrite, 0); + + o->local_ref = resolve_ref("NOTES_MERGE_REF", sha1, 0, NULL); + if (!o->local_ref) + die("Failed to resolve NOTES_MERGE_REF"); + + if (notes_merge_commit(o, t, partial, sha1)) + die("Failed to finalize notes merge"); + + /* Reuse existing commit message in reflog message */ + memset(&pretty_ctx, 0, sizeof(pretty_ctx)); + format_commit_message(partial, "%s", &msg, &pretty_ctx); + strbuf_trim(&msg); + strbuf_insert(&msg, 0, "notes: ", 7); + update_ref(msg.buf, o->local_ref, sha1, + is_null_sha1(parent_sha1) ? NULL : parent_sha1, + 0, DIE_ON_ERR); + + free_notes(t); + strbuf_release(&msg); + return merge_abort(o); +} + +static int merge(int argc, const char **argv, const char *prefix) { + struct strbuf remote_ref = STRBUF_INIT, msg = STRBUF_INIT; + unsigned char result_sha1[20]; + struct notes_tree *t; + struct notes_merge_options o; + int do_merge = 0, do_commit = 0, do_abort = 0; + int verbosity = 0, result; + const char *strategy = NULL; struct option options[] = { + OPT_GROUP("General options"), + OPT__VERBOSITY(&verbosity), + OPT_GROUP("Merge options"), + OPT_STRING('s', "strategy", &strategy, "strategy", + "resolve notes conflicts using the given strategy " + "(manual/ours/theirs/union/cat_sort_uniq)"), + OPT_GROUP("Committing unmerged notes"), + { OPTION_BOOLEAN, 0, "commit", &do_commit, NULL, + "finalize notes merge by committing unmerged notes", + PARSE_OPT_NOARG | PARSE_OPT_NONEG }, + OPT_GROUP("Aborting notes merge resolution"), + { OPTION_BOOLEAN, 0, "abort", &do_abort, NULL, + "abort notes merge", + PARSE_OPT_NOARG | PARSE_OPT_NONEG }, OPT_END() }; - const char *object_ref; - struct notes_tree *t; - unsigned char object[20]; argc = parse_options(argc, argv, prefix, options, - git_notes_remove_usage, 0); + git_notes_merge_usage, 0); - if (1 < argc) { + if (strategy || do_commit + do_abort == 0) + do_merge = 1; + if (do_merge + do_commit + do_abort != 1) { + error("cannot mix --commit, --abort or -s/--strategy"); + usage_with_options(git_notes_merge_usage, options); + } + + if (do_merge && argc != 1) { + error("Must specify a notes ref to merge"); + usage_with_options(git_notes_merge_usage, options); + } else if (!do_merge && argc) { error("too many parameters"); - usage_with_options(git_notes_remove_usage, options); + usage_with_options(git_notes_merge_usage, options); } - object_ref = argc ? argv[0] : "HEAD"; + init_notes_merge_options(&o); + o.verbosity = verbosity + NOTES_MERGE_VERBOSITY_DEFAULT; + + if (do_abort) + return merge_abort(&o); + if (do_commit) + return merge_commit(&o); + + o.local_ref = default_notes_ref(); + strbuf_addstr(&remote_ref, argv[0]); + expand_notes_ref(&remote_ref); + o.remote_ref = remote_ref.buf; + + if (strategy) { + if (!strcmp(strategy, "manual")) + o.strategy = NOTES_MERGE_RESOLVE_MANUAL; + else if (!strcmp(strategy, "ours")) + o.strategy = NOTES_MERGE_RESOLVE_OURS; + else if (!strcmp(strategy, "theirs")) + o.strategy = NOTES_MERGE_RESOLVE_THEIRS; + else if (!strcmp(strategy, "union")) + o.strategy = NOTES_MERGE_RESOLVE_UNION; + else if (!strcmp(strategy, "cat_sort_uniq")) + o.strategy = NOTES_MERGE_RESOLVE_CAT_SORT_UNIQ; + else { + error("Unknown -s/--strategy: %s", strategy); + usage_with_options(git_notes_merge_usage, options); + } + } - if (get_sha1(object_ref, object)) - die("Failed to resolve '%s' as a valid ref.", object_ref); + t = init_notes_check("merge"); + + strbuf_addf(&msg, "notes: Merged notes from %s into %s", + remote_ref.buf, default_notes_ref()); + strbuf_add(&(o.commit_msg), msg.buf + 7, msg.len - 7); /* skip "notes: " */ + + result = notes_merge(&o, t, result_sha1); + + if (result >= 0) /* Merge resulted (trivially) in result_sha1 */ + /* Update default notes ref with new commit */ + update_ref(msg.buf, default_notes_ref(), result_sha1, NULL, + 0, DIE_ON_ERR); + else { /* Merge has unresolved conflicts */ + /* Update .git/NOTES_MERGE_PARTIAL with partial merge result */ + update_ref(msg.buf, "NOTES_MERGE_PARTIAL", result_sha1, NULL, + 0, DIE_ON_ERR); + /* Store ref-to-be-updated into .git/NOTES_MERGE_REF */ + if (create_symref("NOTES_MERGE_REF", default_notes_ref(), NULL)) + die("Failed to store link to current notes ref (%s)", + default_notes_ref()); + printf("Automatic notes merge failed. Fix conflicts in %s and " + "commit the result with 'git notes merge --commit', or " + "abort the merge with 'git notes merge --abort'.\n", + git_path(NOTES_MERGE_WORKTREE)); + } - t = init_notes_check("remove"); + free_notes(t); + strbuf_release(&remote_ref); + strbuf_release(&msg); + return result < 0; /* return non-zero on conflicts */ +} + +#define IGNORE_MISSING 1 + +static int remove_one_note(struct notes_tree *t, const char *name, unsigned flag) +{ + int status; + unsigned char sha1[20]; + if (get_sha1(name, sha1)) + return error(_("Failed to resolve '%s' as a valid ref."), name); + status = remove_note(t, sha1); + if (status) + fprintf(stderr, _("Object %s has no note\n"), name); + else + fprintf(stderr, _("Removing note for object %s\n"), name); + return (flag & IGNORE_MISSING) ? 0 : status; +} + +static int remove_cmd(int argc, const char **argv, const char *prefix) +{ + unsigned flag = 0; + int from_stdin = 0; + struct option options[] = { + OPT_BIT(0, "ignore-missing", &flag, + "attempt to remove non-existent note is not an error", + IGNORE_MISSING), + OPT_BOOLEAN(0, "stdin", &from_stdin, + "read object names from the standard input"), + OPT_END() + }; + struct notes_tree *t; + int retval = 0; - fprintf(stderr, "Removing note for object %s\n", sha1_to_hex(object)); - remove_note(t, object); + argc = parse_options(argc, argv, prefix, options, + git_notes_remove_usage, 0); - commit_notes(t, "Notes removed by 'git notes remove'"); + t = init_notes_check("remove"); + + if (!argc && !from_stdin) { + retval = remove_one_note(t, "HEAD", flag); + } else { + while (*argv) { + retval |= remove_one_note(t, *argv, flag); + argv++; + } + } + if (from_stdin) { + struct strbuf sb = STRBUF_INIT; + while (strbuf_getwholeline(&sb, stdin, '\n') != EOF) { + strbuf_rtrim(&sb); + retval |= remove_one_note(t, sb.buf, flag); + } + strbuf_release(&sb); + } + if (!retval) + commit_notes(t, "Notes removed by 'git notes remove'"); free_notes(t); - return 0; + return retval; } static int prune(int argc, const char **argv, const char *prefix) @@ -798,8 +1016,8 @@ static int prune(int argc, const char **argv, const char *prefix) struct notes_tree *t; int show_only = 0, verbose = 0; struct option options[] = { - OPT_BOOLEAN('n', NULL, &show_only, "do not remove, show only"), - OPT_BOOLEAN('v', NULL, &verbose, "report pruned notes"), + OPT__DRY_RUN(&show_only, "do not remove, show only"), + OPT__VERBOSE(&verbose, "report pruned notes"), OPT_END() }; @@ -807,7 +1025,7 @@ static int prune(int argc, const char **argv, const char *prefix) 0); if (argc) { - error("too many parameters"); + error(_("too many parameters")); usage_with_options(git_notes_prune_usage, options); } @@ -821,6 +1039,21 @@ static int prune(int argc, const char **argv, const char *prefix) return 0; } +static int get_ref(int argc, const char **argv, const char *prefix) +{ + struct option options[] = { OPT_END() }; + argc = parse_options(argc, argv, prefix, options, + git_notes_get_ref_usage, 0); + + if (argc) { + error("too many parameters"); + usage_with_options(git_notes_get_ref_usage, options); + } + + puts(default_notes_ref()); + return 0; +} + int cmd_notes(int argc, const char **argv, const char *prefix) { int result; @@ -837,13 +1070,8 @@ int cmd_notes(int argc, const char **argv, const char *prefix) if (override_notes_ref) { struct strbuf sb = STRBUF_INIT; - if (!prefixcmp(override_notes_ref, "refs/notes/")) - /* we're happy */; - else if (!prefixcmp(override_notes_ref, "notes/")) - strbuf_addstr(&sb, "refs/"); - else - strbuf_addstr(&sb, "refs/notes/"); strbuf_addstr(&sb, override_notes_ref); + expand_notes_ref(&sb); setenv("GIT_NOTES_REF", sb.buf, 1); strbuf_release(&sb); } @@ -858,12 +1086,16 @@ int cmd_notes(int argc, const char **argv, const char *prefix) result = append_edit(argc, argv, prefix); else if (!strcmp(argv[0], "show")) result = show(argc, argv, prefix); + else if (!strcmp(argv[0], "merge")) + result = merge(argc, argv, prefix); else if (!strcmp(argv[0], "remove")) result = remove_cmd(argc, argv, prefix); else if (!strcmp(argv[0], "prune")) result = prune(argc, argv, prefix); + else if (!strcmp(argv[0], "get-ref")) + result = get_ref(argc, argv, prefix); else { - result = error("Unknown subcommand: %s", argv[0]); + result = error(_("Unknown subcommand: %s"), argv[0]); usage_with_options(git_notes_usage, options); } diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c index 0e81673118..a9c67c18ba 100644 --- a/builtin/pack-objects.c +++ b/builtin/pack-objects.c @@ -16,22 +16,18 @@ #include "list-objects.h" #include "progress.h" #include "refs.h" - -#ifndef NO_PTHREADS -#include <pthread.h> #include "thread-utils.h" -#endif static const char pack_usage[] = - "git pack-objects [{ -q | --progress | --all-progress }]\n" + "git pack-objects [ -q | --progress | --all-progress ]\n" " [--all-progress-implied]\n" - " [--max-pack-size=N] [--local] [--incremental]\n" - " [--window=N] [--window-memory=N] [--depth=N]\n" + " [--max-pack-size=<n>] [--local] [--incremental]\n" + " [--window=<n>] [--window-memory=<n>] [--depth=<n>]\n" " [--no-reuse-delta] [--no-reuse-object] [--delta-base-offset]\n" - " [--threads=N] [--non-empty] [--revs [--unpacked | --all]*]\n" + " [--threads=<n>] [--non-empty] [--revs [--unpacked | --all]]\n" " [--reflog] [--stdout | base-name] [--include-tag]\n" - " [--keep-unreachable | --unpack-unreachable \n" - " [<ref-list | <object-list]"; + " [--keep-unreachable | --unpack-unreachable]\n" + " [< ref-list | < object-list]"; struct object_entry { struct pack_idx_entry idx; @@ -55,6 +51,8 @@ struct object_entry { * objects against. */ unsigned char no_try_delta; + unsigned char tagged; /* near the very tip of refs */ + unsigned char filled; /* assigned write-order */ }; /* @@ -74,6 +72,7 @@ static int local; static int incremental; static int ignore_packed_keep; static int allow_ofs_delta; +static struct pack_idx_option pack_idx_opts; static const char *base_name; static int progress = 1; static int window = 10; @@ -99,6 +98,7 @@ static unsigned long window_memory_limit = 0; */ static int *object_ix; static int object_ix_hashsz; +static struct object_entry *locate_object_entry(const unsigned char *sha1); /* * stats @@ -130,13 +130,13 @@ static void *get_delta(struct object_entry *entry) static unsigned long do_compress(void **pptr, unsigned long size) { - z_stream stream; + git_zstream stream; void *in, *out; unsigned long maxsize; memset(&stream, 0, sizeof(stream)); - deflateInit(&stream, pack_compression_level); - maxsize = deflateBound(&stream, size); + git_deflate_init(&stream, pack_compression_level); + maxsize = git_deflate_bound(&stream, size); in = *pptr; out = xmalloc(maxsize); @@ -146,9 +146,9 @@ static unsigned long do_compress(void **pptr, unsigned long size) stream.avail_in = size; stream.next_out = out; stream.avail_out = maxsize; - while (deflate(&stream, Z_FINISH) == Z_OK) + while (git_deflate(&stream, Z_FINISH) == Z_OK) ; /* nothing */ - deflateEnd(&stream); + git_deflate_end(&stream); free(in); return stream.total_out; @@ -164,7 +164,7 @@ static int check_pack_inflate(struct packed_git *p, off_t len, unsigned long expect) { - z_stream stream; + git_zstream stream; unsigned char fakebuf[4096], *in; int st; @@ -191,18 +191,19 @@ static void copy_pack_data(struct sha1file *f, off_t len) { unsigned char *in; - unsigned int avail; + unsigned long avail; while (len) { in = use_pack(p, w_curs, offset, &avail); if (avail > len) - avail = (unsigned int)len; + avail = (unsigned long)len; sha1write(f, in, avail); offset += avail; len -= avail; } } +/* Return 0 if we will bust the pack-size limit */ static unsigned long write_object(struct sha1file *f, struct object_entry *entry, off_t write_offset) @@ -431,12 +432,140 @@ static int write_one(struct sha1file *f, written_list[nr_written++] = &e->idx; /* make sure off_t is sufficiently large not to wrap */ - if (*offset > *offset + size) + if (signed_add_overflows(*offset, size)) die("pack too large for current definition of off_t"); *offset += size; return 1; } +static int mark_tagged(const char *path, const unsigned char *sha1, int flag, + void *cb_data) +{ + unsigned char peeled[20]; + struct object_entry *entry = locate_object_entry(sha1); + + if (entry) + entry->tagged = 1; + if (!peel_ref(path, peeled)) { + entry = locate_object_entry(peeled); + if (entry) + entry->tagged = 1; + } + return 0; +} + +static void add_to_write_order(struct object_entry **wo, + int *endp, + struct object_entry *e) +{ + if (e->filled) + return; + wo[(*endp)++] = e; + e->filled = 1; +} + +static void add_descendants_to_write_order(struct object_entry **wo, + int *endp, + struct object_entry *e) +{ + struct object_entry *child; + + for (child = e->delta_child; child; child = child->delta_sibling) + add_to_write_order(wo, endp, child); + for (child = e->delta_child; child; child = child->delta_sibling) + add_descendants_to_write_order(wo, endp, child); +} + +static void add_family_to_write_order(struct object_entry **wo, + int *endp, + struct object_entry *e) +{ + struct object_entry *root; + + for (root = e; root->delta; root = root->delta) + ; /* nothing */ + add_to_write_order(wo, endp, root); + add_descendants_to_write_order(wo, endp, root); +} + +static struct object_entry **compute_write_order(void) +{ + int i, wo_end; + + struct object_entry **wo = xmalloc(nr_objects * sizeof(*wo)); + + for (i = 0; i < nr_objects; i++) { + objects[i].tagged = 0; + objects[i].filled = 0; + objects[i].delta_child = NULL; + objects[i].delta_sibling = NULL; + } + + /* + * Fully connect delta_child/delta_sibling network. + * Make sure delta_sibling is sorted in the original + * recency order. + */ + for (i = nr_objects - 1; 0 <= i; i--) { + struct object_entry *e = &objects[i]; + if (!e->delta) + continue; + /* Mark me as the first child */ + e->delta_sibling = e->delta->delta_child; + e->delta->delta_child = e; + } + + /* + * Mark objects that are at the tip of tags. + */ + for_each_tag_ref(mark_tagged, NULL); + + /* + * Give the commits in the original recency order until + * we see a tagged tip. + */ + for (i = wo_end = 0; i < nr_objects; i++) { + if (objects[i].tagged) + break; + add_to_write_order(wo, &wo_end, &objects[i]); + } + + /* + * Then fill all the tagged tips. + */ + for (; i < nr_objects; i++) { + if (objects[i].tagged) + add_to_write_order(wo, &wo_end, &objects[i]); + } + + /* + * And then all remaining commits and tags. + */ + for (i = 0; i < nr_objects; i++) { + if (objects[i].type != OBJ_COMMIT && + objects[i].type != OBJ_TAG) + continue; + add_to_write_order(wo, &wo_end, &objects[i]); + } + + /* + * And then all the trees. + */ + for (i = 0; i < nr_objects; i++) { + if (objects[i].type != OBJ_TREE) + continue; + add_to_write_order(wo, &wo_end, &objects[i]); + } + + /* + * Finally all the rest in really tight order + */ + for (i = 0; i < nr_objects; i++) + add_family_to_write_order(wo, &wo_end, &objects[i]); + + return wo; +} + static void write_pack_file(void) { uint32_t i = 0, j; @@ -445,10 +574,12 @@ static void write_pack_file(void) struct pack_header hdr; uint32_t nr_remaining = nr_result; time_t last_mtime = 0; + struct object_entry **write_order; if (progress > pack_to_stdout) progress_state = start_progress("Writing objects", nr_result); written_list = xmalloc(nr_objects * sizeof(*written_list)); + write_order = compute_write_order(); do { unsigned char sha1[20]; @@ -472,7 +603,8 @@ static void write_pack_file(void) offset = sizeof(hdr); nr_written = 0; for (; i < nr_objects; i++) { - if (!write_one(f, objects + i, &offset)) + struct object_entry *e = write_order[i]; + if (!write_one(f, e, &offset)) break; display_progress(progress_state, written); } @@ -497,8 +629,8 @@ static void write_pack_file(void) const char *idx_tmp_name; char tmpname[PATH_MAX]; - idx_tmp_name = write_idx_file(NULL, written_list, - nr_written, sha1); + idx_tmp_name = write_idx_file(NULL, written_list, nr_written, + &pack_idx_opts, sha1); snprintf(tmpname, sizeof(tmpname), "%s-%s.pack", base_name, sha1_to_hex(sha1)); @@ -549,6 +681,7 @@ static void write_pack_file(void) } while (nr_remaining && i < nr_objects); free(written_list); + free(write_order); stop_progress(&progress_state); if (written != nr_result) die("wrote %"PRIu32" objects while expecting %"PRIu32, @@ -637,7 +770,7 @@ static int no_try_delta(const char *path) struct git_attr_check check[1]; setup_delta_attr_check(check); - if (git_checkattr(path, ARRAY_SIZE(check), check)) + if (git_check_attr(path, ARRAY_SIZE(check), check)) return 0; if (ATTR_FALSE(check->value)) return 1; @@ -998,7 +1131,7 @@ static void check_object(struct object_entry *entry) const unsigned char *base_ref = NULL; struct object_entry *base_entry; unsigned long used, used_0; - unsigned int avail; + unsigned long avail; off_t ofs; unsigned char *buf, c; @@ -1146,8 +1279,12 @@ static void get_object_details(void) sorted_by_offset[i] = objects + i; qsort(sorted_by_offset, nr_objects, sizeof(*sorted_by_offset), pack_offset_sort); - for (i = 0; i < nr_objects; i++) - check_object(sorted_by_offset[i]); + for (i = 0; i < nr_objects; i++) { + struct object_entry *entry = sorted_by_offset[i]; + check_object(entry); + if (big_file_threshold <= entry->size) + entry->no_try_delta = 1; + } free(sorted_by_offset); } @@ -1298,9 +1435,23 @@ static int try_delta(struct unpacked *trg, struct unpacked *src, read_lock(); src->data = read_sha1_file(src_entry->idx.sha1, &type, &sz); read_unlock(); - if (!src->data) + if (!src->data) { + if (src_entry->preferred_base) { + static int warned = 0; + if (!warned++) + warning("object %s cannot be read", + sha1_to_hex(src_entry->idx.sha1)); + /* + * Those objects are not included in the + * resulting pack. Be resilient and ignore + * them if they can't be read, in case the + * pack could be created nevertheless. + */ + return 0; + } die("object %s cannot be read", sha1_to_hex(src_entry->idx.sha1)); + } if (sz != src_size) die("object %s inconsistent object length (%lu vs %lu)", sha1_to_hex(src_entry->idx.sha1), sz, src_size); @@ -1529,7 +1680,7 @@ static void try_to_free_from_threads(size_t size) read_unlock(); } -try_to_free_t old_try_to_free_routine; +static try_to_free_t old_try_to_free_routine; /* * The main thread waits on the condition that (at least) one of the workers @@ -1870,10 +2021,10 @@ static int git_pack_config(const char *k, const char *v, void *cb) return 0; } if (!strcmp(k, "pack.indexversion")) { - pack_idx_default_version = git_config_int(k, v); - if (pack_idx_default_version > 2) + pack_idx_opts.version = git_config_int(k, v); + if (pack_idx_opts.version > 2) die("bad pack.indexversion=%"PRIu32, - pack_idx_default_version); + pack_idx_opts.version); return 0; } if (!strcmp(k, "pack.packsizelimit")) { @@ -2120,6 +2271,7 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) rp_av[1] = "--objects"; /* --thin will make it --objects-edge */ rp_ac = 2; + reset_pack_idx_option(&pack_idx_opts); git_config(git_pack_config, NULL); if (!pack_compression_seen && core_compression_seen) pack_compression_level = core_compression_level; @@ -2264,12 +2416,12 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) } if (!prefixcmp(arg, "--index-version=")) { char *c; - pack_idx_default_version = strtoul(arg + 16, &c, 10); - if (pack_idx_default_version > 2) + pack_idx_opts.version = strtoul(arg + 16, &c, 10); + if (pack_idx_opts.version > 2) die("bad %s", arg); if (*c == ',') - pack_idx_off32_limit = strtoul(c+1, &c, 0); - if (*c || pack_idx_off32_limit & 0x80000000) + pack_idx_opts.off32_limit = strtoul(c+1, &c, 0); + if (*c || pack_idx_opts.off32_limit & 0x80000000) die("bad %s", arg); continue; } diff --git a/builtin/pack-redundant.c b/builtin/pack-redundant.c index 41e1615a28..f5c6afc5dd 100644 --- a/builtin/pack-redundant.c +++ b/builtin/pack-redundant.c @@ -6,8 +6,7 @@ * */ -#include "cache.h" -#include "exec_cmd.h" +#include "builtin.h" #define BLKSIZE 512 diff --git a/builtin/pack-refs.c b/builtin/pack-refs.c index 091860b2e3..39a9d89fbd 100644 --- a/builtin/pack-refs.c +++ b/builtin/pack-refs.c @@ -1,4 +1,4 @@ -#include "cache.h" +#include "builtin.h" #include "parse-options.h" #include "pack-refs.h" diff --git a/builtin/patch-id.c b/builtin/patch-id.c index 512530022e..f821eb3f0b 100644 --- a/builtin/patch-id.c +++ b/builtin/patch-id.c @@ -1,5 +1,4 @@ -#include "cache.h" -#include "exec_cmd.h" +#include "builtin.h" static void flush_current_id(int patchlen, unsigned char *id, git_SHA_CTX *c) { @@ -57,7 +56,7 @@ static int scan_hunk_header(const char *p, int *p_before, int *p_after) return 1; } -int get_one_patchid(unsigned char *next_sha1, git_SHA_CTX *ctx) +static int get_one_patchid(unsigned char *next_sha1, git_SHA_CTX *ctx) { static char line[1000]; int patchlen = 0, found_next = 0; @@ -73,6 +72,8 @@ int get_one_patchid(unsigned char *next_sha1, git_SHA_CTX *ctx) p += 7; else if (!memcmp(line, "From ", 5)) p += 5; + else if (!memcmp(line, "\\ ", 2) && 12 < strlen(line)) + continue; if (!get_sha1_hex(p, next_sha1)) { found_next = 1; diff --git a/builtin/prune.c b/builtin/prune.c index 81f915ec31..e65690ba37 100644 --- a/builtin/prune.c +++ b/builtin/prune.c @@ -125,10 +125,8 @@ int cmd_prune(int argc, const char **argv, const char *prefix) { struct rev_info revs; const struct option options[] = { - OPT_BOOLEAN('n', NULL, &show_only, - "do not remove, show only"), - OPT_BOOLEAN('v', NULL, &verbose, - "report pruned objects"), + OPT__DRY_RUN(&show_only, "do not remove, show only"), + OPT__VERBOSE(&verbose, "report pruned objects"), OPT_DATE(0, "expire", &expire, "expire objects older than <time>"), OPT_END() diff --git a/builtin/push.c b/builtin/push.c index f4358b9d23..35cce532f2 100644 --- a/builtin/push.c +++ b/builtin/push.c @@ -8,6 +8,7 @@ #include "remote.h" #include "transport.h" #include "parse-options.h" +#include "submodule.h" static const char * const push_usage[] = { "git push [<options>] [<repository> [<refspec>...]]", @@ -22,13 +23,13 @@ static int progress; static const char **refspec; static int refspec_nr; +static int refspec_alloc; static void add_refspec(const char *ref) { - int nr = refspec_nr + 1; - refspec = xrealloc(refspec, nr * sizeof(char *)); - refspec[nr-1] = ref; - refspec_nr = nr; + refspec_nr++; + ALLOC_GROW(refspec, refspec_nr, refspec_alloc); + refspec[refspec_nr-1] = ref; } static void set_refspecs(const char **refs, int nr) @@ -40,7 +41,7 @@ static void set_refspecs(const char **refs, int nr) char *tag; int len; if (nr <= ++i) - die("tag shorthand without <tag>"); + die(_("tag shorthand without <tag>")); len = strlen(refs[i]) + 11; if (deleterefs) { tag = xmalloc(len+1); @@ -59,28 +60,38 @@ static void set_refspecs(const char **refs, int nr) strcat(delref, ref); ref = delref; } else if (deleterefs) - die("--delete only accepts plain target ref names"); + die(_("--delete only accepts plain target ref names")); add_refspec(ref); } } -static void setup_push_tracking(void) +static void setup_push_upstream(struct remote *remote) { struct strbuf refspec = STRBUF_INIT; struct branch *branch = branch_get(NULL); if (!branch) - die("You are not currently on a 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); if (!branch->merge_nr || !branch->merge) - die("The current branch %s is not tracking anything.", + die(_("The current branch %s has no upstream branch.\n" + "To push the current branch and set the remote as upstream, use\n" + "\n" + " git push --set-upstream %s %s\n"), + branch->name, + remote->name, branch->name); if (branch->merge_nr != 1) - die("The current branch %s is tracking multiple branches, " - "refusing to push.", branch->name); + die(_("The current branch %s has multiple upstream branches, " + "refusing to push."), branch->name); strbuf_addf(&refspec, "%s:%s", branch->name, branch->merge[0]->src); add_refspec(refspec.buf); } -static void setup_default_push_refspecs(void) +static void setup_default_push_refspecs(struct remote *remote) { switch (push_default) { default: @@ -88,8 +99,8 @@ static void setup_default_push_refspecs(void) add_refspec(":"); break; - case PUSH_DEFAULT_TRACKING: - setup_push_tracking(); + case PUSH_DEFAULT_UPSTREAM: + setup_push_upstream(remote); break; case PUSH_DEFAULT_CURRENT: @@ -97,8 +108,8 @@ static void setup_default_push_refspecs(void) break; case PUSH_DEFAULT_NOTHING: - die("You didn't specify any refspecs to push, and " - "push.default is \"nothing\"."); + die(_("You didn't specify any refspecs to push, and " + "push.default is \"nothing\".")); break; } } @@ -117,11 +128,11 @@ static int push_with_options(struct transport *transport, int flags) transport_set_option(transport, TRANS_OPT_THIN, "yes"); if (verbosity > 0) - fprintf(stderr, "Pushing to %s\n", transport->url); + fprintf(stderr, _("Pushing to %s\n"), transport->url); err = transport_push(transport, refspec_nr, refspec, flags, &nonfastforward); if (err != 0) - error("failed to push some refs to '%s'", transport->url); + error(_("failed to push some refs to '%s'"), transport->url); err |= transport_disconnect(transport); @@ -129,9 +140,9 @@ static int push_with_options(struct transport *transport, int flags) return 0; if (nonfastforward && advice_push_nonfastforward) { - fprintf(stderr, "To prevent you from losing history, non-fast-forward updates were rejected\n" - "Merge the remote changes before pushing again. See the 'Note about\n" - "fast-forwards' section of 'git push --help' for details.\n"); + fprintf(stderr, _("To prevent you from losing history, non-fast-forward updates were rejected\n" + "Merge the remote changes (e.g. 'git pull') before pushing again. See the\n" + "'Note about fast-forwards' section of 'git push --help' for details.\n")); } return 1; @@ -146,8 +157,15 @@ static int do_push(const char *repo, int flags) if (!remote) { if (repo) - die("bad repository '%s'", repo); - die("No destination configured to push to."); + die(_("bad repository '%s'"), repo); + die(_("No configured push destination.\n" + "Either specify the URL from the command-line or configure a remote repository using\n" + "\n" + " git remote add <name> <url>\n" + "\n" + "and then push using the remote name\n" + "\n" + " git push <name>\n")); } if (remote->mirror) @@ -155,19 +173,19 @@ static int do_push(const char *repo, int flags) if ((flags & TRANSPORT_PUSH_ALL) && refspec) { if (!strcmp(*refspec, "refs/tags/*")) - return error("--all and --tags are incompatible"); - return error("--all can't be combined with refspecs"); + return error(_("--all and --tags are incompatible")); + return error(_("--all can't be combined with refspecs")); } if ((flags & TRANSPORT_PUSH_MIRROR) && refspec) { if (!strcmp(*refspec, "refs/tags/*")) - return error("--mirror and --tags are incompatible"); - return error("--mirror can't be combined with refspecs"); + return error(_("--mirror and --tags are incompatible")); + return error(_("--mirror can't be combined with refspecs")); } if ((flags & (TRANSPORT_PUSH_ALL|TRANSPORT_PUSH_MIRROR)) == (TRANSPORT_PUSH_ALL|TRANSPORT_PUSH_MIRROR)) { - return error("--all and --mirror are incompatible"); + return error(_("--all and --mirror are incompatible")); } if (!refspec && !(flags & TRANSPORT_PUSH_ALL)) { @@ -175,7 +193,7 @@ static int do_push(const char *repo, int flags) refspec = remote->push_refspec; refspec_nr = remote->push_refspec_nr; } else if (!(flags & TRANSPORT_PUSH_MIRROR)) - setup_default_push_refspecs(); + setup_default_push_refspecs(remote); } errs = 0; if (remote->pushurl_nr) { @@ -202,6 +220,21 @@ static int do_push(const char *repo, int flags) return !!errs; } +static int option_parse_recurse_submodules(const struct option *opt, + const char *arg, int unset) +{ + int *flags = opt->value; + if (arg) { + if (!strcmp(arg, "check")) + *flags |= TRANSPORT_RECURSE_SUBMODULES_CHECK; + else + die("bad %s argument: %s", opt->long_name, arg); + } else + die("option %s needs an argument (check)", opt->long_name); + + return 0; +} + int cmd_push(int argc, const char **argv, const char *prefix) { int flags = 0; @@ -219,6 +252,9 @@ int cmd_push(int argc, const char **argv, const char *prefix) OPT_BIT('n' , "dry-run", &flags, "dry run", TRANSPORT_PUSH_DRY_RUN), OPT_BIT( 0, "porcelain", &flags, "machine-readable output", TRANSPORT_PUSH_PORCELAIN), OPT_BIT('f', "force", &flags, "force updates", TRANSPORT_PUSH_FORCE), + { OPTION_CALLBACK, 0, "recurse-submodules", &flags, "check", + "controls recursive pushing of submodules", + PARSE_OPT_OPTARG, option_parse_recurse_submodules }, OPT_BOOLEAN( 0 , "thin", &thin, "use thin pack"), OPT_STRING( 0 , "receive-pack", &receivepack, "receive-pack", "receive pack program"), OPT_STRING( 0 , "exec", &receivepack, "receive-pack", "receive pack program"), @@ -228,13 +264,14 @@ int cmd_push(int argc, const char **argv, const char *prefix) OPT_END() }; + packet_trace_identity("push"); git_config(git_default_config, NULL); argc = parse_options(argc, argv, prefix, options, push_usage, 0); if (deleterefs && (tags || (flags & (TRANSPORT_PUSH_ALL | TRANSPORT_PUSH_MIRROR)))) - die("--delete is incompatible with --all, --mirror and --tags"); + die(_("--delete is incompatible with --all, --mirror and --tags")); if (deleterefs && argc < 2) - die("--delete doesn't make sense without any refs"); + die(_("--delete doesn't make sense without any refs")); if (tags) add_refspec("refs/tags/*"); diff --git a/builtin/read-tree.c b/builtin/read-tree.c index 9ad1e66916..df6c4c8819 100644 --- a/builtin/read-tree.c +++ b/builtin/read-tree.c @@ -16,6 +16,7 @@ #include "resolve-undo.h" static int nr_trees; +static int read_empty; static struct tree *trees[MAX_UNPACK_TREES]; static int list_tree(unsigned char *sha1) @@ -32,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>] <tree-ish1> [<tree-ish2> [<tree-ish3>]]", + "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 }; @@ -103,10 +104,12 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix) struct unpack_trees_options opts; int prefix_set = 0; const struct option read_tree_options[] = { - { OPTION_CALLBACK, 0, "index-output", NULL, "FILE", - "write resulting index to <FILE>", + { OPTION_CALLBACK, 0, "index-output", NULL, "file", + "write resulting index to <file>", PARSE_OPT_NONEG, index_output_cb }, - OPT__VERBOSE(&opts.verbose_update), + OPT_SET_INT(0, "empty", &read_empty, + "only empty the index", 1), + OPT__VERBOSE(&opts.verbose_update, "be verbose"), OPT_GROUP("Merging"), OPT_SET_INT('m', NULL, &opts.merge, "perform a merge in addition to a read", 1), @@ -127,6 +130,7 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix) PARSE_OPT_NONEG, exclude_per_directory_cb }, OPT_SET_INT('i', NULL, &opts.index_only, "don't check the working tree after merging", 1), + OPT__DRY_RUN(&opts.dry_run, "don't update the index or the work tree"), OPT_SET_INT(0, "no-sparse-checkout", &opts.skip_sparse_checkout, "skip applying sparse checkout filter", 1), OPT_SET_INT(0, "debug-unpack", &opts.debug_unpack, @@ -166,6 +170,11 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix) die("failed to unpack tree object %s", arg); stage++; } + if (nr_trees == 0 && !read_empty) + warning("read-tree: emptying the index with no arguments is deprecated; use --empty"); + else if (nr_trees > 0 && read_empty) + die("passing trees as arguments contradicts --empty"); + if (1 < opts.index_only + opts.update) die("-u and -i at the same time makes no sense"); if ((opts.update||opts.index_only) && !opts.merge) @@ -211,7 +220,7 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix) if (unpack_trees(nr_trees, t, &opts)) return 128; - if (opts.debug_unpack) + if (opts.debug_unpack || opts.dry_run) return 0; /* do not write the index out */ /* diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c index d634b5a3d5..60260d0aa9 100644 --- a/builtin/receive-pack.c +++ b/builtin/receive-pack.c @@ -1,4 +1,4 @@ -#include "cache.h" +#include "builtin.h" #include "pack.h" #include "refs.h" #include "pkt-line.h" @@ -10,6 +10,7 @@ #include "remote.h" #include "transport.h" #include "string-list.h" +#include "sha1-array.h" static const char receive_pack_usage[] = "git receive-pack <git-dir>"; @@ -119,9 +120,25 @@ static int show_ref(const char *path, const unsigned char *sha1, int flag, void return 0; } +static int show_ref_cb(const char *path, const unsigned char *sha1, int flag, void *cb_data) +{ + path = strip_namespace(path); + /* + * Advertise refs outside our current namespace as ".have" + * refs, so that the client can use them to minimize data + * transfer but will otherwise ignore them. This happens to + * cover ".have" that are thrown in by add_one_alternate_ref() + * to mark histories that are complete in our alternates as + * well. + */ + if (!path) + path = ".have"; + return show_ref(path, sha1, flag, cb_data); +} + static void write_head_info(void) { - for_each_ref(show_ref, NULL); + for_each_ref(show_ref_cb, NULL); if (!sent_capabilities) show_ref("capabilities^{}", null_sha1, 0, NULL); @@ -332,6 +349,8 @@ static void refuse_unconfigured_deny_delete_current(void) static const char *update(struct command *cmd) { const char *name = cmd->ref_name; + struct strbuf namespaced_name_buf = STRBUF_INIT; + const char *namespaced_name; unsigned char *old_sha1 = cmd->old_sha1; unsigned char *new_sha1 = cmd->new_sha1; struct ref_lock *lock; @@ -342,7 +361,10 @@ static const char *update(struct command *cmd) return "funny refname"; } - if (is_ref_checked_out(name)) { + strbuf_addf(&namespaced_name_buf, "%s%s", get_git_namespace(), name); + namespaced_name = strbuf_detach(&namespaced_name_buf, NULL); + + if (is_ref_checked_out(namespaced_name)) { switch (deny_current_branch) { case DENY_IGNORE: break; @@ -370,7 +392,7 @@ static const char *update(struct command *cmd) return "deletion prohibited"; } - if (!strcmp(name, head_name)) { + if (!strcmp(namespaced_name, head_name)) { switch (deny_delete_current) { case DENY_IGNORE: break; @@ -426,14 +448,14 @@ static const char *update(struct command *cmd) rp_warning("Allowing deletion of corrupt ref."); old_sha1 = NULL; } - if (delete_ref(name, old_sha1, 0)) { + if (delete_ref(namespaced_name, old_sha1, 0)) { rp_error("failed to delete %s", name); return "failed to delete"; } return NULL; /* good */ } else { - lock = lock_any_ref_for_update(name, old_sha1, 0); + lock = lock_any_ref_for_update(namespaced_name, old_sha1, 0); if (!lock) { rp_error("failed to lock %s", name); return "failed to lock"; @@ -490,17 +512,29 @@ static void run_update_post_hook(struct command *commands) static void check_aliased_update(struct command *cmd, struct string_list *list) { + struct strbuf buf = STRBUF_INIT; + const char *dst_name; struct string_list_item *item; struct command *dst_cmd; unsigned char sha1[20]; char cmd_oldh[41], cmd_newh[41], dst_oldh[41], dst_newh[41]; int flag; - const char *dst_name = resolve_ref(cmd->ref_name, sha1, 0, &flag); + strbuf_addf(&buf, "%s%s", get_git_namespace(), cmd->ref_name); + dst_name = resolve_ref(buf.buf, sha1, 0, &flag); + strbuf_release(&buf); if (!(flag & REF_ISSYMREF)) return; + dst_name = strip_namespace(dst_name); + if (!dst_name) { + rp_error("refusing update to broken symref '%s'", cmd->ref_name); + cmd->skip_update = 1; + cmd->error_string = "broken symref"; + return; + } + if ((item = string_list_lookup(list, dst_name)) == NULL) return; @@ -530,7 +564,7 @@ static void check_aliased_update(struct command *cmd, struct string_list *list) static void check_aliased_updates(struct command *commands) { struct command *cmd; - struct string_list ref_list = { NULL, 0, 0, 0 }; + struct string_list ref_list = STRING_LIST_INIT_NODUP; for (cmd = commands; cmd; cmd = cmd->next) { struct string_list_item *item = @@ -635,7 +669,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 quiet) { struct pack_header hdr; const char *hdr_err; @@ -650,8 +684,10 @@ static const char *unpack(void) if (ntohl(hdr.hdr_entries) < unpack_limit) { int code, i = 0; - const char *unpacker[4]; + const char *unpacker[5]; unpacker[i++] = "unpack-objects"; + if (quiet) + unpacker[i++] = "-q"; if (receive_fsck_objects) unpacker[i++] = "--strict"; unpacker[i++] = hdr_arg; @@ -731,58 +767,46 @@ static int delete_only(struct command *commands) return 1; } -static int add_refs_from_alternate(struct alternate_object_database *e, void *unused) +static void add_one_alternate_sha1(const unsigned char sha1[20], void *unused) { - char *other; - size_t len; - struct remote *remote; - struct transport *transport; - const struct ref *extra; - - e->name[-1] = '\0'; - other = xstrdup(make_absolute_path(e->base)); - e->name[-1] = '/'; - len = strlen(other); - - while (other[len-1] == '/') - other[--len] = '\0'; - if (len < 8 || memcmp(other + len - 8, "/objects", 8)) - return 0; - /* Is this a git repository with refs? */ - memcpy(other + len - 8, "/refs", 6); - if (!is_directory(other)) - return 0; - other[len - 8] = '\0'; - remote = remote_get(other); - transport = transport_get(remote, other); - for (extra = transport_get_remote_refs(transport); - extra; - extra = extra->next) { - add_extra_ref(".have", extra->old_sha1, 0); - } - transport_disconnect(transport); - free(other); - return 0; + add_extra_ref(".have", sha1, 0); +} + +static void collect_one_alternate_ref(const struct ref *ref, void *data) +{ + struct sha1_array *sa = data; + sha1_array_append(sa, ref->old_sha1); } static void add_alternate_refs(void) { - foreach_alt_odb(add_refs_from_alternate, NULL); + struct sha1_array sa = SHA1_ARRAY_INIT; + for_each_alternate_ref(collect_one_alternate_ref, &sa); + sha1_array_for_each_unique(&sa, add_one_alternate_sha1, NULL); + sha1_array_clear(&sa); } int cmd_receive_pack(int argc, const char **argv, const char *prefix) { + int quiet = 0; int advertise_refs = 0; int stateless_rpc = 0; int i; char *dir = NULL; struct command *commands; + packet_trace_identity("receive-pack"); + argv++; for (i = 1; i < argc; i++) { const char *arg = *argv++; if (*arg == '-') { + if (!strcmp(arg, "--quiet")) { + quiet = 1; + continue; + } + if (!strcmp(arg, "--advertise-refs")) { advertise_refs = 1; continue; @@ -831,7 +855,7 @@ 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(quiet); execute_commands(commands, unpack_status); if (pack_lockfile) unlink_or_warn(pack_lockfile); diff --git a/builtin/reflog.c b/builtin/reflog.c index ebf610e64a..3a9c80f3db 100644 --- a/builtin/reflog.c +++ b/builtin/reflog.c @@ -777,6 +777,5 @@ int cmd_reflog(int argc, const char **argv, const char *prefix) if (!strcmp(argv[1], "delete")) return cmd_reflog_delete(argc - 1, argv + 1, prefix); - /* Not a recognized reflog command..*/ - usage(reflog_usage); + return cmd_log_reflog(argc, argv, prefix); } diff --git a/builtin/remote-ext.c b/builtin/remote-ext.c new file mode 100644 index 0000000000..692c834d9d --- /dev/null +++ b/builtin/remote-ext.c @@ -0,0 +1,242 @@ +#include "builtin.h" +#include "transport.h" +#include "run-command.h" + +/* + * URL syntax: + * 'command [arg1 [arg2 [...]]]' Invoke command with given arguments. + * Special characters: + * '% ': Literal space in argument. + * '%%': Literal percent sign. + * '%S': Name of service (git-upload-pack/git-upload-archive/ + * git-receive-pack. + * '%s': Same as \s, but with possible git- prefix stripped. + * '%G': Only allowed as first 'character' of argument. Do not pass this + * Argument to command, instead send this as name of repository + * in in-line git://-style request (also activates sending this + * style of request). + * '%V': Only allowed as first 'character' of argument. Used in + * conjunction with '%G': Do not pass this argument to command, + * instead send this as vhost in git://-style request (note: does + * not activate sending git:// style request). + */ + +static char *git_req; +static char *git_req_vhost; + +static char *strip_escapes(const char *str, const char *service, + const char **next) +{ + size_t rpos = 0; + int escape = 0; + char special = 0; + size_t psoff = 0; + struct strbuf ret = STRBUF_INIT; + + /* Calculate prefix length for \s and lengths for \s and \S */ + if (!strncmp(service, "git-", 4)) + psoff = 4; + + /* Pass the service to command. */ + setenv("GIT_EXT_SERVICE", service, 1); + setenv("GIT_EXT_SERVICE_NOPREFIX", service + psoff, 1); + + /* Scan the length of argument. */ + while (str[rpos] && (escape || str[rpos] != ' ')) { + if (escape) { + switch (str[rpos]) { + case ' ': + case '%': + case 's': + case 'S': + break; + case 'G': + case 'V': + special = str[rpos]; + if (rpos == 1) + break; + /* Fall-through to error. */ + default: + die("Bad remote-ext placeholder '%%%c'.", + str[rpos]); + } + escape = 0; + } else + escape = (str[rpos] == '%'); + rpos++; + } + if (escape && !str[rpos]) + die("remote-ext command has incomplete placeholder"); + *next = str + rpos; + if (**next == ' ') + ++*next; /* Skip over space */ + + /* + * Do the actual placeholder substitution. The string will be short + * enough not to overflow integers. + */ + rpos = special ? 2 : 0; /* Skip first 2 bytes in specials. */ + escape = 0; + while (str[rpos] && (escape || str[rpos] != ' ')) { + if (escape) { + switch (str[rpos]) { + case ' ': + case '%': + strbuf_addch(&ret, str[rpos]); + break; + case 's': + strbuf_addstr(&ret, service + psoff); + break; + case 'S': + strbuf_addstr(&ret, service); + break; + } + escape = 0; + } else + switch (str[rpos]) { + case '%': + escape = 1; + break; + default: + strbuf_addch(&ret, str[rpos]); + break; + } + rpos++; + } + switch (special) { + case 'G': + git_req = strbuf_detach(&ret, NULL); + return NULL; + case 'V': + git_req_vhost = strbuf_detach(&ret, NULL); + return NULL; + default: + return strbuf_detach(&ret, NULL); + } +} + +/* Should be enough... */ +#define MAXARGUMENTS 256 + +static const char **parse_argv(const char *arg, const char *service) +{ + int arguments = 0; + int i; + const char **ret; + char *temparray[MAXARGUMENTS + 1]; + + while (*arg) { + char *expanded; + if (arguments == MAXARGUMENTS) + die("remote-ext command has too many arguments"); + expanded = strip_escapes(arg, service, &arg); + if (expanded) + temparray[arguments++] = expanded; + } + + ret = xmalloc((arguments + 1) * sizeof(char *)); + for (i = 0; i < arguments; i++) + ret[i] = temparray[i]; + ret[arguments] = NULL; + return ret; +} + +static void send_git_request(int stdin_fd, const char *serv, const char *repo, + const char *vhost) +{ + size_t bufferspace; + size_t wpos = 0; + char *buffer; + + /* + * Request needs 12 bytes extra if there is vhost (xxxx \0host=\0) and + * 6 bytes extra (xxxx \0) if there is no vhost. + */ + if (vhost) + bufferspace = strlen(serv) + strlen(repo) + strlen(vhost) + 12; + else + bufferspace = strlen(serv) + strlen(repo) + 6; + + if (bufferspace > 0xFFFF) + die("Request too large to send"); + buffer = xmalloc(bufferspace); + + /* Make the packet. */ + wpos = sprintf(buffer, "%04x%s %s%c", (unsigned)bufferspace, + serv, repo, 0); + + /* Add vhost if any. */ + if (vhost) + sprintf(buffer + wpos, "host=%s%c", vhost, 0); + + /* Send the request */ + if (write_in_full(stdin_fd, buffer, bufferspace) < 0) + die_errno("Failed to send request"); + + free(buffer); +} + +static int run_child(const char *arg, const char *service) +{ + int r; + struct child_process child; + + memset(&child, 0, sizeof(child)); + child.in = -1; + child.out = -1; + child.err = 0; + child.argv = parse_argv(arg, service); + + if (start_command(&child) < 0) + die("Can't run specified command"); + + if (git_req) + send_git_request(child.in, service, git_req, git_req_vhost); + + r = bidirectional_transfer_loop(child.out, child.in); + if (!r) + r = finish_command(&child); + else + finish_command(&child); + return r; +} + +#define MAXCOMMAND 4096 + +static int command_loop(const char *child) +{ + char buffer[MAXCOMMAND]; + + while (1) { + size_t i; + if (!fgets(buffer, MAXCOMMAND - 1, stdin)) { + if (ferror(stdin)) + die("Comammand input error"); + exit(0); + } + /* Strip end of line characters. */ + i = strlen(buffer); + while (i > 0 && isspace(buffer[i - 1])) + buffer[--i] = 0; + + if (!strcmp(buffer, "capabilities")) { + printf("*connect\n\n"); + fflush(stdout); + } else if (!strncmp(buffer, "connect ", 8)) { + printf("\n"); + fflush(stdout); + return run_child(child, buffer + 8); + } else { + fprintf(stderr, "Bad command"); + return 1; + } + } +} + +int cmd_remote_ext(int argc, const char **argv, const char *prefix) +{ + if (argc != 3) + die("Expected two arguments"); + + return command_loop(argv[2]); +} diff --git a/builtin/remote-fd.c b/builtin/remote-fd.c new file mode 100644 index 0000000000..08d7121b6d --- /dev/null +++ b/builtin/remote-fd.c @@ -0,0 +1,79 @@ +#include "builtin.h" +#include "transport.h" + +/* + * URL syntax: + * 'fd::<inoutfd>[/<anything>]' Read/write socket pair + * <inoutfd>. + * 'fd::<infd>,<outfd>[/<anything>]' Read pipe <infd> and write + * pipe <outfd>. + * [foo] indicates 'foo' is optional. <anything> is any string. + * + * The data output to <outfd>/<inoutfd> should be passed unmolested to + * git-receive-pack/git-upload-pack/git-upload-archive and output of + * git-receive-pack/git-upload-pack/git-upload-archive should be passed + * unmolested to <infd>/<inoutfd>. + * + */ + +#define MAXCOMMAND 4096 + +static void command_loop(int input_fd, int output_fd) +{ + char buffer[MAXCOMMAND]; + + while (1) { + size_t i; + if (!fgets(buffer, MAXCOMMAND - 1, stdin)) { + if (ferror(stdin)) + die("Input error"); + return; + } + /* Strip end of line characters. */ + i = strlen(buffer); + while (i > 0 && isspace(buffer[i - 1])) + buffer[--i] = 0; + + if (!strcmp(buffer, "capabilities")) { + printf("*connect\n\n"); + fflush(stdout); + } else if (!strncmp(buffer, "connect ", 8)) { + printf("\n"); + fflush(stdout); + if (bidirectional_transfer_loop(input_fd, + output_fd)) + die("Copying data between file descriptors failed"); + return; + } else { + die("Bad command: %s", buffer); + } + } +} + +int cmd_remote_fd(int argc, const char **argv, const char *prefix) +{ + int input_fd = -1; + int output_fd = -1; + char *end; + + if (argc != 3) + die("Expected two arguments"); + + input_fd = (int)strtoul(argv[2], &end, 10); + + if ((end == argv[2]) || (*end != ',' && *end != '/' && *end)) + die("Bad URL syntax"); + + if (*end == '/' || !*end) { + output_fd = input_fd; + } else { + char *end2; + output_fd = (int)strtoul(end + 1, &end2, 10); + + if ((end2 == end + 1) || (*end2 != '/' && *end2)) + die("Bad URL syntax"); + } + + command_loop(input_fd, output_fd); + return 0; +} diff --git a/builtin/remote.c b/builtin/remote.c index 6699bc5712..f2a9c26dc3 100644 --- a/builtin/remote.c +++ b/builtin/remote.c @@ -1,4 +1,4 @@ -#include "cache.h" +#include "builtin.h" #include "parse-options.h" #include "transport.h" #include "remote.h" @@ -9,13 +9,13 @@ static const char * const builtin_remote_usage[] = { "git remote [-v | --verbose]", - "git remote add [-t <branch>] [-m <master>] [-f] [--mirror] <name> <url>", + "git remote add [-t <branch>] [-m <master>] [-f] [--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 [-v | --verbose] update [-p | --prune] [(<group> | <remote>)...]", "git remote set-branches <name> [--add] <branch>...", "git remote set-url <name> <newurl> [<oldurl>]", "git remote set-url --add <name> <newurl>", @@ -88,16 +88,6 @@ static inline int postfixcmp(const char *string, const char *postfix) return strcmp(string + len1 - len2, postfix); } -static int opt_parse_track(const struct option *opt, const char *arg, int not) -{ - struct string_list *list = opt->value; - if (not) - string_list_clear(list, 0); - else - string_list_append(list, arg); - return 0; -} - static int fetch_remote(const char *name) { const char *argv[] = { "fetch", name, NULL, NULL }; @@ -117,6 +107,11 @@ enum { TAGS_SET = 2 }; +#define MIRROR_NONE 0 +#define MIRROR_FETCH 1 +#define MIRROR_PUSH 2 +#define MIRROR_BOTH (MIRROR_FETCH|MIRROR_PUSH) + static int add_branch(const char *key, const char *branchname, const char *remotename, int mirror, struct strbuf *tmp) { @@ -131,10 +126,33 @@ static int add_branch(const char *key, const char *branchname, return git_config_set_multivar(key, tmp->buf, "^$", 0); } +static const char mirror_advice[] = +"--mirror is dangerous and deprecated; please\n" +"\t use --mirror=fetch or --mirror=push instead"; + +static int parse_mirror_opt(const struct option *opt, const char *arg, int not) +{ + unsigned *mirror = opt->value; + if (not) + *mirror = MIRROR_NONE; + else if (!arg) { + warning("%s", mirror_advice); + *mirror = MIRROR_BOTH; + } + else if (!strcmp(arg, "fetch")) + *mirror = MIRROR_FETCH; + else if (!strcmp(arg, "push")) + *mirror = MIRROR_PUSH; + else + return error("unknown mirror argument: %s", arg); + return 0; +} + static int add(int argc, const char **argv) { - int fetch = 0, mirror = 0, fetch_tags = TAGS_DEFAULT; - struct string_list track = { NULL, 0, 0 }; + int fetch = 0, fetch_tags = TAGS_DEFAULT; + unsigned mirror = MIRROR_NONE; + struct string_list track = STRING_LIST_INIT_NODUP; const char *master = NULL; struct remote *remote; struct strbuf buf = STRBUF_INIT, buf2 = STRBUF_INIT; @@ -148,10 +166,12 @@ static int add(int argc, const char **argv) TAGS_SET), OPT_SET_INT(0, NULL, &fetch_tags, "or do not fetch any tag at all (--no-tags)", TAGS_UNSET), - OPT_CALLBACK('t', "track", &track, "branch", - "branch(es) to track", opt_parse_track), + OPT_STRING_LIST('t', "track", &track, "branch", + "branch(es) to track"), OPT_STRING('m', "master", &master, "branch", "master branch"), - OPT_BOOLEAN(0, "mirror", &mirror, "no separate remotes"), + { OPTION_CALLBACK, 0, "mirror", &mirror, "push|fetch", + "set up remote as a mirror to push to or fetch from", + PARSE_OPT_OPTARG, parse_mirror_opt }, OPT_END() }; @@ -161,6 +181,11 @@ static int add(int argc, const char **argv) if (argc < 2) usage_with_options(builtin_remote_add_usage, options); + if (mirror && master) + die("specifying a master branch makes no sense with --mirror"); + if (mirror && !(mirror & MIRROR_FETCH) && track.nr) + die("specifying branches to track makes sense only with fetch mirrors"); + name = argv[0]; url = argv[1]; @@ -177,18 +202,19 @@ static int add(int argc, const char **argv) if (git_config_set(buf.buf, url)) return 1; - strbuf_reset(&buf); - strbuf_addf(&buf, "remote.%s.fetch", name); - - if (track.nr == 0) - string_list_append(&track, "*"); - for (i = 0; i < track.nr; i++) { - if (add_branch(buf.buf, track.items[i].string, - name, mirror, &buf2)) - return 1; + if (!mirror || mirror & MIRROR_FETCH) { + strbuf_reset(&buf); + strbuf_addf(&buf, "remote.%s.fetch", name); + if (track.nr == 0) + string_list_append(&track, "*"); + for (i = 0; i < track.nr; i++) { + if (add_branch(buf.buf, track.items[i].string, + name, mirror, &buf2)) + return 1; + } } - if (mirror) { + if (mirror & MIRROR_PUSH) { strbuf_reset(&buf); strbuf_addf(&buf, "remote.%s.mirror", name); if (git_config_set(buf.buf, "true")) @@ -507,7 +533,7 @@ static int add_branch_for_removal(const char *refname, return 0; } - /* don't delete non-remote refs */ + /* don't delete non-remote-tracking refs */ if (prefixcmp(refname, "refs/remotes")) { /* advise user how to delete local branches */ if (!prefixcmp(refname, "refs/heads/")) @@ -596,7 +622,7 @@ static int mv(int argc, const char **argv) }; struct remote *oldremote, *newremote; struct strbuf buf = STRBUF_INIT, buf2 = STRBUF_INIT, buf3 = STRBUF_INIT; - struct string_list remote_branches = { NULL, 0, 0, 0 }; + struct string_list remote_branches = STRING_LIST_INIT_NODUP; struct rename_info rename; int i; @@ -734,8 +760,8 @@ static int rm(int argc, const char **argv) struct remote *remote; struct strbuf buf = STRBUF_INIT; struct known_remotes known_remotes = { NULL, NULL }; - struct string_list branches = { NULL, 0, 0, 1 }; - struct string_list skipped = { NULL, 0, 0, 1 }; + struct string_list branches = STRING_LIST_INIT_DUP; + struct string_list skipped = STRING_LIST_INIT_DUP; struct branches_for_remote cb_data; int i, result; @@ -791,9 +817,9 @@ static int rm(int argc, const char **argv) if (skipped.nr) { fprintf(stderr, skipped.nr == 1 ? - "Note: A non-remote branch was not removed; " + "Note: A branch outside the refs/remotes/ hierarchy was not removed;\n" "to delete it, use:\n" : - "Note: Non-remote branches were not removed; " + "Note: Some branches outside the refs/remotes/ hierarchy were not removed;\n" "to delete them, use:\n"); for (i = 0; i < skipped.nr; i++) fprintf(stderr, " git branch -d %s\n", @@ -1044,7 +1070,7 @@ static int show(int argc, const char **argv) OPT_END() }; struct ref_states states; - struct string_list info_list = { NULL, 0, 0, 0 }; + struct string_list info_list = STRING_LIST_INIT_NODUP; struct show_info info; argc = parse_options(argc, argv, NULL, options, builtin_remote_show_usage, @@ -1077,7 +1103,7 @@ static int show(int argc, const char **argv) url = states.remote->url; url_nr = states.remote->url_nr; } - for (i=0; i < url_nr; i++) + for (i = 0; i < url_nr; i++) printf(" Push URL: %s\n", url[i]); if (!i) printf(" Push URL: %s\n", "(no URL)"); @@ -1200,7 +1226,7 @@ static int prune(int argc, const char **argv) { int dry_run = 0, result = 0; struct option options[] = { - OPT__DRY_RUN(&dry_run), + OPT__DRY_RUN(&dry_run, "dry run"), OPT_END() }; @@ -1483,7 +1509,7 @@ static int get_one_entry(struct remote *remote, void *priv) static int show_all(void) { - struct string_list list = { NULL, 0, 0 }; + struct string_list list = STRING_LIST_INIT_NODUP; int result; list.strdup_strings = 1; @@ -1512,7 +1538,7 @@ static int show_all(void) int cmd_remote(int argc, const char **argv, const char *prefix) { struct option options[] = { - OPT_BOOLEAN('v', "verbose", &verbose, "be verbose; must be placed before a subcommand"), + OPT__VERBOSE(&verbose, "be verbose; must be placed before a subcommand"), OPT_END() }; int result; diff --git a/builtin/rerere.c b/builtin/rerere.c index 39ad60169d..08213c7c0b 100644 --- a/builtin/rerere.c +++ b/builtin/rerere.c @@ -1,70 +1,16 @@ #include "builtin.h" #include "cache.h" #include "dir.h" +#include "parse-options.h" #include "string-list.h" #include "rerere.h" #include "xdiff/xdiff.h" #include "xdiff-interface.h" -static const char git_rerere_usage[] = -"git rerere [clear | status | diff | gc]"; - -/* these values are days */ -static int cutoff_noresolve = 15; -static int cutoff_resolve = 60; - -static time_t rerere_created_at(const char *name) -{ - struct stat st; - return stat(rerere_path(name, "preimage"), &st) ? (time_t) 0 : st.st_mtime; -} - -static void unlink_rr_item(const char *name) -{ - unlink(rerere_path(name, "thisimage")); - unlink(rerere_path(name, "preimage")); - unlink(rerere_path(name, "postimage")); - rmdir(git_path("rr-cache/%s", name)); -} - -static int git_rerere_gc_config(const char *var, const char *value, void *cb) -{ - if (!strcmp(var, "gc.rerereresolved")) - cutoff_resolve = git_config_int(var, value); - else if (!strcmp(var, "gc.rerereunresolved")) - cutoff_noresolve = git_config_int(var, value); - else - return git_default_config(var, value, cb); - return 0; -} - -static void garbage_collect(struct string_list *rr) -{ - struct string_list to_remove = { NULL, 0, 0, 1 }; - DIR *dir; - struct dirent *e; - int i, cutoff; - time_t now = time(NULL), then; - - git_config(git_rerere_gc_config, NULL); - dir = opendir(git_path("rr-cache")); - if (!dir) - die_errno("unable to open rr-cache directory"); - while ((e = readdir(dir))) { - if (is_dot_or_dotdot(e->d_name)) - continue; - then = rerere_created_at(e->d_name); - if (!then) - continue; - cutoff = (has_rerere_resolution(e->d_name) - ? cutoff_resolve : cutoff_noresolve); - if (then < now - cutoff * 86400) - string_list_append(&to_remove, e->d_name); - } - for (i = 0; i < to_remove.nr; i++) - unlink_rr_item(to_remove.items[i].string); - string_list_clear(&to_remove, 0); -} +static const char * const rerere_usage[] = { + "git rerere [clear | forget path... | status | remaining | diff | gc]", + NULL, +}; static int outf(void *dummy, mmbuffer_t *ptr, int nbuf) { @@ -102,26 +48,30 @@ static int diff_two(const char *file1, const char *label1, int cmd_rerere(int argc, const char **argv, const char *prefix) { - struct string_list merge_rr = { NULL, 0, 0, 1 }; - int i, fd, flags = 0; + struct string_list merge_rr = STRING_LIST_INIT_DUP; + int i, fd, autoupdate = -1, flags = 0; - if (2 < argc) { - if (!strcmp(argv[1], "-h")) - usage(git_rerere_usage); - if (!strcmp(argv[1], "--rerere-autoupdate")) - flags = RERERE_AUTOUPDATE; - else if (!strcmp(argv[1], "--no-rerere-autoupdate")) - flags = RERERE_NOAUTOUPDATE; - if (flags) { - argc--; - argv++; - } - } - if (argc < 2) + struct option options[] = { + OPT_SET_INT(0, "rerere-autoupdate", &autoupdate, + "register clean resolutions in index", 1), + OPT_END(), + }; + + argc = parse_options(argc, argv, prefix, options, rerere_usage, 0); + + if (autoupdate == 1) + flags = RERERE_AUTOUPDATE; + if (autoupdate == 0) + flags = RERERE_NOAUTOUPDATE; + + if (argc < 1) return rerere(flags); - if (!strcmp(argv[1], "forget")) { - const char **pathspec = get_pathspec(prefix, argv + 2); + if (!strcmp(argv[0], "forget")) { + const char **pathspec; + if (argc < 2) + warning("'git rerere forget' without paths is deprecated"); + pathspec = get_pathspec(prefix, argv + 1); return rerere_forget(pathspec); } @@ -129,26 +79,31 @@ int cmd_rerere(int argc, const char **argv, const char *prefix) if (fd < 0) return 0; - if (!strcmp(argv[1], "clear")) { - for (i = 0; i < merge_rr.nr; i++) { - const char *name = (const char *)merge_rr.items[i].util; - if (!has_rerere_resolution(name)) - unlink_rr_item(name); - } - unlink_or_warn(git_path("MERGE_RR")); - } else if (!strcmp(argv[1], "gc")) - garbage_collect(&merge_rr); - else if (!strcmp(argv[1], "status")) + if (!strcmp(argv[0], "clear")) { + rerere_clear(&merge_rr); + } else if (!strcmp(argv[0], "gc")) + rerere_gc(&merge_rr); + else if (!strcmp(argv[0], "status")) for (i = 0; i < merge_rr.nr; i++) printf("%s\n", merge_rr.items[i].string); - else if (!strcmp(argv[1], "diff")) + else if (!strcmp(argv[0], "remaining")) { + rerere_remaining(&merge_rr); + for (i = 0; i < merge_rr.nr; i++) { + if (merge_rr.items[i].util != RERERE_RESOLVED) + printf("%s\n", merge_rr.items[i].string); + else + /* prepare for later call to + * string_list_clear() */ + merge_rr.items[i].util = NULL; + } + } else if (!strcmp(argv[0], "diff")) for (i = 0; i < merge_rr.nr; i++) { const char *path = merge_rr.items[i].string; const char *name = (const char *)merge_rr.items[i].util; diff_two(rerere_path(name, "preimage"), path, path, path); } else - usage(git_rerere_usage); + usage_with_options(rerere_usage, options); string_list_clear(&merge_rr, 1); return 0; diff --git a/builtin/reset.c b/builtin/reset.c index 1283068fd2..811e8e252c 100644 --- a/builtin/reset.c +++ b/builtin/reset.c @@ -7,7 +7,7 @@ * * Copyright (c) 2005, 2006 Linus Torvalds and Junio C Hamano */ -#include "cache.h" +#include "builtin.h" #include "tag.h" #include "object.h" #include "commit.h" @@ -30,28 +30,9 @@ static const char * const git_reset_usage[] = { enum reset_type { MIXED, SOFT, HARD, MERGE, KEEP, NONE }; static const char *reset_type_names[] = { - "mixed", "soft", "hard", "merge", "keep", NULL + N_("mixed"), N_("soft"), N_("hard"), N_("merge"), N_("keep"), NULL }; -static char *args_to_str(const char **argv) -{ - char *buf = NULL; - unsigned long len, space = 0, nr = 0; - - for (; *argv; argv++) { - len = strlen(*argv); - ALLOC_GROW(buf, nr + 1 + len, space); - if (nr) - buf[nr++] = ' '; - memcpy(buf + nr, *argv, len); - nr += len; - } - ALLOC_GROW(buf, nr + 1, space); - buf[nr] = '\0'; - - return buf; -} - static inline int is_merge(void) { return !access(git_path("MERGE_HEAD"), F_OK); @@ -92,20 +73,20 @@ static int reset_index_file(const unsigned char *sha1, int reset_type, int quiet if (reset_type == KEEP) { unsigned char head_sha1[20]; if (get_sha1("HEAD", head_sha1)) - return error("You do not have a valid HEAD."); + return error(_("You do not have a valid HEAD.")); if (!fill_tree_descriptor(desc, head_sha1)) - return error("Failed to find tree of HEAD."); + return error(_("Failed to find tree of HEAD.")); nr++; opts.fn = twoway_merge; } if (!fill_tree_descriptor(desc + nr - 1, sha1)) - return error("Failed to find tree of %s.", sha1_to_hex(sha1)); + return error(_("Failed to find tree of %s."), sha1_to_hex(sha1)); if (unpack_trees(nr, desc, &opts)) return -1; if (write_cache(newfd, active_cache, active_nr) || commit_locked_index(lock)) - return error("Could not write new index file."); + return error(_("Could not write new index file.")); return 0; } @@ -115,7 +96,7 @@ static void print_new_head_line(struct commit *commit) const char *hex, *body; hex = find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV); - printf("HEAD is now at %s", hex); + printf(_("HEAD is now at %s"), hex); body = strstr(commit->buffer, "\n\n"); if (body) { const char *eol; @@ -139,10 +120,10 @@ static int update_index_refresh(int fd, struct lock_file *index_lock, int flags) } if (read_cache() < 0) - return error("Could not read index"); + return error(_("Could not read index")); result = refresh_index(&the_index, (flags), NULL, NULL, - "Unstaged changes after reset:") ? 1 : 0; + _("Unstaged changes after reset:")) ? 1 : 0; if (write_cache(fd, active_cache, active_nr) || commit_locked_index(index_lock)) return error ("Could not refresh index"); @@ -162,12 +143,12 @@ static void update_index_from_diff(struct diff_queue_struct *q, for (i = 0; i < q->nr; i++) { struct diff_filespec *one = q->queue[i]->one; - if (one->mode) { + if (one->mode && !is_null_sha1(one->sha1)) { struct cache_entry *ce; ce = make_cache_entry(one->mode, one->sha1, one->path, 0, 0); if (!ce) - die("make_cache_entry failed for path '%s'", + die(_("make_cache_entry failed for path '%s'"), one->path); add_cache_entry(ce, ADD_CACHE_OK_TO_ADD | ADD_CACHE_OK_TO_REPLACE); @@ -215,21 +196,25 @@ static int read_from_tree(const char *prefix, const char **argv, return update_index_refresh(index_fd, lock, refresh_flags); } -static void prepend_reflog_action(const char *action, char *buf, size_t size) +static void set_reflog_message(struct strbuf *sb, const char *action, + const char *rev) { - const char *sep = ": "; const char *rla = getenv("GIT_REFLOG_ACTION"); - if (!rla) - rla = sep = ""; - if (snprintf(buf, size, "%s%s%s", rla, sep, action) >= size) - warning("Reflog action message too long: %.*s...", 50, buf); + + strbuf_reset(sb); + if (rla) + strbuf_addf(sb, "%s: %s", rla, action); + else if (rev) + strbuf_addf(sb, "reset: moving to %s", rev); + else + strbuf_addf(sb, "reset: %s", action); } static void die_if_unmerged_cache(int reset_type) { if (is_merge() || read_cache() < 0 || unmerged_cache()) - die("Cannot do a %s reset in the middle of a merge.", - reset_type_names[reset_type]); + die(_("Cannot do a %s reset in the middle of a merge."), + _(reset_type_names[reset_type])); } @@ -241,9 +226,9 @@ int cmd_reset(int argc, const char **argv, const char *prefix) unsigned char sha1[20], *orig = NULL, sha1_orig[20], *old_orig = NULL, sha1_old_orig[20]; struct commit *commit; - char *reflog_action, msg[1024]; + struct strbuf msg = STRBUF_INIT; const struct option options[] = { - OPT__QUIET(&quiet), + OPT__QUIET(&quiet, "be quiet, only report errors"), OPT_SET_INT(0, "mixed", &reset_type, "reset HEAD and index", MIXED), OPT_SET_INT(0, "soft", &reset_type, "reset only HEAD", SOFT), @@ -261,8 +246,6 @@ int cmd_reset(int argc, const char **argv, const char *prefix) argc = parse_options(argc, argv, prefix, options, git_reset_usage, PARSE_OPT_KEEP_DASHDASH); - reflog_action = args_to_str(argv); - setenv("GIT_REFLOG_ACTION", reflog_action, 0); /* * Possible arguments are: @@ -300,16 +283,16 @@ int cmd_reset(int argc, const char **argv, const char *prefix) } if (get_sha1(rev, sha1)) - die("Failed to resolve '%s' as a valid ref.", rev); + die(_("Failed to resolve '%s' as a valid ref."), rev); commit = lookup_commit_reference(sha1); if (!commit) - die("Could not parse object '%s'.", rev); + die(_("Could not parse object '%s'."), rev); hashcpy(sha1, commit->object.sha1); if (patch_mode) { if (reset_type != NONE) - die("--patch is incompatible with --{hard,mixed,soft}"); + die(_("--patch is incompatible with --{hard,mixed,soft}")); return interactive_reset(rev, argv + i, prefix); } @@ -318,10 +301,10 @@ int cmd_reset(int argc, const char **argv, const char *prefix) * affecting the working tree nor HEAD. */ if (i < argc) { if (reset_type == MIXED) - warning("--mixed option is deprecated with paths."); + warning(_("--mixed with paths is deprecated; use 'git reset -- <paths>' instead.")); else if (reset_type != NONE) - die("Cannot do %s reset with paths.", - reset_type_names[reset_type]); + die(_("Cannot do %s reset with paths."), + _(reset_type_names[reset_type])); return read_from_tree(prefix, argv + i, sha1, quiet ? REFRESH_QUIET : REFRESH_IN_PORCELAIN); } @@ -332,8 +315,8 @@ int cmd_reset(int argc, const char **argv, const char *prefix) setup_work_tree(); if (reset_type == MIXED && is_bare_repository()) - die("%s reset is not allowed in a bare repository", - reset_type_names[reset_type]); + die(_("%s reset is not allowed in a bare repository"), + _(reset_type_names[reset_type])); /* Soft reset does not touch the index file nor the working tree * at all, but requires them in a good order. Other resets reset @@ -348,7 +331,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix) if (reset_type == KEEP) err = err || reset_index_file(sha1, MIXED, quiet); if (err) - die("Could not reset index file to revision '%s'.", rev); + die(_("Could not reset index file to revision '%s'."), rev); } /* Any resets update HEAD to the head being switched to, @@ -357,13 +340,13 @@ int cmd_reset(int argc, const char **argv, const char *prefix) old_orig = sha1_old_orig; if (!get_sha1("HEAD", sha1_orig)) { orig = sha1_orig; - prepend_reflog_action("updating ORIG_HEAD", msg, sizeof(msg)); - update_ref(msg, "ORIG_HEAD", orig, old_orig, 0, MSG_ON_ERR); + set_reflog_message(&msg, "updating ORIG_HEAD", NULL); + update_ref(msg.buf, "ORIG_HEAD", orig, old_orig, 0, MSG_ON_ERR); } else if (old_orig) delete_ref("ORIG_HEAD", old_orig, 0); - prepend_reflog_action("updating HEAD", msg, sizeof(msg)); - update_ref_status = update_ref(msg, "HEAD", sha1, orig, 0, MSG_ON_ERR); + set_reflog_message(&msg, "updating HEAD", rev); + update_ref_status = update_ref(msg.buf, "HEAD", sha1, orig, 0, MSG_ON_ERR); switch (reset_type) { case HARD: @@ -380,7 +363,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix) remove_branch_state(); - free(reflog_action); + strbuf_release(&msg); return update_ref_status; } diff --git a/builtin/rev-list.c b/builtin/rev-list.c index efe9360e2f..56727e8c1d 100644 --- a/builtin/rev-list.c +++ b/builtin/rev-list.c @@ -11,11 +11,15 @@ static const char rev_list_usage[] = "git rev-list [OPTION] <commit-id>... [ -- paths... ]\n" " limiting output:\n" -" --max-count=nr\n" -" --max-age=epoch\n" -" --min-age=epoch\n" +" --max-count=<n>\n" +" --max-age=<epoch>\n" +" --min-age=<epoch>\n" " --sparse\n" " --no-merges\n" +" --min-parents=<n>\n" +" --no-min-parents\n" +" --max-parents=<n>\n" +" --no-max-parents\n" " --remove-empty\n" " --all\n" " --branches\n" @@ -33,7 +37,7 @@ static const char rev_list_usage[] = " --objects | --objects-edge\n" " --unpacked\n" " --header | --pretty\n" -" --abbrev=nr | --no-abbrev\n" +" --abbrev=<n> | --no-abbrev\n" " --abbrev-commit\n" " --left-right\n" " special purpose:\n" @@ -51,7 +55,9 @@ static void show_commit(struct commit *commit, void *data) graph_show_commit(revs->graph); if (revs->count) { - if (commit->object.flags & SYMMETRIC_LEFT) + if (commit->object.flags & PATCHSAME) + revs->count_same++; + else if (commit->object.flags & SYMMETRIC_LEFT) revs->count_left++; else revs->count_right++; @@ -64,18 +70,8 @@ static void show_commit(struct commit *commit, void *data) if (info->header_prefix) fputs(info->header_prefix, stdout); - if (!revs->graph) { - if (commit->object.flags & BOUNDARY) - putchar('-'); - else if (commit->object.flags & UNINTERESTING) - putchar('^'); - else if (revs->left_right) { - if (commit->object.flags & SYMMETRIC_LEFT) - putchar('<'); - else - putchar('>'); - } - } + if (!revs->graph) + fputs(get_revision_mark(revs, commit), stdout); if (revs->abbrev_commit && revs->abbrev) fputs(find_unique_abbrev(commit->object.sha1, revs->abbrev), stdout); @@ -108,7 +104,8 @@ static void show_commit(struct commit *commit, void *data) struct pretty_print_context ctx = {0}; ctx.abbrev = revs->abbrev; ctx.date_mode = revs->date_mode; - pretty_print_commit(revs->commit_format, commit, &buf, &ctx); + ctx.fmt = revs->commit_format; + pretty_print_commit(&ctx, commit, &buf); if (revs->graph) { if (buf.len) { if (revs->commit_format != CMIT_FMT_ONELINE) @@ -147,8 +144,10 @@ static void show_commit(struct commit *commit, void *data) } } else { if (revs->commit_format != CMIT_FMT_USERFORMAT || - buf.len) - printf("%s%c", buf.buf, info->hdr_termination); + buf.len) { + fwrite(buf.buf, 1, buf.len, stdout); + putchar(info->hdr_termination); + } } strbuf_release(&buf); } else { @@ -410,8 +409,12 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) &info); if (revs.count) { - if (revs.left_right) + if (revs.left_right && revs.cherry_mark) + printf("%d\t%d\t%d\n", revs.count_left, revs.count_right, revs.count_same); + else if (revs.left_right) printf("%d\t%d\n", revs.count_left, revs.count_right); + else if (revs.cherry_mark) + printf("%d\t%d\n", revs.count_left + revs.count_right, revs.count_same); else printf("%d\n", revs.count_left + revs.count_right); } diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c index a5a1c86e92..4c19f844a9 100644 --- a/builtin/rev-parse.c +++ b/builtin/rev-parse.c @@ -44,10 +44,15 @@ static int is_rev_argument(const char *arg) "--branches=", "--branches", "--header", + "--ignore-missing", "--max-age=", "--max-count=", "--min-age=", "--no-merges", + "--min-parents=", + "--no-min-parents", + "--max-parents=", + "--no-max-parents", "--objects", "--objects-edge", "--parents", diff --git a/builtin/revert.c b/builtin/revert.c index 8b9d829a73..3117776c2c 100644 --- a/builtin/revert.c +++ b/builtin/revert.c @@ -3,7 +3,6 @@ #include "object.h" #include "commit.h" #include "tag.h" -#include "wt-status.h" #include "run-command.h" #include "exec_cmd.h" #include "utf8.h" @@ -44,7 +43,11 @@ static const char **commit_argv; static int allow_rerere_auto; static const char *me; + +/* Merge strategy. */ static const char *strategy; +static const char **xopts; +static size_t xopts_nr, xopts_alloc; #define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION" @@ -55,6 +58,17 @@ static const char * const *revert_or_cherry_pick_usage(void) return action == REVERT ? revert_usage : cherry_pick_usage; } +static int option_parse_x(const struct option *opt, + const char *arg, int unset) +{ + if (unset) + return 0; + + ALLOC_GROW(xopts, xopts_nr + 1, xopts_alloc); + xopts[xopts_nr++] = xstrdup(arg); + return 0; +} + static void parse_args(int argc, const char **argv) { const char * const * usage_str = revert_or_cherry_pick_usage(); @@ -62,11 +76,14 @@ static void parse_args(int argc, const char **argv) struct option options[] = { OPT_BOOLEAN('n', "no-commit", &no_commit, "don't automatically commit"), OPT_BOOLEAN('e', "edit", &edit, "edit the commit message"), - OPT_BOOLEAN('r', NULL, &noop, "no-op (backward compatibility)"), + { OPTION_BOOLEAN, 'r', NULL, &noop, NULL, "no-op (backward compatibility)", + PARSE_OPT_NOARG | PARSE_OPT_HIDDEN, NULL, 0 }, OPT_BOOLEAN('s', "signoff", &signoff, "add Signed-off-by:"), OPT_INTEGER('m', "mainline", &mainline, "parent number"), OPT_RERERE_AUTOUPDATE(&allow_rerere_auto), OPT_STRING(0, "strategy", &strategy, "strategy", "merge strategy"), + OPT_CALLBACK('X', "strategy-option", &xopts, "option", + "option for merge strategy", option_parse_x), OPT_END(), OPT_END(), OPT_END(), @@ -79,7 +96,7 @@ static void parse_args(int argc, const char **argv) OPT_END(), }; if (parse_options_concat(options, ARRAY_SIZE(options), cp_extra)) - die("program error"); + die(_("program error")); } commit_argc = parse_options(argc, argv, NULL, options, usage_str, @@ -102,9 +119,9 @@ struct commit_message { static int get_message(const char *raw_message, struct commit_message *out) { const char *encoding; - const char *p, *abbrev, *eol; + const char *abbrev, *subject; + int abbrev_len, subject_len; char *q; - int abbrev_len, oneline_len; if (!raw_message) return -1; @@ -125,27 +142,17 @@ static int get_message(const char *raw_message, struct commit_message *out) abbrev = find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV); abbrev_len = strlen(abbrev); - /* Find beginning and end of commit subject. */ - p = out->message; - while (*p && (*p != '\n' || p[1] != '\n')) - p++; - if (*p) { - p += 2; - for (eol = p + 1; *eol && *eol != '\n'; eol++) - ; /* do nothing */ - } else - eol = p; - oneline_len = eol - p; + subject_len = find_commit_subject(out->message, &subject); out->parent_label = xmalloc(strlen("parent of ") + abbrev_len + - strlen("... ") + oneline_len + 1); + strlen("... ") + subject_len + 1); q = out->parent_label; q = mempcpy(q, "parent of ", strlen("parent of ")); out->label = q; q = mempcpy(q, abbrev, abbrev_len); q = mempcpy(q, "... ", strlen("... ")); out->subject = q; - q = mempcpy(q, p, oneline_len); + q = mempcpy(q, subject, subject_len); *q = '\0'; return 0; } @@ -161,7 +168,7 @@ static char *get_encoding(const char *message) const char *p = message, *eol; if (!p) - die ("Could not read commit message of %s", + die (_("Could not read commit message of %s"), sha1_to_hex(commit->object.sha1)); while (*p && *p != '\n') { for (eol = p + 1; *eol && *eol != '\n'; eol++) @@ -191,77 +198,49 @@ static void add_message_to_msg(struct strbuf *msgbuf, const char *message) strbuf_addstr(msgbuf, p); } -static void set_author_ident_env(const char *message) +static void write_cherry_pick_head(void) { - const char *p = message; - if (!p) - die ("Could not read commit message of %s", - sha1_to_hex(commit->object.sha1)); - while (*p && *p != '\n') { - const char *eol; - - for (eol = p; *eol && *eol != '\n'; eol++) - ; /* do nothing */ - if (!prefixcmp(p, "author ")) { - char *line, *pend, *email, *timestamp; - - p += 7; - line = xmemdupz(p, eol - p); - email = strchr(line, '<'); - if (!email) - die ("Could not extract author email from %s", - sha1_to_hex(commit->object.sha1)); - if (email == line) - pend = line; - else - for (pend = email; pend != line + 1 && - isspace(pend[-1]); pend--); - ; /* do nothing */ - *pend = '\0'; - email++; - timestamp = strchr(email, '>'); - if (!timestamp) - die ("Could not extract author time from %s", - sha1_to_hex(commit->object.sha1)); - *timestamp = '\0'; - for (timestamp++; *timestamp && isspace(*timestamp); - timestamp++) - ; /* do nothing */ - setenv("GIT_AUTHOR_NAME", line, 1); - setenv("GIT_AUTHOR_EMAIL", email, 1); - setenv("GIT_AUTHOR_DATE", timestamp, 1); - free(line); - return; - } - p = eol; - if (*p == '\n') - p++; - } - die ("No author information found in %s", - sha1_to_hex(commit->object.sha1)); + int fd; + struct strbuf buf = STRBUF_INIT; + + strbuf_addf(&buf, "%s\n", sha1_to_hex(commit->object.sha1)); + + fd = open(git_path("CHERRY_PICK_HEAD"), O_WRONLY | O_CREAT, 0666); + if (fd < 0) + die_errno(_("Could not open '%s' for writing"), + git_path("CHERRY_PICK_HEAD")); + if (write_in_full(fd, buf.buf, buf.len) != buf.len || close(fd)) + die_errno(_("Could not write to '%s'"), git_path("CHERRY_PICK_HEAD")); + strbuf_release(&buf); } -static char *help_msg(void) +static void advise(const char *advice, ...) { - struct strbuf helpbuf = STRBUF_INIT; - char *msg = getenv("GIT_CHERRY_PICK_HELP"); + va_list params; - if (msg) - return msg; + va_start(params, advice); + vreportf("hint: ", advice, params); + va_end(params); +} - strbuf_addstr(&helpbuf, " After resolving the conflicts,\n" - "mark the corrected paths with 'git add <paths>' or 'git rm <paths>'\n" - "and commit the result"); +static void print_advice(void) +{ + char *msg = getenv("GIT_CHERRY_PICK_HELP"); - if (action == CHERRY_PICK) { - strbuf_addf(&helpbuf, " with: \n" - "\n" - " git commit -c %s\n", - sha1_to_hex(commit->object.sha1)); + if (msg) { + fprintf(stderr, "%s\n", msg); + /* + * A conflict has occured but the porcelain + * (typically rebase --interactive) wants to take care + * of the commit itself so remove CHERRY_PICK_HEAD + */ + unlink(git_path("CHERRY_PICK_HEAD")); + return; } - else - strbuf_addch(&helpbuf, '.'); - return strbuf_detach(&helpbuf, NULL); + + advise("after resolving the conflicts, mark the corrected paths"); + advise("with 'git add <paths>' or 'git rm <paths>'"); + advise("and commit the result with 'git commit'"); } static void write_message(struct strbuf *msgbuf, const char *filename) @@ -271,20 +250,15 @@ static void write_message(struct strbuf *msgbuf, const char *filename) int msg_fd = hold_lock_file_for_update(&msg_file, filename, LOCK_DIE_ON_ERROR); if (write_in_full(msg_fd, msgbuf->buf, msgbuf->len) < 0) - die_errno("Could not write to %s.", filename); + die_errno(_("Could not write to %s."), filename); strbuf_release(msgbuf); if (commit_lock_file(&msg_file) < 0) - die("Error wrapping up %s", filename); + die(_("Error wrapping up %s"), filename); } static struct tree *empty_tree(void) { - struct tree *tree = xcalloc(1, sizeof(struct tree)); - - tree->object.parsed = 1; - tree->object.type = OBJ_TREE; - pretend_sha1_file(NULL, 0, OBJ_TREE, tree->object.sha1); - return tree; + return lookup_tree((const unsigned char *)EMPTY_TREE_SHA1_BIN); } static NORETURN void die_dirty_index(const char *me) @@ -292,11 +266,19 @@ static NORETURN void die_dirty_index(const char *me) if (read_cache_unmerged()) { die_resolve_conflict(me); } else { - if (advice_commit_before_merge) - die("Your local changes would be overwritten by %s.\n" - "Please, commit your changes or stash them to proceed.", me); - else - die("Your local changes would be overwritten by %s.\n", me); + if (advice_commit_before_merge) { + if (action == REVERT) + die(_("Your local changes would be overwritten by revert.\n" + "Please, commit your changes or stash them to proceed.")); + else + die(_("Your local changes would be overwritten by cherry-pick.\n" + "Please, commit your changes or stash them to proceed.")); + } else { + if (action == REVERT) + die(_("Your local changes would be overwritten by revert.\n")); + else + die(_("Your local changes would be overwritten by cherry-pick.\n")); + } } } @@ -311,19 +293,20 @@ static int fast_forward_to(const unsigned char *to, const unsigned char *from) return write_ref_sha1(ref_lock, to, "cherry-pick"); } -static void do_recursive_merge(struct commit *base, struct commit *next, - const char *base_label, const char *next_label, - unsigned char *head, struct strbuf *msgbuf, - char *defmsg) +static int do_recursive_merge(struct commit *base, struct commit *next, + const char *base_label, const char *next_label, + unsigned char *head, struct strbuf *msgbuf) { struct merge_options o; struct tree *result, *next_tree, *base_tree, *head_tree; int clean, index_fd; + const char **xopt; static struct lock_file index_lock; index_fd = hold_locked_index(&index_lock, 1); read_cache(); + init_merge_options(&o); o.ancestor = base ? base_label : "(empty tree)"; o.branch1 = "HEAD"; @@ -333,6 +316,9 @@ static void do_recursive_merge(struct commit *base, struct commit *next, next_tree = next ? next->tree : empty_tree(); base_tree = base ? base->tree : empty_tree(); + for (xopt = xopts; xopt != xopts + xopts_nr; xopt++) + parse_merge_opt(&o, *xopt); + clean = merge_trees(&o, head_tree, next_tree, base_tree, &result); @@ -340,7 +326,8 @@ static void do_recursive_merge(struct commit *base, struct commit *next, if (active_cache_changed && (write_cache(index_fd, active_cache, active_nr) || commit_locked_index(&index_lock))) - die("%s: Unable to write new index file", me); + /* TRANSLATORS: %s will be "revert" or "cherry-pick" */ + die(_("%s: Unable to write new index file"), me); rollback_lock_file(&index_lock); if (!clean) { @@ -357,14 +344,35 @@ static void do_recursive_merge(struct commit *base, struct commit *next, i++; } } - write_message(msgbuf, defmsg); - fprintf(stderr, "Automatic %s failed.%s\n", - me, help_msg()); - rerere(allow_rerere_auto); - exit(1); } - write_message(msgbuf, defmsg); - fprintf(stderr, "Finished one %s.\n", me); + + return !clean; +} + +/* + * If we are cherry-pick, and if the merge did not result in + * hand-editing, we will hit this commit and inherit the original + * author date and name. + * If we are revert, or if our cherry-pick results in a hand merge, + * we had better say that the current user is responsible for that. + */ +static int run_git_commit(const char *defmsg) +{ + /* 6 is max possible length of our args array including NULL */ + const char *args[6]; + int i = 0; + + args[i++] = "commit"; + args[i++] = "-n"; + if (signoff) + args[i++] = "-s"; + if (!edit) { + args[i++] = "-F"; + args[i++] = defmsg; + } + args[i] = NULL; + + return run_command_v_opt(args, RUN_GIT_CMD); } static int do_pick_commit(void) @@ -375,6 +383,7 @@ static int do_pick_commit(void) struct commit_message msg = { NULL, NULL, NULL, NULL, NULL }; char *defmsg = NULL; struct strbuf msgbuf = STRBUF_INIT; + int res; if (no_commit) { /* @@ -384,18 +393,16 @@ static int do_pick_commit(void) * to work on. */ if (write_cache_as_tree(head, 0, NULL)) - die ("Your index file is unmerged."); + die (_("Your index file is unmerged.")); } else { if (get_sha1("HEAD", head)) - die ("You do not have a valid HEAD"); + die (_("You do not have a valid HEAD")); if (index_differs_from("HEAD", 0)) die_dirty_index(me); } discard_cache(); if (!commit->parents) { - if (action == REVERT) - die ("Cannot revert a root commit"); parent = NULL; } else if (commit->parents->next) { @@ -404,7 +411,7 @@ static int do_pick_commit(void) struct commit_list *p; if (!mainline) - die("Commit %s is a merge but no -m option was given.", + die(_("Commit %s is a merge but no -m option was given."), sha1_to_hex(commit->object.sha1)); for (cnt = 1, p = commit->parents; @@ -412,24 +419,26 @@ static int do_pick_commit(void) cnt++) p = p->next; if (cnt != mainline || !p) - die("Commit %s does not have parent %d", + die(_("Commit %s does not have parent %d"), sha1_to_hex(commit->object.sha1), mainline); parent = p->item; } else if (0 < mainline) - die("Mainline was specified but commit %s is not a merge.", + die(_("Mainline was specified but commit %s is not a merge."), sha1_to_hex(commit->object.sha1)); else parent = commit->parents->item; - if (allow_ff && !hashcmp(parent->object.sha1, head)) + if (allow_ff && parent && !hashcmp(parent->object.sha1, head)) return fast_forward_to(commit->object.sha1, head); if (parent && parse_commit(parent) < 0) - die("%s: cannot parse parent commit %s", + /* TRANSLATORS: The first %s will be "revert" or + "cherry-pick", the second %s a SHA1 */ + die(_("%s: cannot parse parent commit %s"), me, sha1_to_hex(parent->object.sha1)); if (get_message(commit->buffer, &msg) != 0) - die("Cannot get commit message for %s", + die(_("Cannot get commit message for %s"), sha1_to_hex(commit->object.sha1)); /* @@ -451,7 +460,7 @@ static int do_pick_commit(void) strbuf_addstr(&msgbuf, "\"\n\nThis reverts commit "); strbuf_addstr(&msgbuf, sha1_to_hex(commit->object.sha1)); - if (commit->parents->next) { + if (commit->parents && commit->parents->next) { strbuf_addstr(&msgbuf, ", reversing\nchanges made to "); strbuf_addstr(&msgbuf, sha1_to_hex(parent->object.sha1)); } @@ -461,72 +470,51 @@ static int do_pick_commit(void) base_label = msg.parent_label; next = commit; next_label = msg.label; - set_author_ident_env(msg.message); add_message_to_msg(&msgbuf, msg.message); if (no_replay) { strbuf_addstr(&msgbuf, "(cherry picked from commit "); strbuf_addstr(&msgbuf, sha1_to_hex(commit->object.sha1)); strbuf_addstr(&msgbuf, ")\n"); } + if (!no_commit) + write_cherry_pick_head(); } - if (!strategy || !strcmp(strategy, "recursive") || action == REVERT) - do_recursive_merge(base, next, base_label, next_label, - head, &msgbuf, defmsg); - else { - int res; + if (!strategy || !strcmp(strategy, "recursive") || action == REVERT) { + res = do_recursive_merge(base, next, base_label, next_label, + head, &msgbuf); + write_message(&msgbuf, defmsg); + } else { struct commit_list *common = NULL; struct commit_list *remotes = NULL; + write_message(&msgbuf, defmsg); + commit_list_insert(base, &common); commit_list_insert(next, &remotes); - res = try_merge_command(strategy, common, + res = try_merge_command(strategy, xopts_nr, xopts, common, sha1_to_hex(head), remotes); free_commit_list(common); free_commit_list(remotes); - if (res) { - fprintf(stderr, "Automatic %s with strategy %s failed.%s\n", - me, strategy, help_msg()); - rerere(allow_rerere_auto); - exit(1); - } } - free_message(&msg); - - /* - * - * If we are cherry-pick, and if the merge did not result in - * hand-editing, we will hit this commit and inherit the original - * author date and name. - * If we are revert, or if our cherry-pick results in a hand merge, - * we had better say that the current user is responsible for that. - */ - - if (!no_commit) { - /* 6 is max possible length of our args array including NULL */ - const char *args[6]; - int res; - int i = 0; - - args[i++] = "commit"; - args[i++] = "-n"; - if (signoff) - args[i++] = "-s"; - if (!edit) { - args[i++] = "-F"; - args[i++] = defmsg; - } - args[i] = NULL; - res = run_command_v_opt(args, RUN_GIT_CMD); - free(defmsg); - - return res; + if (res) { + error(action == REVERT + ? _("could not revert %s... %s") + : _("could not apply %s... %s"), + find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV), + msg.subject); + print_advice(); + rerere(allow_rerere_auto); + } else { + if (!no_commit) + res = run_git_commit(defmsg); } + free_message(&msg); free(defmsg); - return 0; + return res; } static void prepare_revs(struct rev_info *revs) @@ -543,10 +531,25 @@ static void prepare_revs(struct rev_info *revs) usage(*revert_or_cherry_pick_usage()); if (prepare_revision_walk(revs)) - die("revision walk setup failed"); + die(_("revision walk setup failed")); if (!revs->commits) - die("empty commit set passed"); + die(_("empty commit set passed")); +} + +static void read_and_refresh_cache(const char *me) +{ + static struct lock_file index_lock; + int index_fd = hold_locked_index(&index_lock, 0); + if (read_index_preload(&the_index, NULL) < 0) + die(_("git %s: failed to read the index"), me); + refresh_index(&the_index, REFRESH_QUIET|REFRESH_UNMERGED, NULL, NULL, NULL); + if (the_index.cache_changed) { + if (write_index(&the_index, index_fd) || + commit_locked_index(&index_lock)) + die(_("git %s: failed to refresh the index"), me); + } + rollback_lock_file(&index_lock); } static int revert_or_cherry_pick(int argc, const char **argv) @@ -560,17 +563,16 @@ static int revert_or_cherry_pick(int argc, const char **argv) if (allow_ff) { if (signoff) - die("cherry-pick --ff cannot be used with --signoff"); + die(_("cherry-pick --ff cannot be used with --signoff")); if (no_commit) - die("cherry-pick --ff cannot be used with --no-commit"); + die(_("cherry-pick --ff cannot be used with --no-commit")); if (no_replay) - die("cherry-pick --ff cannot be used with -x"); + die(_("cherry-pick --ff cannot be used with -x")); if (edit) - die("cherry-pick --ff cannot be used with --edit"); + die(_("cherry-pick --ff cannot be used with --edit")); } - if (read_cache() < 0) - die("git %s: failed to read the index", me); + read_and_refresh_cache(me); prepare_revs(&revs); diff --git a/builtin/rm.c b/builtin/rm.c index f3772c84de..90c8a5047c 100644 --- a/builtin/rm.c +++ b/builtin/rm.c @@ -20,15 +20,6 @@ static struct { const char **name; } list; -static void add_list(const char *name) -{ - if (list.nr >= list.alloc) { - list.alloc = alloc_nr(list.alloc); - list.name = xrealloc(list.name, list.alloc * sizeof(const char *)); - } - list.name[list.nr++] = name; -} - static int check_local_mod(unsigned char *head, int index_only) { /* @@ -115,19 +106,19 @@ static int check_local_mod(unsigned char *head, int index_only) */ if (local_changes && staged_changes) { if (!index_only || !(ce->ce_flags & CE_INTENT_TO_ADD)) - errs = error("'%s' has staged content different " + errs = error(_("'%s' has staged content different " "from both the file and the HEAD\n" - "(use -f to force removal)", name); + "(use -f to force removal)"), name); } else if (!index_only) { if (staged_changes) - errs = error("'%s' has changes staged in the index\n" + errs = error(_("'%s' has changes staged in the index\n" "(use --cached to keep the file, " - "or -f to force removal)", name); + "or -f to force removal)"), name); if (local_changes) - errs = error("'%s' has local modifications\n" + errs = error(_("'%s' has local modifications\n" "(use --cached to keep the file, " - "or -f to force removal)", name); + "or -f to force removal)"), name); } } return errs; @@ -139,10 +130,10 @@ static int show_only = 0, force = 0, index_only = 0, recursive = 0, quiet = 0; static int ignore_unmatch = 0; static struct option builtin_rm_options[] = { - OPT__DRY_RUN(&show_only), - OPT__QUIET(&quiet), + OPT__DRY_RUN(&show_only, "dry run"), + OPT__QUIET(&quiet, "do not list removed files"), OPT_BOOLEAN( 0 , "cached", &index_only, "only remove from the index"), - OPT_BOOLEAN('f', "force", &force, "override the up-to-date check"), + OPT__FORCE(&force, "override the up-to-date check"), OPT_BOOLEAN('r', NULL, &recursive, "allow recursive removal"), OPT_BOOLEAN( 0 , "ignore-unmatch", &ignore_unmatch, "exit with a zero status even if nothing matched"), @@ -168,7 +159,7 @@ int cmd_rm(int argc, const char **argv, const char *prefix) newfd = hold_locked_index(&lock_file, 1); if (read_cache() < 0) - die("index file corrupt"); + die(_("index file corrupt")); pathspec = get_pathspec(prefix, argv); refresh_index(&the_index, REFRESH_QUIET, pathspec, NULL, NULL); @@ -182,7 +173,8 @@ int cmd_rm(int argc, const char **argv, const char *prefix) struct cache_entry *ce = active_cache[i]; if (!match_pathspec(pathspec, ce->name, ce_namelen(ce), 0, seen)) continue; - add_list(ce->name); + ALLOC_GROW(list.name, list.nr + 1, list.alloc); + list.name[list.nr++] = ce->name; } if (pathspec) { @@ -191,7 +183,7 @@ int cmd_rm(int argc, const char **argv, const char *prefix) for (i = 0; (match = pathspec[i]) != NULL ; i++) { if (!seen[i]) { if (!ignore_unmatch) { - die("pathspec '%s' did not match any files", + die(_("pathspec '%s' did not match any files"), match); } } @@ -199,7 +191,7 @@ int cmd_rm(int argc, const char **argv, const char *prefix) seen_any = 1; } if (!recursive && seen[i] == MATCHED_RECURSIVELY) - die("not removing '%s' recursively without -r", + die(_("not removing '%s' recursively without -r"), *match ? match : "."); } @@ -235,7 +227,7 @@ int cmd_rm(int argc, const char **argv, const char *prefix) printf("rm '%s'\n", path); if (remove_file_from_cache(path)) - die("git rm: unable to remove %s", path); + die(_("git rm: unable to remove %s"), path); } if (show_only) @@ -265,7 +257,7 @@ int cmd_rm(int argc, const char **argv, const char *prefix) if (active_cache_changed) { if (write_cache(newfd, active_cache, active_nr) || commit_locked_index(&lock_file)) - die("Unable to write new index file"); + die(_("Unable to write new index file")); } return 0; diff --git a/builtin/send-pack.c b/builtin/send-pack.c index 481602d8ae..40a1675997 100644 --- a/builtin/send-pack.c +++ b/builtin/send-pack.c @@ -1,4 +1,4 @@ -#include "cache.h" +#include "builtin.h" #include "commit.h" #include "refs.h" #include "pkt-line.h" @@ -48,6 +48,7 @@ static int pack_objects(int fd, struct ref *refs, struct extra_have_objects *ext NULL, NULL, NULL, + NULL, }; struct child_process po; int i; @@ -59,6 +60,8 @@ static int pack_objects(int fd, struct ref *refs, struct extra_have_objects *ext argv[i++] = "--delta-base-offset"; if (args->quiet) argv[i++] = "-q"; + if (args->progress) + argv[i++] = "--progress"; memset(&po, 0, sizeof(po)); po.argv = argv; po.in = -1; @@ -101,7 +104,7 @@ static int pack_objects(int fd, struct ref *refs, struct extra_have_objects *ext } if (finish_command(&po)) - return error("pack-objects died with strange error"); + return -1; return 0; } @@ -225,8 +228,11 @@ static void print_helper_status(struct ref *ref) static int sideband_demux(int in, int out, void *data) { - int *fd = data; - int ret = recv_sideband("send-pack", fd[0], out); + int *fd = data, ret; +#ifdef NO_PTHREADS + close(fd[1]); +#endif + ret = recv_sideband("send-pack", fd[0], out); close(out); return ret; } @@ -336,6 +342,10 @@ int send_pack(struct send_pack_args *args, if (pack_objects(out, remote_refs, extra_have, args) < 0) { for (ref = remote_refs; ref; ref = ref->next) ref->status = REF_STATUS_NONE; + if (args->stateless_rpc) + close(out); + if (git_connection_is_socket(conn)) + shutdown(fd[0], SHUT_WR); if (use_sideband) finish_async(&demux); return -1; @@ -429,6 +439,10 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix) args.force_update = 1; continue; } + if (!strcmp(arg, "--quiet")) { + args.quiet = 1; + continue; + } if (!strcmp(arg, "--verbose")) { args.verbose = 1; continue; @@ -478,8 +492,13 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix) fd[0] = 0; fd[1] = 1; } else { - conn = git_connect(fd, dest, receivepack, + struct strbuf sb = STRBUF_INIT; + strbuf_addstr(&sb, receivepack); + if (args.quiet) + strbuf_addstr(&sb, " --quiet"); + conn = git_connect(fd, dest, sb.buf, args.verbose ? CONNECT_VERBOSE : 0); + strbuf_release(&sb); } memset(&extra_have, 0, sizeof(extra_have)); diff --git a/builtin/shortlog.c b/builtin/shortlog.c index 0a9681ba7e..37f3193a33 100644 --- a/builtin/shortlog.c +++ b/builtin/shortlog.c @@ -29,9 +29,6 @@ static int compare_by_number(const void *a1, const void *a2) return -1; } -const char *format_subject(struct strbuf *sb, const char *msg, - const char *line_separator); - static void insert_one_record(struct shortlog *log, const char *author, const char *oneline) @@ -141,9 +138,8 @@ void shortlog_add_commit(struct shortlog *log, struct commit *commit) const char *author = NULL, *buffer; struct strbuf buf = STRBUF_INIT; struct strbuf ufbuf = STRBUF_INIT; - struct pretty_print_context ctx = {0}; - pretty_print_commit(CMIT_FMT_RAW, commit, &buf, &ctx); + pp_commit_easy(CMIT_FMT_RAW, commit, &buf); buffer = buf.buf; while (*buffer && *buffer != '\n') { const char *eol = strchr(buffer, '\n'); @@ -158,15 +154,16 @@ void shortlog_add_commit(struct shortlog *log, struct commit *commit) buffer = eol; } if (!author) - die("Missing author: %s", + die(_("Missing author: %s"), sha1_to_hex(commit->object.sha1)); if (log->user_format) { struct pretty_print_context ctx = {0}; + ctx.fmt = CMIT_FMT_USERFORMAT; ctx.abbrev = log->abbrev; ctx.subject = ""; ctx.after_subject = ""; ctx.date_mode = DATE_NORMAL; - pretty_print_commit(CMIT_FMT_USERFORMAT, commit, &ufbuf, &ctx); + pretty_print_commit(&ctx, commit, &ufbuf); buffer = ufbuf.buf; } else if (*buffer) { buffer++; @@ -181,7 +178,7 @@ static void get_from_rev(struct rev_info *rev, struct shortlog *log) struct commit *commit; if (prepare_revision_walk(rev)) - die("revision walk setup failed"); + die(_("revision walk setup failed")); while ((commit = get_revision(rev)) != NULL) shortlog_add_commit(log, commit); } @@ -249,7 +246,7 @@ int cmd_shortlog(int argc, const char **argv, const char *prefix) { static struct shortlog log; static struct rev_info rev; - int nongit; + int nongit = !startup_info->have_repository; static const struct option options[] = { OPT_BOOLEAN('n', "numbered", &log.sort_by_number, @@ -265,12 +262,11 @@ int cmd_shortlog(int argc, const char **argv, const char *prefix) struct parse_opt_ctx_t ctx; - prefix = setup_git_directory_gently(&nongit); git_config(git_default_config, NULL); shortlog_init(&log); init_revisions(&rev, prefix); - parse_options_start(&ctx, argc, argv, prefix, PARSE_OPT_KEEP_DASHDASH | - PARSE_OPT_KEEP_ARGV0); + parse_options_start(&ctx, argc, argv, prefix, options, + PARSE_OPT_KEEP_DASHDASH | PARSE_OPT_KEEP_ARGV0); for (;;) { switch (parse_options_step(&ctx, options, shortlog_usage)) { @@ -285,7 +281,7 @@ parse_done: argc = parse_options_end(&ctx); if (setup_revisions(argc, argv, &rev, NULL) != 1) { - error("unrecognized argument: %s", argv[1]); + error(_("unrecognized argument: %s"), argv[1]); usage_with_options(shortlog_usage, options); } @@ -297,7 +293,7 @@ parse_done: add_head_to_pending(&rev); if (rev.pending.nr == 0) { if (isatty(0)) - fprintf(stderr, "(reading log message from standard input)\n"); + fprintf(stderr, _("(reading log message from standard input)\n")); read_from_stdin(&log); } else diff --git a/builtin/show-branch.c b/builtin/show-branch.c index e8719aa9e9..4b480d7c7c 100644 --- a/builtin/show-branch.c +++ b/builtin/show-branch.c @@ -6,22 +6,12 @@ #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 [-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>]", NULL }; static int showbranch_use_color = -1; -static char column_colors[][COLOR_MAXLEN] = { - GIT_COLOR_RED, - GIT_COLOR_GREEN, - GIT_COLOR_YELLOW, - GIT_COLOR_BLUE, - GIT_COLOR_MAGENTA, - GIT_COLOR_CYAN, -}; - -#define COLUMN_COLORS_MAX (ARRAY_SIZE(column_colors)) static int default_num; static int default_alloc; @@ -36,14 +26,14 @@ static const char **default_arg; static const char *get_color_code(int idx) { - if (showbranch_use_color) - return column_colors[idx]; + if (want_color(showbranch_use_color)) + return column_colors_ansi[idx % column_colors_ansi_max]; return ""; } static const char *get_color_reset_code(void) { - if (showbranch_use_color) + if (want_color(showbranch_use_color)) return GIT_COLOR_RESET; return ""; } @@ -243,7 +233,7 @@ static void join_revs(struct commit_list **list_p, if (mark_seen(p, seen_p) && !still_interesting) extra--; p->object.flags |= flags; - insert_by_date(p, list_p); + commit_list_insert_by_date(p, list_p); } } @@ -293,8 +283,7 @@ static void show_one_commit(struct commit *commit, int no_name) struct commit_name *name = commit->util; if (commit->object.parsed) { - struct pretty_print_context ctx = {0}; - pretty_print_commit(CMIT_FMT_ONELINE, commit, &pretty, &ctx); + pp_commit_easy(CMIT_FMT_ONELINE, commit, &pretty); pretty_str = pretty.buf; } if (!prefixcmp(pretty_str, "[PATCH] ")) @@ -584,7 +573,7 @@ static int git_show_branch_config(const char *var, const char *value, void *cb) } if (!strcmp(var, "color.showbranch")) { - showbranch_use_color = git_config_colorbool(var, value, -1); + showbranch_use_color = git_config_colorbool(var, value); return 0; } @@ -696,9 +685,6 @@ int cmd_show_branch(int ac, const char **av, const char *prefix) git_config(git_show_branch_config, NULL); - if (showbranch_use_color == -1) - showbranch_use_color = git_use_color_default; - /* If nothing is specified, try the default first */ if (ac == 1 && default_num) { ac = default_num; @@ -859,7 +845,7 @@ int cmd_show_branch(int ac, const char **av, const char *prefix) */ commit->object.flags |= flag; if (commit->object.flags == flag) - insert_by_date(commit, &list); + commit_list_insert_by_date(commit, &list); rev[num_rev] = commit; } for (i = 0; i < num_rev; i++) @@ -868,7 +854,7 @@ int cmd_show_branch(int ac, const char **av, const char *prefix) if (0 <= extra) join_revs(&list, &seen, num_rev, extra); - sort_by_date(&seen); + commit_list_sort_by_date(&seen); if (merge_base) return show_merge_base(seen, num_rev); @@ -892,7 +878,7 @@ int cmd_show_branch(int ac, const char **av, const char *prefix) for (j = 0; j < i; j++) putchar(' '); printf("%s%c%s [%s] ", - get_color_code(i % COLUMN_COLORS_MAX), + get_color_code(i), is_head ? '*' : '!', get_color_reset_code(), ref_name[i]); } @@ -954,7 +940,7 @@ int cmd_show_branch(int ac, const char **av, const char *prefix) else mark = '+'; printf("%s%c%s", - get_color_code(i % COLUMN_COLORS_MAX), + get_color_code(i), mark, get_color_reset_code()); } putchar(' '); diff --git a/builtin/show-ref.c b/builtin/show-ref.c index 0b2a9ad1a9..45f0340c3e 100644 --- a/builtin/show-ref.c +++ b/builtin/show-ref.c @@ -120,7 +120,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 = { NULL, 0, 0, 0 }; + static struct string_list existing_refs = STRING_LIST_INIT_NODUP; char buf[1024]; int matchlen = match ? strlen(match) : 0; @@ -193,7 +193,8 @@ static const struct option show_ref_options[] = { "only show SHA1 hash using <n> digits", PARSE_OPT_OPTARG, &hash_callback }, OPT__ABBREV(&abbrev), - OPT__QUIET(&quiet), + OPT__QUIET(&quiet, + "do not print results to stdout (useful with --verify)"), { OPTION_CALLBACK, 0, "exclude-existing", &exclude_existing_arg, "pattern", "show refs from stdin that aren't in local repository", PARSE_OPT_OPTARG | PARSE_OPT_NONEG, exclude_existing_callback }, diff --git a/builtin/symbolic-ref.c b/builtin/symbolic-ref.c index ca855a5eb2..dea849c3c5 100644 --- a/builtin/symbolic-ref.c +++ b/builtin/symbolic-ref.c @@ -30,7 +30,8 @@ int cmd_symbolic_ref(int argc, const char **argv, const char *prefix) int quiet = 0; const char *msg = NULL; struct option options[] = { - OPT__QUIET(&quiet), + OPT__QUIET(&quiet, + "suppress error message for non-symbolic (detached) refs"), OPT_STRING('m', NULL, &msg, "reason", "reason of the update"), OPT_END(), }; diff --git a/builtin/tag.c b/builtin/tag.c index d311491e49..667515e527 100644 --- a/builtin/tag.c +++ b/builtin/tag.c @@ -12,11 +12,13 @@ #include "tag.h" #include "run-command.h" #include "parse-options.h" +#include "diff.h" +#include "revision.h" static const char * const git_tag_usage[] = { "git tag [-a|-s|-u <key-id>] [-f] [-m <msg>|-F <file>] <tagname> [<head>]", "git tag -d <tagname>...", - "git tag -l [-n[<num>]] [<pattern>]", + "git tag -l [-n[<num>]] [<pattern>...]", "git tag -v <tagname>...", NULL }; @@ -24,19 +26,70 @@ static const char * const git_tag_usage[] = { static char signingkey[1000]; struct tag_filter { - const char *pattern; + const char **patterns; int lines; struct commit_list *with_commit; }; -#define PGP_SIGNATURE "-----BEGIN PGP SIGNATURE-----" +static int match_pattern(const char **patterns, const char *ref) +{ + /* no pattern means match everything */ + if (!*patterns) + return 1; + for (; *patterns; patterns++) + if (!fnmatch(*patterns, ref, 0)) + return 1; + return 0; +} + +static int in_commit_list(const struct commit_list *want, struct commit *c) +{ + for (; want; want = want->next) + if (!hashcmp(want->item->object.sha1, c->object.sha1)) + return 1; + return 0; +} + +static int contains_recurse(struct commit *candidate, + const struct commit_list *want) +{ + struct commit_list *p; + + /* was it previously marked as containing a want commit? */ + if (candidate->object.flags & TMP_MARK) + return 1; + /* or marked as not possibly containing a want commit? */ + if (candidate->object.flags & UNINTERESTING) + return 0; + /* or are we it? */ + if (in_commit_list(want, candidate)) + return 1; + + if (parse_commit(candidate) < 0) + return 0; + + /* Otherwise recurse and mark ourselves for future traversals. */ + for (p = candidate->parents; p; p = p->next) { + if (contains_recurse(p->item, want)) { + candidate->object.flags |= TMP_MARK; + return 1; + } + } + candidate->object.flags |= UNINTERESTING; + return 0; +} + +static int contains(struct commit *candidate, const struct commit_list *want) +{ + return contains_recurse(candidate, want); +} static int show_reference(const char *refname, const unsigned char *sha1, int flag, void *cb_data) { struct tag_filter *filter = cb_data; - if (!fnmatch(filter->pattern, refname, 0)) { + if (match_pattern(filter->patterns, refname)) { int i; unsigned long size; enum object_type type; @@ -49,7 +102,7 @@ static int show_reference(const char *refname, const unsigned char *sha1, commit = lookup_commit_reference_gently(sha1, 1); if (!commit) return 0; - if (!is_descendant_of(commit, filter->with_commit)) + if (!contains(commit, filter->with_commit)) return 0; } @@ -70,9 +123,9 @@ static int show_reference(const char *refname, const unsigned char *sha1, return 0; } /* only take up to "lines" lines, and strip the signature */ + size = parse_signature(buf, size); for (i = 0, sp += 2; - i < filter->lines && sp < buf + size && - prefixcmp(sp, PGP_SIGNATURE "\n"); + i < filter->lines && sp < buf + size; i++) { if (i) printf("\n "); @@ -90,15 +143,12 @@ static int show_reference(const char *refname, const unsigned char *sha1, return 0; } -static int list_tags(const char *pattern, int lines, +static int list_tags(const char **patterns, int lines, struct commit_list *with_commit) { struct tag_filter filter; - if (pattern == NULL) - pattern = "*"; - - filter.pattern = pattern; + filter.patterns = patterns; filter.lines = lines; filter.with_commit = with_commit; @@ -120,12 +170,12 @@ static int for_each_tag_name(const char **argv, each_tag_name_fn fn) for (p = argv; *p; p++) { if (snprintf(ref, sizeof(ref), "refs/tags/%s", *p) >= sizeof(ref)) { - error("tag name too long: %.*s...", 50, *p); + error(_("tag name too long: %.*s..."), 50, *p); had_error = 1; continue; } if (!resolve_ref(ref, sha1, 1, NULL)) { - error("tag '%s' not found.", *p); + error(_("tag '%s' not found."), *p); had_error = 1; continue; } @@ -140,7 +190,7 @@ static int delete_tag(const char *name, const char *ref, { if (delete_ref(ref, sha1, 0)) return 1; - printf("Deleted tag '%s' (was %s)\n", name, find_unique_abbrev(sha1, DEFAULT_ABBREV)); + printf(_("Deleted tag '%s' (was %s)\n"), name, find_unique_abbrev(sha1, DEFAULT_ABBREV)); return 0; } @@ -152,7 +202,7 @@ static int verify_tag(const char *name, const char *ref, argv_verify_tag[2] = sha1_to_hex(sha1); if (run_command_v_opt(argv_verify_tag, RUN_GIT_CMD)) - return error("could not verify the tag '%s'", name); + return error(_("could not verify the tag '%s'"), name); return 0; } @@ -167,7 +217,7 @@ static int do_sign(struct strbuf *buffer) if (!*signingkey) { if (strlcpy(signingkey, git_committer_info(IDENT_ERROR_ON_NO_NAME), sizeof(signingkey)) > sizeof(signingkey) - 1) - return error("committer info too long."); + return error(_("committer info too long.")); bracket = strchr(signingkey, '>'); if (bracket) bracket[1] = '\0'; @@ -187,20 +237,20 @@ static int do_sign(struct strbuf *buffer) args[3] = NULL; if (start_command(&gpg)) - return error("could not run gpg."); + return error(_("could not run gpg.")); if (write_in_full(gpg.in, buffer->buf, buffer->len) != buffer->len) { close(gpg.in); close(gpg.out); finish_command(&gpg); - return error("gpg did not accept the tag data"); + return error(_("gpg did not accept the tag data")); } close(gpg.in); len = strbuf_read(buffer, gpg.out, 1024); close(gpg.out); if (finish_command(&gpg) || !len || len < 0) - return error("gpg failed to sign the tag"); + return error(_("gpg failed to sign the tag")); /* Strip CR from the line endings, in case we are on Windows. */ for (i = j = 0; i < buffer->len; i++) @@ -215,15 +265,15 @@ static int do_sign(struct strbuf *buffer) } static const char tag_template[] = - "\n" + N_("\n" "#\n" "# Write a tag message\n" - "#\n"; + "#\n"); static void set_signingkey(const char *value) { if (strlcpy(signingkey, value, sizeof(signingkey)) >= sizeof(signingkey)) - die("signing key value too long (%.10s...)", value); + die(_("signing key value too long (%.10s...)"), value); } static int git_tag_config(const char *var, const char *value, void *cb) @@ -242,8 +292,7 @@ static void write_tag_body(int fd, const unsigned char *sha1) { unsigned long size; enum object_type type; - char *buf, *sp, *eob; - size_t len; + char *buf, *sp; buf = read_sha1_file(sha1, &type, &size); if (!buf) @@ -256,12 +305,7 @@ static void write_tag_body(int fd, const unsigned char *sha1) return; } sp += 2; /* skip the 2 LFs */ - eob = strstr(sp, "\n" PGP_SIGNATURE "\n"); - if (eob) - len = eob - sp; - else - len = buf + size - sp; - write_or_die(fd, sp, len); + write_or_die(fd, sp, parse_signature(sp, buf + size - sp)); free(buf); } @@ -269,9 +313,9 @@ static void write_tag_body(int fd, const unsigned char *sha1) static int build_tag_object(struct strbuf *buf, int sign, unsigned char *result) { if (sign && do_sign(buf) < 0) - return error("unable to sign the tag"); + return error(_("unable to sign the tag")); if (write_sha1_file(buf->buf, buf->len, tag_type, result) < 0) - return error("unable to write tag file"); + return error(_("unable to write tag file")); return 0; } @@ -286,7 +330,7 @@ static void create_tag(const unsigned char *object, const char *tag, type = sha1_object_info(object, NULL); if (type <= OBJ_NONE) - die("bad object type."); + die(_("bad object type.")); header_len = snprintf(header_buf, sizeof(header_buf), "object %s\n" @@ -299,7 +343,7 @@ static void create_tag(const unsigned char *object, const char *tag, git_committer_info(IDENT_ERROR_ON_NO_NAME)); if (header_len > sizeof(header_buf) - 1) - die("tag header too big."); + die(_("tag header too big.")); if (!message) { int fd; @@ -308,17 +352,17 @@ static void create_tag(const unsigned char *object, const char *tag, path = git_pathdup("TAG_EDITMSG"); fd = open(path, O_CREAT | O_TRUNC | O_WRONLY, 0600); if (fd < 0) - die_errno("could not create file '%s'", path); + die_errno(_("could not create file '%s'"), path); if (!is_null_sha1(prev)) write_tag_body(fd, prev); else - write_or_die(fd, tag_template, strlen(tag_template)); + write_or_die(fd, _(tag_template), strlen(_(tag_template))); close(fd); if (launch_editor(path, buf, NULL)) { fprintf(stderr, - "Please supply the message using either -m or -F option.\n"); + _("Please supply the message using either -m or -F option.\n")); exit(1); } } @@ -326,13 +370,13 @@ static void create_tag(const unsigned char *object, const char *tag, stripspace(buf, 1); if (!message && !buf->len) - die("no tag message?"); + die(_("no tag message?")); strbuf_insert(buf, 0, header_buf, header_len); if (build_tag_object(buf, sign, result) < 0) { if (path) - fprintf(stderr, "The tag message has been left in %s\n", + fprintf(stderr, _("The tag message has been left in %s\n"), path); exit(128); } @@ -360,11 +404,22 @@ static int parse_msg_arg(const struct option *opt, const char *arg, int unset) return 0; } +static int strbuf_check_tag_ref(struct strbuf *sb, const char *name) +{ + if (name[0] == '-') + return CHECK_REF_FORMAT_ERROR; + + strbuf_reset(sb); + strbuf_addf(sb, "refs/tags/%s", name); + + return check_ref_format(sb->buf); +} + int cmd_tag(int argc, const char **argv, const char *prefix) { struct strbuf buf = STRBUF_INIT; + struct strbuf ref = STRBUF_INIT; unsigned char object[20], prev[20]; - char ref[PATH_MAX]; const char *object_ref, *tag; struct ref_lock *lock; @@ -384,13 +439,13 @@ int cmd_tag(int argc, const char **argv, const char *prefix) OPT_GROUP("Tag creation options"), OPT_BOOLEAN('a', NULL, &annotate, "annotated tag, needs a message"), - OPT_CALLBACK('m', NULL, &msg, "msg", - "message for the tag", parse_msg_arg), - OPT_FILENAME('F', NULL, &msgfile, "message in a file"), + OPT_CALLBACK('m', NULL, &msg, "message", + "tag message", parse_msg_arg), + OPT_FILENAME('F', NULL, &msgfile, "read message from file"), OPT_BOOLEAN('s', NULL, &sign, "annotated and GPG-signed tag"), OPT_STRING('u', NULL, &keyid, "key-id", "use another key to sign the tag"), - OPT_BOOLEAN('f', "force", &force, "replace the tag if exists"), + OPT__FORCE(&force, "replace the tag if exists"), OPT_GROUP("Tag listing options"), { @@ -422,12 +477,12 @@ int cmd_tag(int argc, const char **argv, const char *prefix) if (list + delete + verify > 1) usage_with_options(git_tag_usage, options); if (list) - return list_tags(argv[0], lines == -1 ? 0 : lines, + return list_tags(argv, lines == -1 ? 0 : lines, with_commit); if (lines != -1) - die("-n option is only allowed with -l."); + die(_("-n option is only allowed with -l.")); if (with_commit) - die("--contains option is only allowed with -l."); + die(_("--contains option is only allowed with -l.")); if (delete) return for_each_tag_name(argv, delete_tag); if (verify) @@ -435,17 +490,17 @@ int cmd_tag(int argc, const char **argv, const char *prefix) if (msg.given || msgfile) { if (msg.given && msgfile) - die("only one -F or -m option is allowed."); + die(_("only one -F or -m option is allowed.")); annotate = 1; if (msg.given) strbuf_addbuf(&buf, &(msg.buf)); else { if (!strcmp(msgfile, "-")) { if (strbuf_read(&buf, 0, 1024) < 0) - die_errno("cannot read '%s'", msgfile); + die_errno(_("cannot read '%s'"), msgfile); } else { if (strbuf_read_file(&buf, msgfile, 1024) < 0) - die_errno("could not open or read '%s'", + die_errno(_("could not open or read '%s'"), msgfile); } } @@ -455,33 +510,32 @@ int cmd_tag(int argc, const char **argv, const char *prefix) object_ref = argc == 2 ? argv[1] : "HEAD"; if (argc > 2) - die("too many params"); + die(_("too many params")); if (get_sha1(object_ref, object)) - die("Failed to resolve '%s' as a valid ref.", object_ref); + die(_("Failed to resolve '%s' as a valid ref."), object_ref); - if (snprintf(ref, sizeof(ref), "refs/tags/%s", tag) > sizeof(ref) - 1) - die("tag name too long: %.*s...", 50, tag); - if (check_ref_format(ref)) - die("'%s' is not a valid tag name.", tag); + if (strbuf_check_tag_ref(&ref, tag)) + die(_("'%s' is not a valid tag name."), tag); - if (!resolve_ref(ref, prev, 1, NULL)) + if (!resolve_ref(ref.buf, prev, 1, NULL)) hashclr(prev); else if (!force) - die("tag '%s' already exists", tag); + die(_("tag '%s' already exists"), tag); if (annotate) create_tag(object, tag, &buf, msg.given || msgfile, sign, prev, object); - lock = lock_any_ref_for_update(ref, prev, 0); + lock = lock_any_ref_for_update(ref.buf, prev, 0); if (!lock) - die("%s: cannot lock the ref", ref); + die(_("%s: cannot lock the ref"), ref.buf); if (write_ref_sha1(lock, object, NULL) < 0) - die("%s: cannot update the ref", ref); + die(_("%s: cannot update the ref"), ref.buf); if (force && hashcmp(prev, object)) - printf("Updated tag '%s' (was %s)\n", tag, find_unique_abbrev(prev, DEFAULT_ABBREV)); + printf(_("Updated tag '%s' (was %s)\n"), tag, find_unique_abbrev(prev, DEFAULT_ABBREV)); strbuf_release(&buf); + strbuf_release(&ref); return 0; } diff --git a/builtin/unpack-file.c b/builtin/unpack-file.c index 608590ada8..19200291a2 100644 --- a/builtin/unpack-file.c +++ b/builtin/unpack-file.c @@ -1,6 +1,4 @@ -#include "cache.h" -#include "blob.h" -#include "exec_cmd.h" +#include "builtin.h" static char *create_temp_file(unsigned char *sha1) { diff --git a/builtin/unpack-objects.c b/builtin/unpack-objects.c index 685566e0b5..14e04e6795 100644 --- a/builtin/unpack-objects.c +++ b/builtin/unpack-objects.c @@ -83,14 +83,14 @@ static void use(int bytes) offset += bytes; /* make sure off_t is sufficiently large not to wrap */ - if (consumed_bytes > consumed_bytes + bytes) + if (signed_add_overflows(consumed_bytes, bytes)) die("pack too large for current definition of off_t"); consumed_bytes += bytes; } static void *get_data(unsigned long size) { - z_stream stream; + git_zstream stream; void *buf = xmalloc(size); memset(&stream, 0, sizeof(stream)); diff --git a/builtin/update-index.c b/builtin/update-index.c index 3ab214d24e..a6a23fa1f3 100644 --- a/builtin/update-index.c +++ b/builtin/update-index.c @@ -10,6 +10,7 @@ #include "builtin.h" #include "refs.h" #include "resolve-undo.h" +#include "parse-options.h" /* * Default to not allowing changes to the list of files. The @@ -98,8 +99,11 @@ static int add_one_path(struct cache_entry *old, const char *path, int len, stru fill_stat_cache_info(ce, st); ce->ce_mode = ce_mode_from_stat(old, st->st_mode); - if (index_path(ce->sha1, path, st, !info_only)) + if (index_path(ce->sha1, path, st, + info_only ? 0 : HASH_WRITE_OBJECT)) { + free(ce); return -1; + } option = allow_add ? ADD_CACHE_OK_TO_ADD : 0; option |= allow_replace ? ADD_CACHE_OK_TO_REPLACE : 0; if (add_cache_entry(ce, option)) @@ -397,8 +401,10 @@ static void read_index_info(int line_termination) strbuf_release(&uq); } -static const char update_index_usage[] = -"git update-index [-q] [--add] [--replace] [--remove] [--unmerged] [--refresh] [--really-refresh] [--cacheinfo] [--chmod=(+|-)x] [--assume-unchanged] [--skip-worktree|--no-skip-worktree] [--info-only] [--force-remove] [--stdin] [--index-info] [--unresolve] [--again | -g] [--ignore-missing] [-z] [--verbose] [--] <file>..."; +static const char * const update_index_usage[] = { + "git update-index [options] [--] [<file>...]", + NULL +}; static unsigned char head_sha1[20]; static unsigned char merge_head_sha1[20]; @@ -543,7 +549,10 @@ static int do_reupdate(int ac, const char **av, */ int pos; int has_head = 1; - const char **pathspec = get_pathspec(prefix, av + 1); + const char **paths = get_pathspec(prefix, av + 1); + struct pathspec pathspec; + + init_pathspec(&pathspec, paths); if (read_ref("HEAD", head_sha1)) /* If there is no HEAD, that means it is an initial @@ -556,7 +565,7 @@ static int do_reupdate(int ac, const char **av, struct cache_entry *old = NULL; int save_nr; - if (ce_stage(ce) || !ce_path_match(ce, pathspec)) + if (ce_stage(ce) || !ce_path_match(ce, &pathspec)) continue; if (has_head) old = read_one_ent(NULL, head_sha1, @@ -575,19 +584,218 @@ static int do_reupdate(int ac, const char **av, if (save_nr != active_nr) goto redo; } + free_pathspec(&pathspec); + return 0; +} + +struct refresh_params { + unsigned int flags; + int *has_errors; +}; + +static int refresh(struct refresh_params *o, unsigned int flag) +{ + setup_work_tree(); + *o->has_errors |= refresh_cache(o->flags | flag); + return 0; +} + +static int refresh_callback(const struct option *opt, + const char *arg, int unset) +{ + return refresh(opt->value, 0); +} + +static int really_refresh_callback(const struct option *opt, + const char *arg, int unset) +{ + return refresh(opt->value, REFRESH_REALLY); +} + +static int chmod_callback(const struct option *opt, + const char *arg, int unset) +{ + char *flip = opt->value; + if ((arg[0] != '-' && arg[0] != '+') || arg[1] != 'x' || arg[2]) + return error("option 'chmod' expects \"+x\" or \"-x\""); + *flip = arg[0]; + return 0; +} + +static int resolve_undo_clear_callback(const struct option *opt, + const char *arg, int unset) +{ + resolve_undo_clear(); + return 0; +} + +static int cacheinfo_callback(struct parse_opt_ctx_t *ctx, + const struct option *opt, int unset) +{ + unsigned char sha1[20]; + unsigned int mode; + + if (ctx->argc <= 3) + return error("option 'cacheinfo' expects three arguments"); + if (strtoul_ui(*++ctx->argv, 8, &mode) || + get_sha1_hex(*++ctx->argv, sha1) || + add_cacheinfo(mode, sha1, *++ctx->argv, 0)) + die("git update-index: --cacheinfo cannot add %s", *ctx->argv); + ctx->argc -= 3; + return 0; +} + +static int stdin_cacheinfo_callback(struct parse_opt_ctx_t *ctx, + const struct option *opt, int unset) +{ + int *line_termination = opt->value; + + if (ctx->argc != 1) + return error("option '%s' must be the last argument", opt->long_name); + allow_add = allow_replace = allow_remove = 1; + read_index_info(*line_termination); + return 0; +} + +static int stdin_callback(struct parse_opt_ctx_t *ctx, + const struct option *opt, int unset) +{ + int *read_from_stdin = opt->value; + + if (ctx->argc != 1) + return error("option '%s' must be the last argument", opt->long_name); + *read_from_stdin = 1; + return 0; +} + +static int unresolve_callback(struct parse_opt_ctx_t *ctx, + const struct option *opt, int flags) +{ + int *has_errors = opt->value; + const char *prefix = startup_info->prefix; + + /* consume remaining arguments. */ + *has_errors = do_unresolve(ctx->argc, ctx->argv, + prefix, prefix ? strlen(prefix) : 0); + if (*has_errors) + active_cache_changed = 0; + + ctx->argv += ctx->argc - 1; + ctx->argc = 1; + return 0; +} + +static int reupdate_callback(struct parse_opt_ctx_t *ctx, + const struct option *opt, int flags) +{ + int *has_errors = opt->value; + const char *prefix = startup_info->prefix; + + /* consume remaining arguments. */ + setup_work_tree(); + *has_errors = do_reupdate(ctx->argc, ctx->argv, + prefix, prefix ? strlen(prefix) : 0); + if (*has_errors) + active_cache_changed = 0; + + ctx->argv += ctx->argc - 1; + ctx->argc = 1; return 0; } int cmd_update_index(int argc, const char **argv, const char *prefix) { - int i, newfd, entries, has_errors = 0, line_termination = '\n'; - int allow_options = 1; + int newfd, entries, has_errors = 0, line_termination = '\n'; int read_from_stdin = 0; int prefix_length = prefix ? strlen(prefix) : 0; char set_executable_bit = 0; - unsigned int refresh_flags = 0; + struct refresh_params refresh_args = {0, &has_errors}; int lock_error = 0; struct lock_file *lock_file; + struct parse_opt_ctx_t ctx; + int parseopt_state = PARSE_OPT_UNKNOWN; + struct option options[] = { + OPT_BIT('q', NULL, &refresh_args.flags, + "continue refresh even when index needs update", + REFRESH_QUIET), + OPT_BIT(0, "ignore-submodules", &refresh_args.flags, + "refresh: ignore submodules", + REFRESH_IGNORE_SUBMODULES), + OPT_SET_INT(0, "add", &allow_add, + "do not ignore new files", 1), + OPT_SET_INT(0, "replace", &allow_replace, + "let files replace directories and vice-versa", 1), + OPT_SET_INT(0, "remove", &allow_remove, + "notice files missing from worktree", 1), + OPT_BIT(0, "unmerged", &refresh_args.flags, + "refresh even if index contains unmerged entries", + REFRESH_UNMERGED), + {OPTION_CALLBACK, 0, "refresh", &refresh_args, NULL, + "refresh stat information", + PARSE_OPT_NOARG | PARSE_OPT_NONEG, + refresh_callback}, + {OPTION_CALLBACK, 0, "really-refresh", &refresh_args, NULL, + "like --refresh, but ignore assume-unchanged setting", + PARSE_OPT_NOARG | PARSE_OPT_NONEG, + really_refresh_callback}, + {OPTION_LOWLEVEL_CALLBACK, 0, "cacheinfo", NULL, + "<mode> <object> <path>", + "add the specified entry to the index", + PARSE_OPT_NOARG | /* disallow --cacheinfo=<mode> form */ + PARSE_OPT_NONEG | PARSE_OPT_LITERAL_ARGHELP, + (parse_opt_cb *) cacheinfo_callback}, + {OPTION_CALLBACK, 0, "chmod", &set_executable_bit, "(+/-)x", + "override the executable bit of the listed files", + PARSE_OPT_NONEG | PARSE_OPT_LITERAL_ARGHELP, + chmod_callback}, + {OPTION_SET_INT, 0, "assume-unchanged", &mark_valid_only, NULL, + "mark files as \"not changing\"", + PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, MARK_FLAG}, + {OPTION_SET_INT, 0, "no-assume-unchanged", &mark_valid_only, NULL, + "clear assumed-unchanged bit", + PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, UNMARK_FLAG}, + {OPTION_SET_INT, 0, "skip-worktree", &mark_skip_worktree_only, NULL, + "mark files as \"index-only\"", + PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, MARK_FLAG}, + {OPTION_SET_INT, 0, "no-skip-worktree", &mark_skip_worktree_only, NULL, + "clear skip-worktree bit", + PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, UNMARK_FLAG}, + OPT_SET_INT(0, "info-only", &info_only, + "add to index only; do not add content to object database", 1), + OPT_SET_INT(0, "force-remove", &force_remove, + "remove named paths even if present in worktree", 1), + OPT_SET_INT('z', NULL, &line_termination, + "with --stdin: input lines are terminated by null bytes", '\0'), + {OPTION_LOWLEVEL_CALLBACK, 0, "stdin", &read_from_stdin, NULL, + "read list of paths to be updated from standard input", + PARSE_OPT_NONEG | PARSE_OPT_NOARG, + (parse_opt_cb *) stdin_callback}, + {OPTION_LOWLEVEL_CALLBACK, 0, "index-info", &line_termination, NULL, + "add entries from standard input to the index", + PARSE_OPT_NONEG | PARSE_OPT_NOARG, + (parse_opt_cb *) stdin_cacheinfo_callback}, + {OPTION_LOWLEVEL_CALLBACK, 0, "unresolve", &has_errors, NULL, + "repopulate stages #2 and #3 for the listed paths", + PARSE_OPT_NONEG | PARSE_OPT_NOARG, + (parse_opt_cb *) unresolve_callback}, + {OPTION_LOWLEVEL_CALLBACK, 'g', "again", &has_errors, NULL, + "only update entries that differ from HEAD", + PARSE_OPT_NONEG | PARSE_OPT_NOARG, + (parse_opt_cb *) reupdate_callback}, + OPT_BIT(0, "ignore-missing", &refresh_args.flags, + "ignore files missing from worktree", + REFRESH_IGNORE_MISSING), + OPT_SET_INT(0, "verbose", &verbose, + "report actions to standard output", 1), + {OPTION_CALLBACK, 0, "clear-resolve-undo", NULL, NULL, + "(for porcelains) forget saved unresolved conflicts", + PARSE_OPT_NOARG | PARSE_OPT_NONEG, + resolve_undo_clear_callback}, + OPT_END() + }; + + if (argc == 2 && !strcmp(argv[1], "-h")) + usage(update_index_usage[0]); git_config(git_default_config, NULL); @@ -602,151 +810,48 @@ int cmd_update_index(int argc, const char **argv, const char *prefix) if (entries < 0) die("cache corrupted"); - for (i = 1 ; i < argc; i++) { - const char *path = argv[i]; - const char *p; + /* + * Custom copy of parse_options() because we want to handle + * filename arguments as they come. + */ + parse_options_start(&ctx, argc, argv, prefix, + options, PARSE_OPT_STOP_AT_NON_OPTION); + while (ctx.argc) { + if (parseopt_state != PARSE_OPT_DONE) + parseopt_state = parse_options_step(&ctx, options, + update_index_usage); + if (!ctx.argc) + break; + switch (parseopt_state) { + case PARSE_OPT_HELP: + exit(129); + case PARSE_OPT_NON_OPTION: + case PARSE_OPT_DONE: + { + const char *path = ctx.argv[0]; + const char *p; - if (allow_options && *path == '-') { - if (!strcmp(path, "--")) { - allow_options = 0; - continue; - } - if (!strcmp(path, "-q")) { - refresh_flags |= REFRESH_QUIET; - continue; - } - if (!strcmp(path, "--ignore-submodules")) { - refresh_flags |= REFRESH_IGNORE_SUBMODULES; - continue; - } - if (!strcmp(path, "--add")) { - allow_add = 1; - continue; - } - if (!strcmp(path, "--replace")) { - allow_replace = 1; - continue; - } - if (!strcmp(path, "--remove")) { - allow_remove = 1; - continue; - } - if (!strcmp(path, "--unmerged")) { - refresh_flags |= REFRESH_UNMERGED; - continue; - } - if (!strcmp(path, "--refresh")) { - setup_work_tree(); - has_errors |= refresh_cache(refresh_flags); - continue; - } - if (!strcmp(path, "--really-refresh")) { - setup_work_tree(); - has_errors |= refresh_cache(REFRESH_REALLY | refresh_flags); - continue; - } - if (!strcmp(path, "--cacheinfo")) { - unsigned char sha1[20]; - unsigned int mode; - - if (i+3 >= argc) - die("git update-index: --cacheinfo <mode> <sha1> <path>"); - - if (strtoul_ui(argv[i+1], 8, &mode) || - get_sha1_hex(argv[i+2], sha1) || - add_cacheinfo(mode, sha1, argv[i+3], 0)) - die("git update-index: --cacheinfo" - " cannot add %s", argv[i+3]); - i += 3; - continue; - } - if (!strcmp(path, "--chmod=-x") || - !strcmp(path, "--chmod=+x")) { - if (argc <= i+1) - die("git update-index: %s <path>", path); - set_executable_bit = path[8]; - continue; - } - if (!strcmp(path, "--assume-unchanged")) { - mark_valid_only = MARK_FLAG; - continue; - } - if (!strcmp(path, "--no-assume-unchanged")) { - mark_valid_only = UNMARK_FLAG; - continue; - } - if (!strcmp(path, "--no-skip-worktree")) { - mark_skip_worktree_only = UNMARK_FLAG; - continue; - } - if (!strcmp(path, "--skip-worktree")) { - mark_skip_worktree_only = MARK_FLAG; - continue; - } - if (!strcmp(path, "--info-only")) { - info_only = 1; - continue; - } - if (!strcmp(path, "--force-remove")) { - force_remove = 1; - continue; - } - if (!strcmp(path, "-z")) { - line_termination = 0; - continue; - } - if (!strcmp(path, "--stdin")) { - if (i != argc - 1) - die("--stdin must be at the end"); - read_from_stdin = 1; - break; - } - if (!strcmp(path, "--index-info")) { - if (i != argc - 1) - die("--index-info must be at the end"); - allow_add = allow_replace = allow_remove = 1; - read_index_info(line_termination); - break; - } - if (!strcmp(path, "--unresolve")) { - has_errors = do_unresolve(argc - i, argv + i, - prefix, prefix_length); - if (has_errors) - active_cache_changed = 0; - goto finish; - } - if (!strcmp(path, "--again") || !strcmp(path, "-g")) { - setup_work_tree(); - has_errors = do_reupdate(argc - i, argv + i, - prefix, prefix_length); - if (has_errors) - active_cache_changed = 0; - goto finish; - } - if (!strcmp(path, "--ignore-missing")) { - refresh_flags |= REFRESH_IGNORE_MISSING; - continue; - } - if (!strcmp(path, "--verbose")) { - verbose = 1; - continue; - } - if (!strcmp(path, "--clear-resolve-undo")) { - resolve_undo_clear(); - continue; - } - if (!strcmp(path, "-h") || !strcmp(path, "--help")) - usage(update_index_usage); - die("unknown option %s", path); + setup_work_tree(); + p = prefix_path(prefix, prefix_length, path); + update_one(p, NULL, 0); + if (set_executable_bit) + chmod_path(set_executable_bit, p); + if (p < path || p > path + strlen(path)) + free((char *)p); + ctx.argc--; + ctx.argv++; + break; + } + case PARSE_OPT_UNKNOWN: + if (ctx.argv[0][1] == '-') + error("unknown option '%s'", ctx.argv[0] + 2); + else + error("unknown switch '%c'", *ctx.opt); + usage_with_options(update_index_usage, options); } - setup_work_tree(); - p = prefix_path(prefix, prefix_length, path); - update_one(p, NULL, 0); - if (set_executable_bit) - chmod_path(set_executable_bit, p); - if (p < path || p > path + strlen(path)) - free((char *)p); } + argc = parse_options_end(&ctx); + if (read_from_stdin) { struct strbuf buf = STRBUF_INIT, nbuf = STRBUF_INIT; @@ -770,10 +875,9 @@ int cmd_update_index(int argc, const char **argv, const char *prefix) strbuf_release(&buf); } - finish: if (active_cache_changed) { if (newfd < 0) { - if (refresh_flags & REFRESH_QUIET) + if (refresh_args.flags & REFRESH_QUIET) exit(128); unable_to_lock_index_die(get_index_file(), lock_error); } diff --git a/builtin/update-ref.c b/builtin/update-ref.c index 76ba1d5881..835c62ab15 100644 --- a/builtin/update-ref.c +++ b/builtin/update-ref.c @@ -11,7 +11,7 @@ static const char * const git_update_ref_usage[] = { int cmd_update_ref(int argc, const char **argv, const char *prefix) { - const char *refname, *oldval, *msg=NULL; + const char *refname, *oldval, *msg = NULL; unsigned char sha1[20], oldsha1[20]; int delete = 0, no_deref = 0, flags = 0; struct option options[] = { diff --git a/builtin/update-server-info.c b/builtin/update-server-info.c index 2b3fddcc69..b90dce6358 100644 --- a/builtin/update-server-info.c +++ b/builtin/update-server-info.c @@ -11,8 +11,7 @@ int cmd_update_server_info(int argc, const char **argv, const char *prefix) { int force = 0; struct option options[] = { - OPT_BOOLEAN('f', "force", &force, - "update the info files from scratch"), + OPT__FORCE(&force, "update the info files from scratch"), OPT_END() }; diff --git a/builtin/upload-archive.c b/builtin/upload-archive.c index 73f788ef24..2d0b38333e 100644 --- a/builtin/upload-archive.c +++ b/builtin/upload-archive.c @@ -64,7 +64,7 @@ static int run_upload_archive(int argc, const char **argv, const char *prefix) sent_argv[sent_argc] = NULL; /* parse all options sent by the client */ - return write_archive(sent_argc, sent_argv, prefix, 0); + return write_archive(sent_argc, sent_argv, prefix, 0, NULL, 1); } __attribute__((format (printf, 1, 2))) diff --git a/builtin/var.c b/builtin/var.c index 70fdb4dec7..99d068a532 100644 --- a/builtin/var.c +++ b/builtin/var.c @@ -3,8 +3,7 @@ * * Copyright (C) Eric Biederman, 2005 */ -#include "cache.h" -#include "exec_cmd.h" +#include "builtin.h" static const char var_usage[] = "git var (-l | <variable>)"; @@ -74,14 +73,9 @@ static int show_config(const char *var, const char *value, void *cb) int cmd_var(int argc, const char **argv, const char *prefix) { - const char *val; - int nongit; - if (argc != 2) { + const char *val = NULL; + if (argc != 2) usage(var_usage); - } - - setup_git_directory_gently(&nongit); - val = NULL; if (strcmp(argv[1], "-l") == 0) { git_config(show_config, NULL); diff --git a/builtin/verify-pack.c b/builtin/verify-pack.c index b6079ae6cb..e841b4a38d 100644 --- a/builtin/verify-pack.c +++ b/builtin/verify-pack.c @@ -1,134 +1,53 @@ #include "builtin.h" #include "cache.h" -#include "pack.h" -#include "pack-revindex.h" +#include "run-command.h" #include "parse-options.h" -#define MAX_CHAIN 50 - #define VERIFY_PACK_VERBOSE 01 #define VERIFY_PACK_STAT_ONLY 02 -static void show_pack_info(struct packed_git *p, unsigned int flags) -{ - uint32_t nr_objects, i; - int cnt; - int stat_only = flags & VERIFY_PACK_STAT_ONLY; - unsigned long chain_histogram[MAX_CHAIN+1], baseobjects; - - nr_objects = p->num_objects; - memset(chain_histogram, 0, sizeof(chain_histogram)); - baseobjects = 0; - - for (i = 0; i < nr_objects; i++) { - const unsigned char *sha1; - unsigned char base_sha1[20]; - const char *type; - unsigned long size; - unsigned long store_size; - off_t offset; - unsigned int delta_chain_length; - - sha1 = nth_packed_object_sha1(p, i); - if (!sha1) - die("internal error pack-check nth-packed-object"); - offset = nth_packed_object_offset(p, i); - type = packed_object_info_detail(p, offset, &size, &store_size, - &delta_chain_length, - base_sha1); - if (!stat_only) - printf("%s ", sha1_to_hex(sha1)); - if (!delta_chain_length) { - if (!stat_only) - printf("%-6s %lu %lu %"PRIuMAX"\n", - type, size, store_size, (uintmax_t)offset); - baseobjects++; - } - else { - if (!stat_only) - printf("%-6s %lu %lu %"PRIuMAX" %u %s\n", - type, size, store_size, (uintmax_t)offset, - delta_chain_length, sha1_to_hex(base_sha1)); - if (delta_chain_length <= MAX_CHAIN) - chain_histogram[delta_chain_length]++; - else - chain_histogram[0]++; - } - } - - if (baseobjects) - printf("non delta: %lu object%s\n", - baseobjects, baseobjects > 1 ? "s" : ""); - - for (cnt = 1; cnt <= MAX_CHAIN; cnt++) { - if (!chain_histogram[cnt]) - continue; - printf("chain length = %d: %lu object%s\n", cnt, - chain_histogram[cnt], - chain_histogram[cnt] > 1 ? "s" : ""); - } - if (chain_histogram[0]) - printf("chain length > %d: %lu object%s\n", MAX_CHAIN, - chain_histogram[0], - chain_histogram[0] > 1 ? "s" : ""); -} - static int verify_one_pack(const char *path, unsigned int flags) { - char arg[PATH_MAX]; - int len; + struct child_process index_pack; + const char *argv[] = {"index-pack", NULL, NULL, NULL }; + struct strbuf arg = STRBUF_INIT; int verbose = flags & VERIFY_PACK_VERBOSE; int stat_only = flags & VERIFY_PACK_STAT_ONLY; - struct packed_git *pack; int err; - len = strlcpy(arg, path, PATH_MAX); - if (len >= PATH_MAX) - return error("name too long: %s", path); - - /* - * In addition to "foo.idx" we accept "foo.pack" and "foo"; - * normalize these forms to "foo.idx" for add_packed_git(). - */ - if (has_extension(arg, ".pack")) { - strcpy(arg + len - 5, ".idx"); - len--; - } else if (!has_extension(arg, ".idx")) { - if (len + 4 >= PATH_MAX) - return error("name too long: %s.idx", arg); - strcpy(arg + len, ".idx"); - len += 4; - } + if (stat_only) + argv[1] = "--verify-stat-only"; + else if (verbose) + argv[1] = "--verify-stat"; + else + argv[1] = "--verify"; /* - * add_packed_git() uses our buffer (containing "foo.idx") to - * build the pack filename ("foo.pack"). Make sure it fits. + * In addition to "foo.pack" we accept "foo.idx" and "foo"; + * normalize these forms to "foo.pack" for "index-pack --verify". */ - if (len + 1 >= PATH_MAX) { - arg[len - 4] = '\0'; - return error("name too long: %s.pack", arg); - } - - pack = add_packed_git(arg, len, 1); - if (!pack) - return error("packfile %s not found.", arg); + strbuf_addstr(&arg, path); + if (has_extension(arg.buf, ".idx")) + strbuf_splice(&arg, arg.len - 3, 3, "pack", 4); + else if (!has_extension(arg.buf, ".pack")) + strbuf_add(&arg, ".pack", 5); + argv[2] = arg.buf; - install_packed_git(pack); + memset(&index_pack, 0, sizeof(index_pack)); + index_pack.argv = argv; + index_pack.git_cmd = 1; - if (!stat_only) - err = verify_pack(pack); - else - err = open_pack_index(pack); + err = run_command(&index_pack); if (verbose || stat_only) { if (err) - printf("%s: bad\n", pack->pack_name); + printf("%s: bad\n", arg.buf); else { - show_pack_info(pack, flags); if (!stat_only) - printf("%s: ok\n", pack->pack_name); + printf("%s: ok\n", arg.buf); } } + strbuf_release(&arg); return err; } @@ -159,7 +78,6 @@ int cmd_verify_pack(int argc, const char **argv, const char *prefix) for (i = 0; i < argc; i++) { if (verify_one_pack(argv[i], flags)) err = 1; - discard_revindex(); } return err; diff --git a/builtin/verify-tag.c b/builtin/verify-tag.c index 9f482c29f5..3134766049 100644 --- a/builtin/verify-tag.c +++ b/builtin/verify-tag.c @@ -17,13 +17,11 @@ static const char * const verify_tag_usage[] = { NULL }; -#define PGP_SIGNATURE "-----BEGIN PGP SIGNATURE-----" - static int run_gpg_verify(const char *buf, unsigned long size, int verbose) { struct child_process gpg; const char *args_gpg[] = {"gpg", "--verify", "FILE", "-", NULL}; - char path[PATH_MAX], *eol; + char path[PATH_MAX]; size_t len; int fd, ret; @@ -37,11 +35,7 @@ static int run_gpg_verify(const char *buf, unsigned long size, int verbose) close(fd); /* find the length without signature */ - len = 0; - while (len < size && prefixcmp(buf + len, PGP_SIGNATURE)) { - eol = memchr(buf + len, '\n', size - len); - len += eol ? eol - (buf + len) + 1 : size - len; - } + len = parse_signature(buf, size); if (verbose) write_in_full(1, buf, len); @@ -93,7 +87,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), + OPT__VERBOSE(&verbose, "print tag contents"), OPT_END() }; |