diff options
Diffstat (limited to 'builtin')
91 files changed, 10366 insertions, 11237 deletions
diff --git a/builtin/add.c b/builtin/add.c index df5135bf62..e8fb80b36e 100644 --- a/builtin/add.c +++ b/builtin/add.c @@ -30,6 +30,21 @@ struct update_callback_data { int add_errors; }; +static void chmod_pathspec(struct pathspec *pathspec, int force_mode) +{ + int i; + + for (i = 0; i < active_nr; i++) { + struct cache_entry *ce = active_cache[i]; + + if (pathspec && !ce_path_match(ce, pathspec, NULL)) + continue; + + if (chmod_cache_entry(ce, force_mode) < 0) + fprintf(stderr, "cannot chmod '%s'", ce->name); + } +} + static int fix_unmerged_status(struct diff_filepair *p, struct update_callback_data *data) { @@ -63,10 +78,9 @@ static void update_callback(struct diff_queue_struct *q, switch (fix_unmerged_status(p, data)) { default: die(_("unexpected diff status %c"), p->status); - case DIFF_STATUS_ADDED: case DIFF_STATUS_MODIFIED: case DIFF_STATUS_TYPE_CHANGED: - if (add_file_to_index(&the_index, path, data->flags)) { + if (add_file_to_index(&the_index, path, data->flags)) { if (!(data->flags & ADD_CACHE_IGNORE_ERRORS)) die(_("updating files failed")); data->add_errors++; @@ -239,6 +253,8 @@ static int ignore_add_errors, intent_to_add, ignore_missing; static int addremove = ADDREMOVE_DEFAULT; static int addremove_explicit = -1; /* unspecified */ +static char *chmod_arg; + static int ignore_removal_cb(const struct option *opt, const char *arg, int unset) { /* if we are told to ignore, we are not adding removals */ @@ -264,6 +280,7 @@ static struct option builtin_add_options[] = { OPT_BOOL( 0 , "refresh", &refresh_only, N_("don't add, only refresh the index")), OPT_BOOL( 0 , "ignore-errors", &ignore_add_errors, N_("just skip files which cannot be added because of errors")), OPT_BOOL( 0 , "ignore-missing", &ignore_missing, N_("check if - even missing - files are ignored in dry run")), + OPT_STRING( 0 , "chmod", &chmod_arg, N_("(+/-)x"), N_("override the executable bit of the listed files")), OPT_END(), }; @@ -290,7 +307,7 @@ static int add_files(struct dir_struct *dir, int flags) } for (i = 0; i < dir->nr; i++) - if (add_file_to_cache(dir->entries[i]->name, flags)) { + if (add_file_to_index(&the_index, dir->entries[i]->name, flags)) { if (!ignore_add_errors) die(_("adding files failed")); exit_status = 1; @@ -337,14 +354,12 @@ int cmd_add(int argc, const char **argv, const char *prefix) if (!show_only && ignore_missing) die(_("Option --ignore-missing can only be used together with --dry-run")); - if ((0 < addremove_explicit || take_worktree_changes) && !argc) { - static const char *whole[2] = { ":/", NULL }; - argc = 1; - argv = whole; - } + if (chmod_arg && ((chmod_arg[0] != '-' && chmod_arg[0] != '+') || + chmod_arg[1] != 'x' || chmod_arg[2])) + die(_("--chmod param '%s' must be either -x or +x"), chmod_arg); add_new_files = !take_worktree_changes && !refresh_only; - require_pathspec = !take_worktree_changes; + require_pathspec = !(take_worktree_changes || (0 < addremove_explicit)); hold_locked_index(&lock_file, 1); @@ -376,7 +391,6 @@ int cmd_add(int argc, const char **argv, const char *prefix) if (add_new_files) { int baselen; - struct pathspec empty_pathspec; /* Set up the default git porcelain excludes */ memset(&dir, 0, sizeof(dir)); @@ -385,7 +399,6 @@ int cmd_add(int argc, const char **argv, const char *prefix) setup_standard_excludes(&dir); } - memset(&empty_pathspec, 0, sizeof(empty_pathspec)); /* This picks up the paths that are not tracked */ baselen = fill_directory(&dir, &pathspec); if (pathspec.nr) @@ -440,6 +453,8 @@ int cmd_add(int argc, const char **argv, const char *prefix) if (add_new_files) exit_status |= add_files(&dir, flags); + if (chmod_arg && pathspec.nr) + chmod_pathspec(&pathspec, chmod_arg[0]); unplug_bulk_checkin(); finish: diff --git a/builtin/am.c b/builtin/am.c new file mode 100644 index 0000000000..826f18ba12 --- /dev/null +++ b/builtin/am.c @@ -0,0 +1,2423 @@ +/* + * Builtin "git am" + * + * Based on git-am.sh by Junio C Hamano. + */ +#include "cache.h" +#include "builtin.h" +#include "exec_cmd.h" +#include "parse-options.h" +#include "dir.h" +#include "run-command.h" +#include "quote.h" +#include "tempfile.h" +#include "lockfile.h" +#include "cache-tree.h" +#include "refs.h" +#include "commit.h" +#include "diff.h" +#include "diffcore.h" +#include "unpack-trees.h" +#include "branch.h" +#include "sequencer.h" +#include "revision.h" +#include "merge-recursive.h" +#include "revision.h" +#include "log-tree.h" +#include "notes-utils.h" +#include "rerere.h" +#include "prompt.h" +#include "mailinfo.h" +#include "apply.h" +#include "string-list.h" + +/** + * Returns 1 if the file is empty or does not exist, 0 otherwise. + */ +static int is_empty_file(const char *filename) +{ + struct stat st; + + if (stat(filename, &st) < 0) { + if (errno == ENOENT) + return 1; + die_errno(_("could not stat %s"), filename); + } + + return !st.st_size; +} + +/** + * Returns the length of the first line of msg. + */ +static int linelen(const char *msg) +{ + return strchrnul(msg, '\n') - msg; +} + +/** + * Returns true if `str` consists of only whitespace, false otherwise. + */ +static int str_isspace(const char *str) +{ + for (; *str; str++) + if (!isspace(*str)) + return 0; + + return 1; +} + +enum patch_format { + PATCH_FORMAT_UNKNOWN = 0, + PATCH_FORMAT_MBOX, + PATCH_FORMAT_STGIT, + PATCH_FORMAT_STGIT_SERIES, + PATCH_FORMAT_HG, + PATCH_FORMAT_MBOXRD +}; + +enum keep_type { + KEEP_FALSE = 0, + KEEP_TRUE, /* pass -k flag to git-mailinfo */ + KEEP_NON_PATCH /* pass -b flag to git-mailinfo */ +}; + +enum scissors_type { + SCISSORS_UNSET = -1, + SCISSORS_FALSE = 0, /* pass --no-scissors to git-mailinfo */ + SCISSORS_TRUE /* pass --scissors to git-mailinfo */ +}; + +enum signoff_type { + SIGNOFF_FALSE = 0, + SIGNOFF_TRUE = 1, + SIGNOFF_EXPLICIT /* --signoff was set on the command-line */ +}; + +struct am_state { + /* state directory path */ + char *dir; + + /* current and last patch numbers, 1-indexed */ + int cur; + int last; + + /* commit metadata and message */ + char *author_name; + char *author_email; + char *author_date; + char *msg; + size_t msg_len; + + /* when --rebasing, records the original commit the patch came from */ + struct object_id orig_commit; + + /* number of digits in patch filename */ + int prec; + + /* various operating modes and command line options */ + int interactive; + int threeway; + int quiet; + int signoff; /* enum signoff_type */ + int utf8; + int keep; /* enum keep_type */ + int message_id; + int scissors; /* enum scissors_type */ + struct argv_array git_apply_opts; + const char *resolvemsg; + int committer_date_is_author_date; + int ignore_date; + int allow_rerere_autoupdate; + const char *sign_commit; + int rebasing; +}; + +/** + * Initializes am_state with the default values. The state directory is set to + * dir. + */ +static void am_state_init(struct am_state *state, const char *dir) +{ + int gpgsign; + + memset(state, 0, sizeof(*state)); + + assert(dir); + state->dir = xstrdup(dir); + + state->prec = 4; + + git_config_get_bool("am.threeway", &state->threeway); + + state->utf8 = 1; + + git_config_get_bool("am.messageid", &state->message_id); + + state->scissors = SCISSORS_UNSET; + + argv_array_init(&state->git_apply_opts); + + if (!git_config_get_bool("commit.gpgsign", &gpgsign)) + state->sign_commit = gpgsign ? "" : NULL; +} + +/** + * Releases memory allocated by an am_state. + */ +static void am_state_release(struct am_state *state) +{ + free(state->dir); + free(state->author_name); + free(state->author_email); + free(state->author_date); + free(state->msg); + argv_array_clear(&state->git_apply_opts); +} + +/** + * Returns path relative to the am_state directory. + */ +static inline const char *am_path(const struct am_state *state, const char *path) +{ + return mkpath("%s/%s", state->dir, path); +} + +/** + * For convenience to call write_file() + */ +static void write_state_text(const struct am_state *state, + const char *name, const char *string) +{ + write_file(am_path(state, name), "%s", string); +} + +static void write_state_count(const struct am_state *state, + const char *name, int value) +{ + write_file(am_path(state, name), "%d", value); +} + +static void write_state_bool(const struct am_state *state, + const char *name, int value) +{ + write_state_text(state, name, value ? "t" : "f"); +} + +/** + * If state->quiet is false, calls fprintf(fp, fmt, ...), and appends a newline + * at the end. + */ +static void say(const struct am_state *state, FILE *fp, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + if (!state->quiet) { + vfprintf(fp, fmt, ap); + putc('\n', fp); + } + va_end(ap); +} + +/** + * Returns 1 if there is an am session in progress, 0 otherwise. + */ +static int am_in_progress(const struct am_state *state) +{ + struct stat st; + + if (lstat(state->dir, &st) < 0 || !S_ISDIR(st.st_mode)) + return 0; + if (lstat(am_path(state, "last"), &st) || !S_ISREG(st.st_mode)) + return 0; + if (lstat(am_path(state, "next"), &st) || !S_ISREG(st.st_mode)) + return 0; + return 1; +} + +/** + * Reads the contents of `file` in the `state` directory into `sb`. Returns the + * number of bytes read on success, -1 if the file does not exist. If `trim` is + * set, trailing whitespace will be removed. + */ +static int read_state_file(struct strbuf *sb, const struct am_state *state, + const char *file, int trim) +{ + strbuf_reset(sb); + + if (strbuf_read_file(sb, am_path(state, file), 0) >= 0) { + if (trim) + strbuf_trim(sb); + + return sb->len; + } + + if (errno == ENOENT) + return -1; + + die_errno(_("could not read '%s'"), am_path(state, file)); +} + +/** + * Take a series of KEY='VALUE' lines where VALUE part is + * sq-quoted, and append <KEY, VALUE> at the end of the string list + */ +static int parse_key_value_squoted(char *buf, struct string_list *list) +{ + while (*buf) { + struct string_list_item *item; + char *np; + char *cp = strchr(buf, '='); + if (!cp) + return -1; + np = strchrnul(cp, '\n'); + *cp++ = '\0'; + item = string_list_append(list, buf); + + buf = np + (*np == '\n'); + *np = '\0'; + cp = sq_dequote(cp); + if (!cp) + return -1; + item->util = xstrdup(cp); + } + return 0; +} + +/** + * Reads and parses the state directory's "author-script" file, and sets + * state->author_name, state->author_email and state->author_date accordingly. + * Returns 0 on success, -1 if the file could not be parsed. + * + * The author script is of the format: + * + * GIT_AUTHOR_NAME='$author_name' + * GIT_AUTHOR_EMAIL='$author_email' + * GIT_AUTHOR_DATE='$author_date' + * + * where $author_name, $author_email and $author_date are quoted. We are strict + * with our parsing, as the file was meant to be eval'd in the old git-am.sh + * script, and thus if the file differs from what this function expects, it is + * better to bail out than to do something that the user does not expect. + */ +static int read_author_script(struct am_state *state) +{ + const char *filename = am_path(state, "author-script"); + struct strbuf buf = STRBUF_INIT; + struct string_list kv = STRING_LIST_INIT_DUP; + int retval = -1; /* assume failure */ + int fd; + + assert(!state->author_name); + assert(!state->author_email); + assert(!state->author_date); + + fd = open(filename, O_RDONLY); + if (fd < 0) { + if (errno == ENOENT) + return 0; + die_errno(_("could not open '%s' for reading"), filename); + } + strbuf_read(&buf, fd, 0); + close(fd); + if (parse_key_value_squoted(buf.buf, &kv)) + goto finish; + + if (kv.nr != 3 || + strcmp(kv.items[0].string, "GIT_AUTHOR_NAME") || + strcmp(kv.items[1].string, "GIT_AUTHOR_EMAIL") || + strcmp(kv.items[2].string, "GIT_AUTHOR_DATE")) + goto finish; + state->author_name = kv.items[0].util; + state->author_email = kv.items[1].util; + state->author_date = kv.items[2].util; + retval = 0; +finish: + string_list_clear(&kv, !!retval); + strbuf_release(&buf); + return retval; +} + +/** + * Saves state->author_name, state->author_email and state->author_date in the + * state directory's "author-script" file. + */ +static void write_author_script(const struct am_state *state) +{ + struct strbuf sb = STRBUF_INIT; + + strbuf_addstr(&sb, "GIT_AUTHOR_NAME="); + sq_quote_buf(&sb, state->author_name); + strbuf_addch(&sb, '\n'); + + strbuf_addstr(&sb, "GIT_AUTHOR_EMAIL="); + sq_quote_buf(&sb, state->author_email); + strbuf_addch(&sb, '\n'); + + strbuf_addstr(&sb, "GIT_AUTHOR_DATE="); + sq_quote_buf(&sb, state->author_date); + strbuf_addch(&sb, '\n'); + + write_state_text(state, "author-script", sb.buf); + + strbuf_release(&sb); +} + +/** + * Reads the commit message from the state directory's "final-commit" file, + * setting state->msg to its contents and state->msg_len to the length of its + * contents in bytes. + * + * Returns 0 on success, -1 if the file does not exist. + */ +static int read_commit_msg(struct am_state *state) +{ + struct strbuf sb = STRBUF_INIT; + + assert(!state->msg); + + if (read_state_file(&sb, state, "final-commit", 0) < 0) { + strbuf_release(&sb); + return -1; + } + + state->msg = strbuf_detach(&sb, &state->msg_len); + return 0; +} + +/** + * Saves state->msg in the state directory's "final-commit" file. + */ +static void write_commit_msg(const struct am_state *state) +{ + const char *filename = am_path(state, "final-commit"); + write_file_buf(filename, state->msg, state->msg_len); +} + +/** + * Loads state from disk. + */ +static void am_load(struct am_state *state) +{ + struct strbuf sb = STRBUF_INIT; + + if (read_state_file(&sb, state, "next", 1) < 0) + die("BUG: state file 'next' does not exist"); + state->cur = strtol(sb.buf, NULL, 10); + + if (read_state_file(&sb, state, "last", 1) < 0) + die("BUG: state file 'last' does not exist"); + state->last = strtol(sb.buf, NULL, 10); + + if (read_author_script(state) < 0) + die(_("could not parse author script")); + + read_commit_msg(state); + + if (read_state_file(&sb, state, "original-commit", 1) < 0) + oidclr(&state->orig_commit); + else if (get_oid_hex(sb.buf, &state->orig_commit) < 0) + die(_("could not parse %s"), am_path(state, "original-commit")); + + read_state_file(&sb, state, "threeway", 1); + state->threeway = !strcmp(sb.buf, "t"); + + read_state_file(&sb, state, "quiet", 1); + state->quiet = !strcmp(sb.buf, "t"); + + read_state_file(&sb, state, "sign", 1); + state->signoff = !strcmp(sb.buf, "t"); + + read_state_file(&sb, state, "utf8", 1); + state->utf8 = !strcmp(sb.buf, "t"); + + read_state_file(&sb, state, "keep", 1); + if (!strcmp(sb.buf, "t")) + state->keep = KEEP_TRUE; + else if (!strcmp(sb.buf, "b")) + state->keep = KEEP_NON_PATCH; + else + state->keep = KEEP_FALSE; + + read_state_file(&sb, state, "messageid", 1); + state->message_id = !strcmp(sb.buf, "t"); + + read_state_file(&sb, state, "scissors", 1); + if (!strcmp(sb.buf, "t")) + state->scissors = SCISSORS_TRUE; + else if (!strcmp(sb.buf, "f")) + state->scissors = SCISSORS_FALSE; + else + state->scissors = SCISSORS_UNSET; + + read_state_file(&sb, state, "apply-opt", 1); + argv_array_clear(&state->git_apply_opts); + if (sq_dequote_to_argv_array(sb.buf, &state->git_apply_opts) < 0) + die(_("could not parse %s"), am_path(state, "apply-opt")); + + state->rebasing = !!file_exists(am_path(state, "rebasing")); + + strbuf_release(&sb); +} + +/** + * Removes the am_state directory, forcefully terminating the current am + * session. + */ +static void am_destroy(const struct am_state *state) +{ + struct strbuf sb = STRBUF_INIT; + + strbuf_addstr(&sb, state->dir); + remove_dir_recursively(&sb, 0); + strbuf_release(&sb); +} + +/** + * Runs applypatch-msg hook. Returns its exit code. + */ +static int run_applypatch_msg_hook(struct am_state *state) +{ + int ret; + + assert(state->msg); + ret = run_hook_le(NULL, "applypatch-msg", am_path(state, "final-commit"), NULL); + + if (!ret) { + free(state->msg); + state->msg = NULL; + if (read_commit_msg(state) < 0) + die(_("'%s' was deleted by the applypatch-msg hook"), + am_path(state, "final-commit")); + } + + return ret; +} + +/** + * Runs post-rewrite hook. Returns it exit code. + */ +static int run_post_rewrite_hook(const struct am_state *state) +{ + struct child_process cp = CHILD_PROCESS_INIT; + const char *hook = find_hook("post-rewrite"); + int ret; + + if (!hook) + return 0; + + argv_array_push(&cp.args, hook); + argv_array_push(&cp.args, "rebase"); + + cp.in = xopen(am_path(state, "rewritten"), O_RDONLY); + cp.stdout_to_stderr = 1; + + ret = run_command(&cp); + + close(cp.in); + return ret; +} + +/** + * Reads the state directory's "rewritten" file, and copies notes from the old + * commits listed in the file to their rewritten commits. + * + * Returns 0 on success, -1 on failure. + */ +static int copy_notes_for_rebase(const struct am_state *state) +{ + struct notes_rewrite_cfg *c; + struct strbuf sb = STRBUF_INIT; + const char *invalid_line = _("Malformed input line: '%s'."); + const char *msg = "Notes added by 'git rebase'"; + FILE *fp; + int ret = 0; + + assert(state->rebasing); + + c = init_copy_notes_for_rewrite("rebase"); + if (!c) + return 0; + + fp = xfopen(am_path(state, "rewritten"), "r"); + + while (!strbuf_getline_lf(&sb, fp)) { + struct object_id from_obj, to_obj; + + if (sb.len != GIT_SHA1_HEXSZ * 2 + 1) { + ret = error(invalid_line, sb.buf); + goto finish; + } + + if (get_oid_hex(sb.buf, &from_obj)) { + ret = error(invalid_line, sb.buf); + goto finish; + } + + if (sb.buf[GIT_SHA1_HEXSZ] != ' ') { + ret = error(invalid_line, sb.buf); + goto finish; + } + + if (get_oid_hex(sb.buf + GIT_SHA1_HEXSZ + 1, &to_obj)) { + ret = error(invalid_line, sb.buf); + goto finish; + } + + if (copy_note_for_rewrite(c, from_obj.hash, to_obj.hash)) + ret = error(_("Failed to copy notes from '%s' to '%s'"), + oid_to_hex(&from_obj), oid_to_hex(&to_obj)); + } + +finish: + finish_copy_notes_for_rewrite(c, msg); + fclose(fp); + strbuf_release(&sb); + return ret; +} + +/** + * Determines if the file looks like a piece of RFC2822 mail by grabbing all + * non-indented lines and checking if they look like they begin with valid + * header field names. + * + * Returns 1 if the file looks like a piece of mail, 0 otherwise. + */ +static int is_mail(FILE *fp) +{ + const char *header_regex = "^[!-9;-~]+:"; + struct strbuf sb = STRBUF_INIT; + regex_t regex; + int ret = 1; + + if (fseek(fp, 0L, SEEK_SET)) + die_errno(_("fseek failed")); + + if (regcomp(®ex, header_regex, REG_NOSUB | REG_EXTENDED)) + die("invalid pattern: %s", header_regex); + + while (!strbuf_getline(&sb, fp)) { + if (!sb.len) + break; /* End of header */ + + /* Ignore indented folded lines */ + if (*sb.buf == '\t' || *sb.buf == ' ') + continue; + + /* It's a header if it matches header_regex */ + if (regexec(®ex, sb.buf, 0, NULL, 0)) { + ret = 0; + goto done; + } + } + +done: + regfree(®ex); + strbuf_release(&sb); + return ret; +} + +/** + * Attempts to detect the patch_format of the patches contained in `paths`, + * returning the PATCH_FORMAT_* enum value. Returns PATCH_FORMAT_UNKNOWN if + * detection fails. + */ +static int detect_patch_format(const char **paths) +{ + enum patch_format ret = PATCH_FORMAT_UNKNOWN; + struct strbuf l1 = STRBUF_INIT; + struct strbuf l2 = STRBUF_INIT; + struct strbuf l3 = STRBUF_INIT; + FILE *fp; + + /* + * We default to mbox format if input is from stdin and for directories + */ + if (!*paths || !strcmp(*paths, "-") || is_directory(*paths)) + return PATCH_FORMAT_MBOX; + + /* + * Otherwise, check the first few lines of the first patch, starting + * from the first non-blank line, to try to detect its format. + */ + + fp = xfopen(*paths, "r"); + + while (!strbuf_getline(&l1, fp)) { + if (l1.len) + break; + } + + if (starts_with(l1.buf, "From ") || starts_with(l1.buf, "From: ")) { + ret = PATCH_FORMAT_MBOX; + goto done; + } + + if (starts_with(l1.buf, "# This series applies on GIT commit")) { + ret = PATCH_FORMAT_STGIT_SERIES; + goto done; + } + + if (!strcmp(l1.buf, "# HG changeset patch")) { + ret = PATCH_FORMAT_HG; + goto done; + } + + strbuf_reset(&l2); + strbuf_getline(&l2, fp); + strbuf_reset(&l3); + strbuf_getline(&l3, fp); + + /* + * If the second line is empty and the third is a From, Author or Date + * entry, this is likely an StGit patch. + */ + if (l1.len && !l2.len && + (starts_with(l3.buf, "From:") || + starts_with(l3.buf, "Author:") || + starts_with(l3.buf, "Date:"))) { + ret = PATCH_FORMAT_STGIT; + goto done; + } + + if (l1.len && is_mail(fp)) { + ret = PATCH_FORMAT_MBOX; + goto done; + } + +done: + fclose(fp); + strbuf_release(&l1); + return ret; +} + +/** + * Splits out individual email patches from `paths`, where each path is either + * a mbox file or a Maildir. Returns 0 on success, -1 on failure. + */ +static int split_mail_mbox(struct am_state *state, const char **paths, + int keep_cr, int mboxrd) +{ + struct child_process cp = CHILD_PROCESS_INIT; + struct strbuf last = STRBUF_INIT; + + cp.git_cmd = 1; + argv_array_push(&cp.args, "mailsplit"); + argv_array_pushf(&cp.args, "-d%d", state->prec); + argv_array_pushf(&cp.args, "-o%s", state->dir); + argv_array_push(&cp.args, "-b"); + if (keep_cr) + argv_array_push(&cp.args, "--keep-cr"); + if (mboxrd) + argv_array_push(&cp.args, "--mboxrd"); + argv_array_push(&cp.args, "--"); + argv_array_pushv(&cp.args, paths); + + if (capture_command(&cp, &last, 8)) + return -1; + + state->cur = 1; + state->last = strtol(last.buf, NULL, 10); + + return 0; +} + +/** + * Callback signature for split_mail_conv(). The foreign patch should be + * read from `in`, and the converted patch (in RFC2822 mail format) should be + * written to `out`. Return 0 on success, or -1 on failure. + */ +typedef int (*mail_conv_fn)(FILE *out, FILE *in, int keep_cr); + +/** + * Calls `fn` for each file in `paths` to convert the foreign patch to the + * RFC2822 mail format suitable for parsing with git-mailinfo. + * + * Returns 0 on success, -1 on failure. + */ +static int split_mail_conv(mail_conv_fn fn, struct am_state *state, + const char **paths, int keep_cr) +{ + static const char *stdin_only[] = {"-", NULL}; + int i; + + if (!*paths) + paths = stdin_only; + + for (i = 0; *paths; paths++, i++) { + FILE *in, *out; + const char *mail; + int ret; + + if (!strcmp(*paths, "-")) + in = stdin; + else + in = fopen(*paths, "r"); + + if (!in) + return error_errno(_("could not open '%s' for reading"), + *paths); + + mail = mkpath("%s/%0*d", state->dir, state->prec, i + 1); + + out = fopen(mail, "w"); + if (!out) + return error_errno(_("could not open '%s' for writing"), + mail); + + ret = fn(out, in, keep_cr); + + fclose(out); + fclose(in); + + if (ret) + return error(_("could not parse patch '%s'"), *paths); + } + + state->cur = 1; + state->last = i; + return 0; +} + +/** + * A split_mail_conv() callback that converts an StGit patch to an RFC2822 + * message suitable for parsing with git-mailinfo. + */ +static int stgit_patch_to_mail(FILE *out, FILE *in, int keep_cr) +{ + struct strbuf sb = STRBUF_INIT; + int subject_printed = 0; + + while (!strbuf_getline_lf(&sb, in)) { + const char *str; + + if (str_isspace(sb.buf)) + continue; + else if (skip_prefix(sb.buf, "Author:", &str)) + fprintf(out, "From:%s\n", str); + else if (starts_with(sb.buf, "From") || starts_with(sb.buf, "Date")) + fprintf(out, "%s\n", sb.buf); + else if (!subject_printed) { + fprintf(out, "Subject: %s\n", sb.buf); + subject_printed = 1; + } else { + fprintf(out, "\n%s\n", sb.buf); + break; + } + } + + strbuf_reset(&sb); + while (strbuf_fread(&sb, 8192, in) > 0) { + fwrite(sb.buf, 1, sb.len, out); + strbuf_reset(&sb); + } + + strbuf_release(&sb); + return 0; +} + +/** + * This function only supports a single StGit series file in `paths`. + * + * Given an StGit series file, converts the StGit patches in the series into + * RFC2822 messages suitable for parsing with git-mailinfo, and queues them in + * the state directory. + * + * Returns 0 on success, -1 on failure. + */ +static int split_mail_stgit_series(struct am_state *state, const char **paths, + int keep_cr) +{ + const char *series_dir; + char *series_dir_buf; + FILE *fp; + struct argv_array patches = ARGV_ARRAY_INIT; + struct strbuf sb = STRBUF_INIT; + int ret; + + if (!paths[0] || paths[1]) + return error(_("Only one StGIT patch series can be applied at once")); + + series_dir_buf = xstrdup(*paths); + series_dir = dirname(series_dir_buf); + + fp = fopen(*paths, "r"); + if (!fp) + return error_errno(_("could not open '%s' for reading"), *paths); + + while (!strbuf_getline_lf(&sb, fp)) { + if (*sb.buf == '#') + continue; /* skip comment lines */ + + argv_array_push(&patches, mkpath("%s/%s", series_dir, sb.buf)); + } + + fclose(fp); + strbuf_release(&sb); + free(series_dir_buf); + + ret = split_mail_conv(stgit_patch_to_mail, state, patches.argv, keep_cr); + + argv_array_clear(&patches); + return ret; +} + +/** + * A split_patches_conv() callback that converts a mercurial patch to a RFC2822 + * message suitable for parsing with git-mailinfo. + */ +static int hg_patch_to_mail(FILE *out, FILE *in, int keep_cr) +{ + struct strbuf sb = STRBUF_INIT; + + while (!strbuf_getline_lf(&sb, in)) { + const char *str; + + if (skip_prefix(sb.buf, "# User ", &str)) + fprintf(out, "From: %s\n", str); + else if (skip_prefix(sb.buf, "# Date ", &str)) { + unsigned long timestamp; + long tz, tz2; + char *end; + + errno = 0; + timestamp = strtoul(str, &end, 10); + if (errno) + return error(_("invalid timestamp")); + + if (!skip_prefix(end, " ", &str)) + return error(_("invalid Date line")); + + errno = 0; + tz = strtol(str, &end, 10); + if (errno) + return error(_("invalid timezone offset")); + + if (*end) + return error(_("invalid Date line")); + + /* + * mercurial's timezone is in seconds west of UTC, + * however git's timezone is in hours + minutes east of + * UTC. Convert it. + */ + tz2 = labs(tz) / 3600 * 100 + labs(tz) % 3600 / 60; + if (tz > 0) + tz2 = -tz2; + + fprintf(out, "Date: %s\n", show_date(timestamp, tz2, DATE_MODE(RFC2822))); + } else if (starts_with(sb.buf, "# ")) { + continue; + } else { + fprintf(out, "\n%s\n", sb.buf); + break; + } + } + + strbuf_reset(&sb); + while (strbuf_fread(&sb, 8192, in) > 0) { + fwrite(sb.buf, 1, sb.len, out); + strbuf_reset(&sb); + } + + strbuf_release(&sb); + return 0; +} + +/** + * Splits a list of files/directories into individual email patches. Each path + * in `paths` must be a file/directory that is formatted according to + * `patch_format`. + * + * Once split out, the individual email patches will be stored in the state + * directory, with each patch's filename being its index, padded to state->prec + * digits. + * + * state->cur will be set to the index of the first mail, and state->last will + * be set to the index of the last mail. + * + * Set keep_cr to 0 to convert all lines ending with \r\n to end with \n, 1 + * to disable this behavior, -1 to use the default configured setting. + * + * Returns 0 on success, -1 on failure. + */ +static int split_mail(struct am_state *state, enum patch_format patch_format, + const char **paths, int keep_cr) +{ + if (keep_cr < 0) { + keep_cr = 0; + git_config_get_bool("am.keepcr", &keep_cr); + } + + switch (patch_format) { + case PATCH_FORMAT_MBOX: + return split_mail_mbox(state, paths, keep_cr, 0); + case PATCH_FORMAT_STGIT: + return split_mail_conv(stgit_patch_to_mail, state, paths, keep_cr); + case PATCH_FORMAT_STGIT_SERIES: + return split_mail_stgit_series(state, paths, keep_cr); + case PATCH_FORMAT_HG: + return split_mail_conv(hg_patch_to_mail, state, paths, keep_cr); + case PATCH_FORMAT_MBOXRD: + return split_mail_mbox(state, paths, keep_cr, 1); + default: + die("BUG: invalid patch_format"); + } + return -1; +} + +/** + * Setup a new am session for applying patches + */ +static void am_setup(struct am_state *state, enum patch_format patch_format, + const char **paths, int keep_cr) +{ + struct object_id curr_head; + const char *str; + struct strbuf sb = STRBUF_INIT; + + if (!patch_format) + patch_format = detect_patch_format(paths); + + if (!patch_format) { + fprintf_ln(stderr, _("Patch format detection failed.")); + exit(128); + } + + if (mkdir(state->dir, 0777) < 0 && errno != EEXIST) + die_errno(_("failed to create directory '%s'"), state->dir); + + if (split_mail(state, patch_format, paths, keep_cr) < 0) { + am_destroy(state); + die(_("Failed to split patches.")); + } + + if (state->rebasing) + state->threeway = 1; + + write_state_bool(state, "threeway", state->threeway); + write_state_bool(state, "quiet", state->quiet); + write_state_bool(state, "sign", state->signoff); + write_state_bool(state, "utf8", state->utf8); + + switch (state->keep) { + case KEEP_FALSE: + str = "f"; + break; + case KEEP_TRUE: + str = "t"; + break; + case KEEP_NON_PATCH: + str = "b"; + break; + default: + die("BUG: invalid value for state->keep"); + } + + write_state_text(state, "keep", str); + write_state_bool(state, "messageid", state->message_id); + + switch (state->scissors) { + case SCISSORS_UNSET: + str = ""; + break; + case SCISSORS_FALSE: + str = "f"; + break; + case SCISSORS_TRUE: + str = "t"; + break; + default: + die("BUG: invalid value for state->scissors"); + } + write_state_text(state, "scissors", str); + + sq_quote_argv(&sb, state->git_apply_opts.argv, 0); + write_state_text(state, "apply-opt", sb.buf); + + if (state->rebasing) + write_state_text(state, "rebasing", ""); + else + write_state_text(state, "applying", ""); + + if (!get_oid("HEAD", &curr_head)) { + write_state_text(state, "abort-safety", oid_to_hex(&curr_head)); + if (!state->rebasing) + update_ref_oid("am", "ORIG_HEAD", &curr_head, NULL, 0, + UPDATE_REFS_DIE_ON_ERR); + } else { + write_state_text(state, "abort-safety", ""); + if (!state->rebasing) + delete_ref("ORIG_HEAD", NULL, 0); + } + + /* + * NOTE: Since the "next" and "last" files determine if an am_state + * session is in progress, they should be written last. + */ + + write_state_count(state, "next", state->cur); + write_state_count(state, "last", state->last); + + strbuf_release(&sb); +} + +/** + * Increments the patch pointer, and cleans am_state for the application of the + * next patch. + */ +static void am_next(struct am_state *state) +{ + struct object_id head; + + free(state->author_name); + state->author_name = NULL; + + free(state->author_email); + state->author_email = NULL; + + free(state->author_date); + state->author_date = NULL; + + free(state->msg); + state->msg = NULL; + state->msg_len = 0; + + unlink(am_path(state, "author-script")); + unlink(am_path(state, "final-commit")); + + oidclr(&state->orig_commit); + unlink(am_path(state, "original-commit")); + + if (!get_oid("HEAD", &head)) + write_state_text(state, "abort-safety", oid_to_hex(&head)); + else + write_state_text(state, "abort-safety", ""); + + state->cur++; + write_state_count(state, "next", state->cur); +} + +/** + * Returns the filename of the current patch email. + */ +static const char *msgnum(const struct am_state *state) +{ + static struct strbuf sb = STRBUF_INIT; + + strbuf_reset(&sb); + strbuf_addf(&sb, "%0*d", state->prec, state->cur); + + return sb.buf; +} + +/** + * Refresh and write index. + */ +static void refresh_and_write_cache(void) +{ + struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file)); + + hold_locked_index(lock_file, 1); + refresh_cache(REFRESH_QUIET); + if (write_locked_index(&the_index, lock_file, COMMIT_LOCK)) + die(_("unable to write index file")); +} + +/** + * Returns 1 if the index differs from HEAD, 0 otherwise. When on an unborn + * branch, returns 1 if there are entries in the index, 0 otherwise. If an + * strbuf is provided, the space-separated list of files that differ will be + * appended to it. + */ +static int index_has_changes(struct strbuf *sb) +{ + struct object_id head; + int i; + + if (!get_sha1_tree("HEAD", head.hash)) { + struct diff_options opt; + + diff_setup(&opt); + DIFF_OPT_SET(&opt, EXIT_WITH_STATUS); + if (!sb) + DIFF_OPT_SET(&opt, QUICK); + do_diff_cache(head.hash, &opt); + diffcore_std(&opt); + for (i = 0; sb && i < diff_queued_diff.nr; i++) { + if (i) + strbuf_addch(sb, ' '); + strbuf_addstr(sb, diff_queued_diff.queue[i]->two->path); + } + diff_flush(&opt); + return DIFF_OPT_TST(&opt, HAS_CHANGES) != 0; + } else { + for (i = 0; sb && i < active_nr; i++) { + if (i) + strbuf_addch(sb, ' '); + strbuf_addstr(sb, active_cache[i]->name); + } + return !!active_nr; + } +} + +/** + * Dies with a user-friendly message on how to proceed after resolving the + * problem. This message can be overridden with state->resolvemsg. + */ +static void NORETURN die_user_resolve(const struct am_state *state) +{ + if (state->resolvemsg) { + printf_ln("%s", state->resolvemsg); + } else { + const char *cmdline = state->interactive ? "git am -i" : "git am"; + + printf_ln(_("When you have resolved this problem, run \"%s --continue\"."), cmdline); + printf_ln(_("If you prefer to skip this patch, run \"%s --skip\" instead."), cmdline); + printf_ln(_("To restore the original branch and stop patching, run \"%s --abort\"."), cmdline); + } + + exit(128); +} + +static void am_signoff(struct strbuf *sb) +{ + char *cp; + struct strbuf mine = STRBUF_INIT; + + /* Does it end with our own sign-off? */ + strbuf_addf(&mine, "\n%s%s\n", + sign_off_header, + fmt_name(getenv("GIT_COMMITTER_NAME"), + getenv("GIT_COMMITTER_EMAIL"))); + if (mine.len < sb->len && + !strcmp(mine.buf, sb->buf + sb->len - mine.len)) + goto exit; /* no need to duplicate */ + + /* Does it have any Signed-off-by: in the text */ + for (cp = sb->buf; + cp && *cp && (cp = strstr(cp, sign_off_header)) != NULL; + cp = strchr(cp, '\n')) { + if (sb->buf == cp || cp[-1] == '\n') + break; + } + + strbuf_addstr(sb, mine.buf + !!cp); +exit: + strbuf_release(&mine); +} + +/** + * Appends signoff to the "msg" field of the am_state. + */ +static void am_append_signoff(struct am_state *state) +{ + struct strbuf sb = STRBUF_INIT; + + strbuf_attach(&sb, state->msg, state->msg_len, state->msg_len); + am_signoff(&sb); + state->msg = strbuf_detach(&sb, &state->msg_len); +} + +/** + * Parses `mail` using git-mailinfo, extracting its patch and authorship info. + * state->msg will be set to the patch message. state->author_name, + * state->author_email and state->author_date will be set to the patch author's + * name, email and date respectively. The patch body will be written to the + * state directory's "patch" file. + * + * Returns 1 if the patch should be skipped, 0 otherwise. + */ +static int parse_mail(struct am_state *state, const char *mail) +{ + FILE *fp; + struct strbuf sb = STRBUF_INIT; + struct strbuf msg = STRBUF_INIT; + struct strbuf author_name = STRBUF_INIT; + struct strbuf author_date = STRBUF_INIT; + struct strbuf author_email = STRBUF_INIT; + int ret = 0; + struct mailinfo mi; + + setup_mailinfo(&mi); + + if (state->utf8) + mi.metainfo_charset = get_commit_output_encoding(); + else + mi.metainfo_charset = NULL; + + switch (state->keep) { + case KEEP_FALSE: + break; + case KEEP_TRUE: + mi.keep_subject = 1; + break; + case KEEP_NON_PATCH: + mi.keep_non_patch_brackets_in_subject = 1; + break; + default: + die("BUG: invalid value for state->keep"); + } + + if (state->message_id) + mi.add_message_id = 1; + + switch (state->scissors) { + case SCISSORS_UNSET: + break; + case SCISSORS_FALSE: + mi.use_scissors = 0; + break; + case SCISSORS_TRUE: + mi.use_scissors = 1; + break; + default: + die("BUG: invalid value for state->scissors"); + } + + mi.input = fopen(mail, "r"); + if (!mi.input) + die("could not open input"); + mi.output = fopen(am_path(state, "info"), "w"); + if (!mi.output) + die("could not open output 'info'"); + if (mailinfo(&mi, am_path(state, "msg"), am_path(state, "patch"))) + die("could not parse patch"); + + fclose(mi.input); + fclose(mi.output); + + /* Extract message and author information */ + fp = xfopen(am_path(state, "info"), "r"); + while (!strbuf_getline_lf(&sb, fp)) { + const char *x; + + if (skip_prefix(sb.buf, "Subject: ", &x)) { + if (msg.len) + strbuf_addch(&msg, '\n'); + strbuf_addstr(&msg, x); + } else if (skip_prefix(sb.buf, "Author: ", &x)) + strbuf_addstr(&author_name, x); + else if (skip_prefix(sb.buf, "Email: ", &x)) + strbuf_addstr(&author_email, x); + else if (skip_prefix(sb.buf, "Date: ", &x)) + strbuf_addstr(&author_date, x); + } + fclose(fp); + + /* Skip pine's internal folder data */ + if (!strcmp(author_name.buf, "Mail System Internal Data")) { + ret = 1; + goto finish; + } + + if (is_empty_file(am_path(state, "patch"))) { + printf_ln(_("Patch is empty. Was it split wrong?")); + die_user_resolve(state); + } + + strbuf_addstr(&msg, "\n\n"); + strbuf_addbuf(&msg, &mi.log_message); + strbuf_stripspace(&msg, 0); + + if (state->signoff) + am_signoff(&msg); + + assert(!state->author_name); + state->author_name = strbuf_detach(&author_name, NULL); + + assert(!state->author_email); + state->author_email = strbuf_detach(&author_email, NULL); + + assert(!state->author_date); + state->author_date = strbuf_detach(&author_date, NULL); + + assert(!state->msg); + state->msg = strbuf_detach(&msg, &state->msg_len); + +finish: + strbuf_release(&msg); + strbuf_release(&author_date); + strbuf_release(&author_email); + strbuf_release(&author_name); + strbuf_release(&sb); + clear_mailinfo(&mi); + return ret; +} + +/** + * Sets commit_id to the commit hash where the mail was generated from. + * Returns 0 on success, -1 on failure. + */ +static int get_mail_commit_oid(struct object_id *commit_id, const char *mail) +{ + struct strbuf sb = STRBUF_INIT; + FILE *fp = xfopen(mail, "r"); + const char *x; + + if (strbuf_getline_lf(&sb, fp)) + return -1; + + if (!skip_prefix(sb.buf, "From ", &x)) + return -1; + + if (get_oid_hex(x, commit_id) < 0) + return -1; + + strbuf_release(&sb); + fclose(fp); + return 0; +} + +/** + * Sets state->msg, state->author_name, state->author_email, state->author_date + * to the commit's respective info. + */ +static void get_commit_info(struct am_state *state, struct commit *commit) +{ + const char *buffer, *ident_line, *author_date, *msg; + size_t ident_len; + struct ident_split ident_split; + struct strbuf sb = STRBUF_INIT; + + buffer = logmsg_reencode(commit, NULL, get_commit_output_encoding()); + + ident_line = find_commit_header(buffer, "author", &ident_len); + + if (split_ident_line(&ident_split, ident_line, ident_len) < 0) { + strbuf_add(&sb, ident_line, ident_len); + die(_("invalid ident line: %s"), sb.buf); + } + + assert(!state->author_name); + if (ident_split.name_begin) { + strbuf_add(&sb, ident_split.name_begin, + ident_split.name_end - ident_split.name_begin); + state->author_name = strbuf_detach(&sb, NULL); + } else + state->author_name = xstrdup(""); + + assert(!state->author_email); + if (ident_split.mail_begin) { + strbuf_add(&sb, ident_split.mail_begin, + ident_split.mail_end - ident_split.mail_begin); + state->author_email = strbuf_detach(&sb, NULL); + } else + state->author_email = xstrdup(""); + + author_date = show_ident_date(&ident_split, DATE_MODE(NORMAL)); + strbuf_addstr(&sb, author_date); + assert(!state->author_date); + state->author_date = strbuf_detach(&sb, NULL); + + assert(!state->msg); + msg = strstr(buffer, "\n\n"); + if (!msg) + die(_("unable to parse commit %s"), oid_to_hex(&commit->object.oid)); + state->msg = xstrdup(msg + 2); + state->msg_len = strlen(state->msg); +} + +/** + * Writes `commit` as a patch to the state directory's "patch" file. + */ +static void write_commit_patch(const struct am_state *state, struct commit *commit) +{ + struct rev_info rev_info; + FILE *fp; + + fp = xfopen(am_path(state, "patch"), "w"); + init_revisions(&rev_info, NULL); + rev_info.diff = 1; + rev_info.abbrev = 0; + rev_info.disable_stdin = 1; + rev_info.show_root_diff = 1; + rev_info.diffopt.output_format = DIFF_FORMAT_PATCH; + rev_info.no_commit_id = 1; + DIFF_OPT_SET(&rev_info.diffopt, BINARY); + DIFF_OPT_SET(&rev_info.diffopt, FULL_INDEX); + rev_info.diffopt.use_color = 0; + rev_info.diffopt.file = fp; + rev_info.diffopt.close_file = 1; + add_pending_object(&rev_info, &commit->object, ""); + diff_setup_done(&rev_info.diffopt); + log_tree_commit(&rev_info, commit); +} + +/** + * Writes the diff of the index against HEAD as a patch to the state + * directory's "patch" file. + */ +static void write_index_patch(const struct am_state *state) +{ + struct tree *tree; + struct object_id head; + struct rev_info rev_info; + FILE *fp; + + if (!get_sha1_tree("HEAD", head.hash)) + tree = lookup_tree(head.hash); + else + tree = lookup_tree(EMPTY_TREE_SHA1_BIN); + + fp = xfopen(am_path(state, "patch"), "w"); + init_revisions(&rev_info, NULL); + rev_info.diff = 1; + rev_info.disable_stdin = 1; + rev_info.no_commit_id = 1; + rev_info.diffopt.output_format = DIFF_FORMAT_PATCH; + rev_info.diffopt.use_color = 0; + rev_info.diffopt.file = fp; + rev_info.diffopt.close_file = 1; + add_pending_object(&rev_info, &tree->object, ""); + diff_setup_done(&rev_info.diffopt); + run_diff_index(&rev_info, 1); +} + +/** + * Like parse_mail(), but parses the mail by looking up its commit ID + * directly. This is used in --rebasing mode to bypass git-mailinfo's munging + * of patches. + * + * state->orig_commit will be set to the original commit ID. + * + * Will always return 0 as the patch should never be skipped. + */ +static int parse_mail_rebase(struct am_state *state, const char *mail) +{ + struct commit *commit; + struct object_id commit_oid; + + if (get_mail_commit_oid(&commit_oid, mail) < 0) + die(_("could not parse %s"), mail); + + commit = lookup_commit_or_die(commit_oid.hash, mail); + + get_commit_info(state, commit); + + write_commit_patch(state, commit); + + oidcpy(&state->orig_commit, &commit_oid); + write_state_text(state, "original-commit", oid_to_hex(&commit_oid)); + + return 0; +} + +/** + * Applies current patch with git-apply. Returns 0 on success, -1 otherwise. If + * `index_file` is not NULL, the patch will be applied to that index. + */ +static int run_apply(const struct am_state *state, const char *index_file) +{ + struct argv_array apply_paths = ARGV_ARRAY_INIT; + struct argv_array apply_opts = ARGV_ARRAY_INIT; + struct apply_state apply_state; + int res, opts_left; + static struct lock_file lock_file; + int force_apply = 0; + int options = 0; + + if (init_apply_state(&apply_state, NULL, &lock_file)) + die("BUG: init_apply_state() failed"); + + argv_array_push(&apply_opts, "apply"); + argv_array_pushv(&apply_opts, state->git_apply_opts.argv); + + opts_left = apply_parse_options(apply_opts.argc, apply_opts.argv, + &apply_state, &force_apply, &options, + NULL); + + if (opts_left != 0) + die("unknown option passed through to git apply"); + + if (index_file) { + apply_state.index_file = index_file; + apply_state.cached = 1; + } else + apply_state.check_index = 1; + + /* + * If we are allowed to fall back on 3-way merge, don't give false + * errors during the initial attempt. + */ + if (state->threeway && !index_file) + apply_state.apply_verbosity = verbosity_silent; + + if (check_apply_state(&apply_state, force_apply)) + die("BUG: check_apply_state() failed"); + + argv_array_push(&apply_paths, am_path(state, "patch")); + + res = apply_all_patches(&apply_state, apply_paths.argc, apply_paths.argv, options); + + argv_array_clear(&apply_paths); + argv_array_clear(&apply_opts); + clear_apply_state(&apply_state); + + if (res) + return res; + + if (index_file) { + /* Reload index as apply_all_patches() will have modified it. */ + discard_cache(); + read_cache_from(index_file); + } + + return 0; +} + +/** + * Builds an index that contains just the blobs needed for a 3way merge. + */ +static int build_fake_ancestor(const struct am_state *state, const char *index_file) +{ + struct child_process cp = CHILD_PROCESS_INIT; + + cp.git_cmd = 1; + argv_array_push(&cp.args, "apply"); + argv_array_pushv(&cp.args, state->git_apply_opts.argv); + argv_array_pushf(&cp.args, "--build-fake-ancestor=%s", index_file); + argv_array_push(&cp.args, am_path(state, "patch")); + + if (run_command(&cp)) + return -1; + + return 0; +} + +/** + * Attempt a threeway merge, using index_path as the temporary index. + */ +static int fall_back_threeway(const struct am_state *state, const char *index_path) +{ + struct object_id orig_tree, their_tree, our_tree; + const struct object_id *bases[1] = { &orig_tree }; + struct merge_options o; + struct commit *result; + char *their_tree_name; + + if (get_oid("HEAD", &our_tree) < 0) + hashcpy(our_tree.hash, EMPTY_TREE_SHA1_BIN); + + if (build_fake_ancestor(state, index_path)) + return error("could not build fake ancestor"); + + discard_cache(); + read_cache_from(index_path); + + if (write_index_as_tree(orig_tree.hash, &the_index, index_path, 0, NULL)) + return error(_("Repository lacks necessary blobs to fall back on 3-way merge.")); + + say(state, stdout, _("Using index info to reconstruct a base tree...")); + + if (!state->quiet) { + /* + * List paths that needed 3-way fallback, so that the user can + * review them with extra care to spot mismerges. + */ + struct rev_info rev_info; + const char *diff_filter_str = "--diff-filter=AM"; + + init_revisions(&rev_info, NULL); + rev_info.diffopt.output_format = DIFF_FORMAT_NAME_STATUS; + diff_opt_parse(&rev_info.diffopt, &diff_filter_str, 1, rev_info.prefix); + add_pending_sha1(&rev_info, "HEAD", our_tree.hash, 0); + diff_setup_done(&rev_info.diffopt); + run_diff_index(&rev_info, 1); + } + + if (run_apply(state, index_path)) + return error(_("Did you hand edit your patch?\n" + "It does not apply to blobs recorded in its index.")); + + if (write_index_as_tree(their_tree.hash, &the_index, index_path, 0, NULL)) + return error("could not write tree"); + + say(state, stdout, _("Falling back to patching base and 3-way merge...")); + + discard_cache(); + read_cache(); + + /* + * This is not so wrong. Depending on which base we picked, orig_tree + * may be wildly different from ours, but their_tree has the same set of + * wildly different changes in parts the patch did not touch, so + * recursive ends up canceling them, saying that we reverted all those + * changes. + */ + + init_merge_options(&o); + + o.branch1 = "HEAD"; + their_tree_name = xstrfmt("%.*s", linelen(state->msg), state->msg); + o.branch2 = their_tree_name; + + if (state->quiet) + o.verbosity = 0; + + if (merge_recursive_generic(&o, &our_tree, &their_tree, 1, bases, &result)) { + rerere(state->allow_rerere_autoupdate); + free(their_tree_name); + return error(_("Failed to merge in the changes.")); + } + + free(their_tree_name); + return 0; +} + +/** + * Commits the current index with state->msg as the commit message and + * state->author_name, state->author_email and state->author_date as the author + * information. + */ +static void do_commit(const struct am_state *state) +{ + struct object_id tree, parent, commit; + const struct object_id *old_oid; + struct commit_list *parents = NULL; + const char *reflog_msg, *author; + struct strbuf sb = STRBUF_INIT; + + if (run_hook_le(NULL, "pre-applypatch", NULL)) + exit(1); + + if (write_cache_as_tree(tree.hash, 0, NULL)) + die(_("git write-tree failed to write a tree")); + + if (!get_sha1_commit("HEAD", parent.hash)) { + old_oid = &parent; + commit_list_insert(lookup_commit(parent.hash), &parents); + } else { + old_oid = NULL; + say(state, stderr, _("applying to an empty history")); + } + + author = fmt_ident(state->author_name, state->author_email, + state->ignore_date ? NULL : state->author_date, + IDENT_STRICT); + + if (state->committer_date_is_author_date) + setenv("GIT_COMMITTER_DATE", + state->ignore_date ? "" : state->author_date, 1); + + if (commit_tree(state->msg, state->msg_len, tree.hash, parents, commit.hash, + author, state->sign_commit)) + die(_("failed to write commit object")); + + reflog_msg = getenv("GIT_REFLOG_ACTION"); + if (!reflog_msg) + reflog_msg = "am"; + + strbuf_addf(&sb, "%s: %.*s", reflog_msg, linelen(state->msg), + state->msg); + + update_ref_oid(sb.buf, "HEAD", &commit, old_oid, 0, + UPDATE_REFS_DIE_ON_ERR); + + if (state->rebasing) { + FILE *fp = xfopen(am_path(state, "rewritten"), "a"); + + assert(!is_null_oid(&state->orig_commit)); + fprintf(fp, "%s ", oid_to_hex(&state->orig_commit)); + fprintf(fp, "%s\n", oid_to_hex(&commit)); + fclose(fp); + } + + run_hook_le(NULL, "post-applypatch", NULL); + + strbuf_release(&sb); +} + +/** + * Validates the am_state for resuming -- the "msg" and authorship fields must + * be filled up. + */ +static void validate_resume_state(const struct am_state *state) +{ + if (!state->msg) + die(_("cannot resume: %s does not exist."), + am_path(state, "final-commit")); + + if (!state->author_name || !state->author_email || !state->author_date) + die(_("cannot resume: %s does not exist."), + am_path(state, "author-script")); +} + +/** + * Interactively prompt the user on whether the current patch should be + * applied. + * + * Returns 0 if the user chooses to apply the patch, 1 if the user chooses to + * skip it. + */ +static int do_interactive(struct am_state *state) +{ + assert(state->msg); + + if (!isatty(0)) + die(_("cannot be interactive without stdin connected to a terminal.")); + + for (;;) { + const char *reply; + + puts(_("Commit Body is:")); + puts("--------------------------"); + printf("%s", state->msg); + puts("--------------------------"); + + /* + * TRANSLATORS: Make sure to include [y], [n], [e], [v] and [a] + * in your translation. The program will only accept English + * input at this point. + */ + reply = git_prompt(_("Apply? [y]es/[n]o/[e]dit/[v]iew patch/[a]ccept all: "), PROMPT_ECHO); + + if (!reply) { + continue; + } else if (*reply == 'y' || *reply == 'Y') { + return 0; + } else if (*reply == 'a' || *reply == 'A') { + state->interactive = 0; + return 0; + } else if (*reply == 'n' || *reply == 'N') { + return 1; + } else if (*reply == 'e' || *reply == 'E') { + struct strbuf msg = STRBUF_INIT; + + if (!launch_editor(am_path(state, "final-commit"), &msg, NULL)) { + free(state->msg); + state->msg = strbuf_detach(&msg, &state->msg_len); + } + strbuf_release(&msg); + } else if (*reply == 'v' || *reply == 'V') { + const char *pager = git_pager(1); + struct child_process cp = CHILD_PROCESS_INIT; + + if (!pager) + pager = "cat"; + prepare_pager_args(&cp, pager); + argv_array_push(&cp.args, am_path(state, "patch")); + run_command(&cp); + } + } +} + +/** + * Applies all queued mail. + * + * If `resume` is true, we are "resuming". The "msg" and authorship fields, as + * well as the state directory's "patch" file is used as-is for applying the + * patch and committing it. + */ +static void am_run(struct am_state *state, int resume) +{ + const char *argv_gc_auto[] = {"gc", "--auto", NULL}; + struct strbuf sb = STRBUF_INIT; + + unlink(am_path(state, "dirtyindex")); + + refresh_and_write_cache(); + + if (index_has_changes(&sb)) { + write_state_bool(state, "dirtyindex", 1); + die(_("Dirty index: cannot apply patches (dirty: %s)"), sb.buf); + } + + strbuf_release(&sb); + + while (state->cur <= state->last) { + const char *mail = am_path(state, msgnum(state)); + int apply_status; + + reset_ident_date(); + + if (!file_exists(mail)) + goto next; + + if (resume) { + validate_resume_state(state); + } else { + int skip; + + if (state->rebasing) + skip = parse_mail_rebase(state, mail); + else + skip = parse_mail(state, mail); + + if (skip) + goto next; /* mail should be skipped */ + + write_author_script(state); + write_commit_msg(state); + } + + if (state->interactive && do_interactive(state)) + goto next; + + if (run_applypatch_msg_hook(state)) + exit(1); + + say(state, stdout, _("Applying: %.*s"), linelen(state->msg), state->msg); + + apply_status = run_apply(state, NULL); + + if (apply_status && state->threeway) { + struct strbuf sb = STRBUF_INIT; + + strbuf_addstr(&sb, am_path(state, "patch-merge-index")); + apply_status = fall_back_threeway(state, sb.buf); + strbuf_release(&sb); + + /* + * Applying the patch to an earlier tree and merging + * the result may have produced the same tree as ours. + */ + if (!apply_status && !index_has_changes(NULL)) { + say(state, stdout, _("No changes -- Patch already applied.")); + goto next; + } + } + + if (apply_status) { + int advice_amworkdir = 1; + + printf_ln(_("Patch failed at %s %.*s"), msgnum(state), + linelen(state->msg), state->msg); + + git_config_get_bool("advice.amworkdir", &advice_amworkdir); + + if (advice_amworkdir) + printf_ln(_("The copy of the patch that failed is found in: %s"), + am_path(state, "patch")); + + die_user_resolve(state); + } + + do_commit(state); + +next: + am_next(state); + + if (resume) + am_load(state); + resume = 0; + } + + if (!is_empty_file(am_path(state, "rewritten"))) { + assert(state->rebasing); + copy_notes_for_rebase(state); + run_post_rewrite_hook(state); + } + + /* + * In rebasing mode, it's up to the caller to take care of + * housekeeping. + */ + if (!state->rebasing) { + am_destroy(state); + close_all_packs(); + run_command_v_opt(argv_gc_auto, RUN_GIT_CMD); + } +} + +/** + * Resume the current am session after patch application failure. The user did + * all the hard work, and we do not have to do any patch application. Just + * trust and commit what the user has in the index and working tree. + */ +static void am_resolve(struct am_state *state) +{ + validate_resume_state(state); + + say(state, stdout, _("Applying: %.*s"), linelen(state->msg), state->msg); + + if (!index_has_changes(NULL)) { + printf_ln(_("No changes - did you forget to use 'git add'?\n" + "If there is nothing left to stage, chances are that something else\n" + "already introduced the same changes; you might want to skip this patch.")); + die_user_resolve(state); + } + + if (unmerged_cache()) { + printf_ln(_("You still have unmerged paths in your index.\n" + "Did you forget to use 'git add'?")); + die_user_resolve(state); + } + + if (state->interactive) { + write_index_patch(state); + if (do_interactive(state)) + goto next; + } + + rerere(0); + + do_commit(state); + +next: + am_next(state); + am_load(state); + am_run(state, 0); +} + +/** + * Performs a checkout fast-forward from `head` to `remote`. If `reset` is + * true, any unmerged entries will be discarded. Returns 0 on success, -1 on + * failure. + */ +static int fast_forward_to(struct tree *head, struct tree *remote, int reset) +{ + struct lock_file *lock_file; + struct unpack_trees_options opts; + struct tree_desc t[2]; + + if (parse_tree(head) || parse_tree(remote)) + return -1; + + lock_file = xcalloc(1, sizeof(struct lock_file)); + hold_locked_index(lock_file, 1); + + refresh_cache(REFRESH_QUIET); + + memset(&opts, 0, sizeof(opts)); + opts.head_idx = 1; + opts.src_index = &the_index; + opts.dst_index = &the_index; + opts.update = 1; + opts.merge = 1; + opts.reset = reset; + opts.fn = twoway_merge; + init_tree_desc(&t[0], head->buffer, head->size); + init_tree_desc(&t[1], remote->buffer, remote->size); + + if (unpack_trees(2, t, &opts)) { + rollback_lock_file(lock_file); + return -1; + } + + if (write_locked_index(&the_index, lock_file, COMMIT_LOCK)) + die(_("unable to write new index file")); + + return 0; +} + +/** + * Merges a tree into the index. The index's stat info will take precedence + * over the merged tree's. Returns 0 on success, -1 on failure. + */ +static int merge_tree(struct tree *tree) +{ + struct lock_file *lock_file; + struct unpack_trees_options opts; + struct tree_desc t[1]; + + if (parse_tree(tree)) + return -1; + + lock_file = xcalloc(1, sizeof(struct lock_file)); + hold_locked_index(lock_file, 1); + + memset(&opts, 0, sizeof(opts)); + opts.head_idx = 1; + opts.src_index = &the_index; + opts.dst_index = &the_index; + opts.merge = 1; + opts.fn = oneway_merge; + init_tree_desc(&t[0], tree->buffer, tree->size); + + if (unpack_trees(1, t, &opts)) { + rollback_lock_file(lock_file); + return -1; + } + + if (write_locked_index(&the_index, lock_file, COMMIT_LOCK)) + die(_("unable to write new index file")); + + return 0; +} + +/** + * Clean the index without touching entries that are not modified between + * `head` and `remote`. + */ +static int clean_index(const struct object_id *head, const struct object_id *remote) +{ + struct tree *head_tree, *remote_tree, *index_tree; + struct object_id index; + + head_tree = parse_tree_indirect(head->hash); + if (!head_tree) + return error(_("Could not parse object '%s'."), oid_to_hex(head)); + + remote_tree = parse_tree_indirect(remote->hash); + if (!remote_tree) + return error(_("Could not parse object '%s'."), oid_to_hex(remote)); + + read_cache_unmerged(); + + if (fast_forward_to(head_tree, head_tree, 1)) + return -1; + + if (write_cache_as_tree(index.hash, 0, NULL)) + return -1; + + index_tree = parse_tree_indirect(index.hash); + if (!index_tree) + return error(_("Could not parse object '%s'."), oid_to_hex(&index)); + + if (fast_forward_to(index_tree, remote_tree, 0)) + return -1; + + if (merge_tree(remote_tree)) + return -1; + + remove_branch_state(); + + return 0; +} + +/** + * Resets rerere's merge resolution metadata. + */ +static void am_rerere_clear(void) +{ + struct string_list merge_rr = STRING_LIST_INIT_DUP; + rerere_clear(&merge_rr); + string_list_clear(&merge_rr, 1); +} + +/** + * Resume the current am session by skipping the current patch. + */ +static void am_skip(struct am_state *state) +{ + struct object_id head; + + am_rerere_clear(); + + if (get_oid("HEAD", &head)) + hashcpy(head.hash, EMPTY_TREE_SHA1_BIN); + + if (clean_index(&head, &head)) + die(_("failed to clean index")); + + am_next(state); + am_load(state); + am_run(state, 0); +} + +/** + * Returns true if it is safe to reset HEAD to the ORIG_HEAD, false otherwise. + * + * It is not safe to reset HEAD when: + * 1. git-am previously failed because the index was dirty. + * 2. HEAD has moved since git-am previously failed. + */ +static int safe_to_abort(const struct am_state *state) +{ + struct strbuf sb = STRBUF_INIT; + struct object_id abort_safety, head; + + if (file_exists(am_path(state, "dirtyindex"))) + return 0; + + if (read_state_file(&sb, state, "abort-safety", 1) > 0) { + if (get_oid_hex(sb.buf, &abort_safety)) + die(_("could not parse %s"), am_path(state, "abort-safety")); + } else + oidclr(&abort_safety); + + if (get_oid("HEAD", &head)) + oidclr(&head); + + if (!oidcmp(&head, &abort_safety)) + return 1; + + warning(_("You seem to have moved HEAD since the last 'am' failure.\n" + "Not rewinding to ORIG_HEAD")); + + return 0; +} + +/** + * Aborts the current am session if it is safe to do so. + */ +static void am_abort(struct am_state *state) +{ + struct object_id curr_head, orig_head; + int has_curr_head, has_orig_head; + char *curr_branch; + + if (!safe_to_abort(state)) { + am_destroy(state); + return; + } + + am_rerere_clear(); + + curr_branch = resolve_refdup("HEAD", 0, curr_head.hash, NULL); + has_curr_head = !is_null_oid(&curr_head); + if (!has_curr_head) + hashcpy(curr_head.hash, EMPTY_TREE_SHA1_BIN); + + has_orig_head = !get_oid("ORIG_HEAD", &orig_head); + if (!has_orig_head) + hashcpy(orig_head.hash, EMPTY_TREE_SHA1_BIN); + + clean_index(&curr_head, &orig_head); + + if (has_orig_head) + update_ref_oid("am --abort", "HEAD", &orig_head, + has_curr_head ? &curr_head : NULL, 0, + UPDATE_REFS_DIE_ON_ERR); + else if (curr_branch) + delete_ref(curr_branch, NULL, REF_NODEREF); + + free(curr_branch); + am_destroy(state); +} + +/** + * parse_options() callback that validates and sets opt->value to the + * PATCH_FORMAT_* enum value corresponding to `arg`. + */ +static int parse_opt_patchformat(const struct option *opt, const char *arg, int unset) +{ + int *opt_value = opt->value; + + if (!strcmp(arg, "mbox")) + *opt_value = PATCH_FORMAT_MBOX; + else if (!strcmp(arg, "stgit")) + *opt_value = PATCH_FORMAT_STGIT; + else if (!strcmp(arg, "stgit-series")) + *opt_value = PATCH_FORMAT_STGIT_SERIES; + else if (!strcmp(arg, "hg")) + *opt_value = PATCH_FORMAT_HG; + else if (!strcmp(arg, "mboxrd")) + *opt_value = PATCH_FORMAT_MBOXRD; + else + return error(_("Invalid value for --patch-format: %s"), arg); + return 0; +} + +enum resume_mode { + RESUME_FALSE = 0, + RESUME_APPLY, + RESUME_RESOLVED, + RESUME_SKIP, + RESUME_ABORT +}; + +static int git_am_config(const char *k, const char *v, void *cb) +{ + int status; + + status = git_gpg_config(k, v, NULL); + if (status) + return status; + + return git_default_config(k, v, NULL); +} + +int cmd_am(int argc, const char **argv, const char *prefix) +{ + struct am_state state; + int binary = -1; + int keep_cr = -1; + int patch_format = PATCH_FORMAT_UNKNOWN; + enum resume_mode resume = RESUME_FALSE; + int in_progress; + + const char * const usage[] = { + N_("git am [<options>] [(<mbox> | <Maildir>)...]"), + N_("git am [<options>] (--continue | --skip | --abort)"), + NULL + }; + + struct option options[] = { + OPT_BOOL('i', "interactive", &state.interactive, + N_("run interactively")), + OPT_HIDDEN_BOOL('b', "binary", &binary, + N_("historical option -- no-op")), + OPT_BOOL('3', "3way", &state.threeway, + N_("allow fall back on 3way merging if needed")), + OPT__QUIET(&state.quiet, N_("be quiet")), + OPT_SET_INT('s', "signoff", &state.signoff, + N_("add a Signed-off-by line to the commit message"), + SIGNOFF_EXPLICIT), + OPT_BOOL('u', "utf8", &state.utf8, + N_("recode into utf8 (default)")), + OPT_SET_INT('k', "keep", &state.keep, + N_("pass -k flag to git-mailinfo"), KEEP_TRUE), + OPT_SET_INT(0, "keep-non-patch", &state.keep, + N_("pass -b flag to git-mailinfo"), KEEP_NON_PATCH), + OPT_BOOL('m', "message-id", &state.message_id, + N_("pass -m flag to git-mailinfo")), + { OPTION_SET_INT, 0, "keep-cr", &keep_cr, NULL, + N_("pass --keep-cr flag to git-mailsplit for mbox format"), + PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, 1}, + { OPTION_SET_INT, 0, "no-keep-cr", &keep_cr, NULL, + N_("do not pass --keep-cr flag to git-mailsplit independent of am.keepcr"), + PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, 0}, + OPT_BOOL('c', "scissors", &state.scissors, + N_("strip everything before a scissors line")), + OPT_PASSTHRU_ARGV(0, "whitespace", &state.git_apply_opts, N_("action"), + N_("pass it through git-apply"), + 0), + OPT_PASSTHRU_ARGV(0, "ignore-space-change", &state.git_apply_opts, NULL, + N_("pass it through git-apply"), + PARSE_OPT_NOARG), + OPT_PASSTHRU_ARGV(0, "ignore-whitespace", &state.git_apply_opts, NULL, + N_("pass it through git-apply"), + PARSE_OPT_NOARG), + OPT_PASSTHRU_ARGV(0, "directory", &state.git_apply_opts, N_("root"), + N_("pass it through git-apply"), + 0), + OPT_PASSTHRU_ARGV(0, "exclude", &state.git_apply_opts, N_("path"), + N_("pass it through git-apply"), + 0), + OPT_PASSTHRU_ARGV(0, "include", &state.git_apply_opts, N_("path"), + N_("pass it through git-apply"), + 0), + OPT_PASSTHRU_ARGV('C', NULL, &state.git_apply_opts, N_("n"), + N_("pass it through git-apply"), + 0), + OPT_PASSTHRU_ARGV('p', NULL, &state.git_apply_opts, N_("num"), + N_("pass it through git-apply"), + 0), + OPT_CALLBACK(0, "patch-format", &patch_format, N_("format"), + N_("format the patch(es) are in"), + parse_opt_patchformat), + OPT_PASSTHRU_ARGV(0, "reject", &state.git_apply_opts, NULL, + N_("pass it through git-apply"), + PARSE_OPT_NOARG), + OPT_STRING(0, "resolvemsg", &state.resolvemsg, NULL, + N_("override error message when patch failure occurs")), + OPT_CMDMODE(0, "continue", &resume, + N_("continue applying patches after resolving a conflict"), + RESUME_RESOLVED), + OPT_CMDMODE('r', "resolved", &resume, + N_("synonyms for --continue"), + RESUME_RESOLVED), + OPT_CMDMODE(0, "skip", &resume, + N_("skip the current patch"), + RESUME_SKIP), + OPT_CMDMODE(0, "abort", &resume, + N_("restore the original branch and abort the patching operation."), + RESUME_ABORT), + OPT_BOOL(0, "committer-date-is-author-date", + &state.committer_date_is_author_date, + N_("lie about committer date")), + OPT_BOOL(0, "ignore-date", &state.ignore_date, + N_("use current timestamp for author date")), + OPT_RERERE_AUTOUPDATE(&state.allow_rerere_autoupdate), + { OPTION_STRING, 'S', "gpg-sign", &state.sign_commit, N_("key-id"), + N_("GPG-sign commits"), + PARSE_OPT_OPTARG, NULL, (intptr_t) "" }, + OPT_HIDDEN_BOOL(0, "rebasing", &state.rebasing, + N_("(internal use for git-rebase)")), + OPT_END() + }; + + git_config(git_am_config, NULL); + + am_state_init(&state, git_path("rebase-apply")); + + in_progress = am_in_progress(&state); + if (in_progress) + am_load(&state); + + argc = parse_options(argc, argv, prefix, options, usage, 0); + + if (binary >= 0) + fprintf_ln(stderr, _("The -b/--binary option has been a no-op for long time, and\n" + "it will be removed. Please do not use it anymore.")); + + /* Ensure a valid committer ident can be constructed */ + git_committer_info(IDENT_STRICT); + + if (read_index_preload(&the_index, NULL) < 0) + die(_("failed to read the index")); + + if (in_progress) { + /* + * Catch user error to feed us patches when there is a session + * in progress: + * + * 1. mbox path(s) are provided on the command-line. + * 2. stdin is not a tty: the user is trying to feed us a patch + * from standard input. This is somewhat unreliable -- stdin + * could be /dev/null for example and the caller did not + * intend to feed us a patch but wanted to continue + * unattended. + */ + if (argc || (resume == RESUME_FALSE && !isatty(0))) + die(_("previous rebase directory %s still exists but mbox given."), + state.dir); + + if (resume == RESUME_FALSE) + resume = RESUME_APPLY; + + if (state.signoff == SIGNOFF_EXPLICIT) + am_append_signoff(&state); + } else { + struct argv_array paths = ARGV_ARRAY_INIT; + int i; + + /* + * Handle stray state directory in the independent-run case. In + * the --rebasing case, it is up to the caller to take care of + * stray directories. + */ + if (file_exists(state.dir) && !state.rebasing) { + if (resume == RESUME_ABORT) { + am_destroy(&state); + am_state_release(&state); + return 0; + } + + die(_("Stray %s directory found.\n" + "Use \"git am --abort\" to remove it."), + state.dir); + } + + if (resume) + die(_("Resolve operation not in progress, we are not resuming.")); + + for (i = 0; i < argc; i++) { + if (is_absolute_path(argv[i]) || !prefix) + argv_array_push(&paths, argv[i]); + else + argv_array_push(&paths, mkpath("%s/%s", prefix, argv[i])); + } + + am_setup(&state, patch_format, paths.argv, keep_cr); + + argv_array_clear(&paths); + } + + switch (resume) { + case RESUME_FALSE: + am_run(&state, 0); + break; + case RESUME_APPLY: + am_run(&state, 1); + break; + case RESUME_RESOLVED: + am_resolve(&state); + break; + case RESUME_SKIP: + am_skip(&state); + break; + case RESUME_ABORT: + am_abort(&state); + break; + default: + die("BUG: invalid resume value"); + } + + am_state_release(&state); + + return 0; +} diff --git a/builtin/apply.c b/builtin/apply.c index 146be97a1a..81b9a61c37 100644 --- a/builtin/apply.c +++ b/builtin/apply.c @@ -1,4679 +1,36 @@ -/* - * apply.c - * - * Copyright (C) Linus Torvalds, 2005 - * - * This applies patches on top of some (arbitrary) version of the SCM. - * - */ #include "cache.h" -#include "lockfile.h" -#include "cache-tree.h" -#include "quote.h" -#include "blob.h" -#include "delta.h" #include "builtin.h" -#include "string-list.h" -#include "dir.h" -#include "diff.h" #include "parse-options.h" -#include "xdiff-interface.h" -#include "ll-merge.h" -#include "rerere.h" - -/* - * --check turns on checking that the working tree matches the - * files that are being modified, but doesn't apply the patch - * --stat does just a diffstat, and doesn't actually apply - * --numstat does numeric diffstat, and doesn't actually apply - * --index-info shows the old and new index info for paths if available. - * --index updates the cache as well. - * --cached updates only the cache without ever touching the working tree. - */ -static const char *prefix; -static int prefix_length = -1; -static int newfd = -1; +#include "lockfile.h" +#include "apply.h" -static int unidiff_zero; -static int p_value = 1; -static int p_value_known; -static int check_index; -static int update_index; -static int cached; -static int diffstat; -static int numstat; -static int summary; -static int check; -static int apply = 1; -static int apply_in_reverse; -static int apply_with_reject; -static int apply_verbosely; -static int allow_overlap; -static int no_add; -static int threeway; -static int unsafe_paths; -static const char *fake_ancestor; -static int line_termination = '\n'; -static unsigned int p_context = UINT_MAX; static const char * const apply_usage[] = { N_("git apply [<options>] [<patch>...]"), NULL }; -static enum ws_error_action { - nowarn_ws_error, - warn_on_ws_error, - die_on_ws_error, - correct_ws_error -} ws_error_action = warn_on_ws_error; -static int whitespace_error; -static int squelch_whitespace_errors = 5; -static int applied_after_fixing_ws; - -static enum ws_ignore { - ignore_ws_none, - ignore_ws_change -} ws_ignore_action = ignore_ws_none; - - -static const char *patch_input_file; -static const char *root; -static int root_len; -static int read_stdin = 1; -static int options; - -static void parse_whitespace_option(const char *option) -{ - if (!option) { - ws_error_action = warn_on_ws_error; - return; - } - if (!strcmp(option, "warn")) { - ws_error_action = warn_on_ws_error; - return; - } - if (!strcmp(option, "nowarn")) { - ws_error_action = nowarn_ws_error; - return; - } - if (!strcmp(option, "error")) { - ws_error_action = die_on_ws_error; - return; - } - if (!strcmp(option, "error-all")) { - ws_error_action = die_on_ws_error; - squelch_whitespace_errors = 0; - return; - } - if (!strcmp(option, "strip") || !strcmp(option, "fix")) { - ws_error_action = correct_ws_error; - return; - } - die(_("unrecognized whitespace option '%s'"), option); -} - -static void parse_ignorewhitespace_option(const char *option) -{ - if (!option || !strcmp(option, "no") || - !strcmp(option, "false") || !strcmp(option, "never") || - !strcmp(option, "none")) { - ws_ignore_action = ignore_ws_none; - return; - } - if (!strcmp(option, "change")) { - ws_ignore_action = ignore_ws_change; - return; - } - die(_("unrecognized whitespace ignore option '%s'"), option); -} - -static void set_default_whitespace_mode(const char *whitespace_option) -{ - if (!whitespace_option && !apply_default_whitespace) - ws_error_action = (apply ? warn_on_ws_error : nowarn_ws_error); -} - -/* - * For "diff-stat" like behaviour, we keep track of the biggest change - * we've seen, and the longest filename. That allows us to do simple - * scaling. - */ -static int max_change, max_len; - -/* - * Various "current state", notably line numbers and what - * file (and how) we're patching right now.. The "is_xxxx" - * things are flags, where -1 means "don't know yet". - */ -static int linenr = 1; - -/* - * This represents one "hunk" from a patch, starting with - * "@@ -oldpos,oldlines +newpos,newlines @@" marker. The - * patch text is pointed at by patch, and its byte length - * is stored in size. leading and trailing are the number - * of context lines. - */ -struct fragment { - unsigned long leading, trailing; - unsigned long oldpos, oldlines; - unsigned long newpos, newlines; - /* - * 'patch' is usually borrowed from buf in apply_patch(), - * but some codepaths store an allocated buffer. - */ - const char *patch; - unsigned free_patch:1, - rejected:1; - int size; - int linenr; - struct fragment *next; -}; - -/* - * When dealing with a binary patch, we reuse "leading" field - * to store the type of the binary hunk, either deflated "delta" - * or deflated "literal". - */ -#define binary_patch_method leading -#define BINARY_DELTA_DEFLATED 1 -#define BINARY_LITERAL_DEFLATED 2 - -/* - * This represents a "patch" to a file, both metainfo changes - * such as creation/deletion, filemode and content changes represented - * as a series of fragments. - */ -struct patch { - char *new_name, *old_name, *def_name; - unsigned int old_mode, new_mode; - int is_new, is_delete; /* -1 = unknown, 0 = false, 1 = true */ - int rejected; - unsigned ws_rule; - int lines_added, lines_deleted; - int score; - unsigned int is_toplevel_relative:1; - unsigned int inaccurate_eof:1; - unsigned int is_binary:1; - unsigned int is_copy:1; - unsigned int is_rename:1; - unsigned int recount:1; - unsigned int conflicted_threeway:1; - unsigned int direct_to_threeway:1; - struct fragment *fragments; - char *result; - size_t resultsize; - char old_sha1_prefix[41]; - char new_sha1_prefix[41]; - struct patch *next; - - /* three-way fallback result */ - struct object_id threeway_stage[3]; -}; - -static void free_fragment_list(struct fragment *list) -{ - while (list) { - struct fragment *next = list->next; - if (list->free_patch) - free((char *)list->patch); - free(list); - list = next; - } -} - -static void free_patch(struct patch *patch) -{ - free_fragment_list(patch->fragments); - free(patch->def_name); - free(patch->old_name); - free(patch->new_name); - free(patch->result); - free(patch); -} - -static void free_patch_list(struct patch *list) -{ - while (list) { - struct patch *next = list->next; - free_patch(list); - list = next; - } -} - -/* - * A line in a file, len-bytes long (includes the terminating LF, - * except for an incomplete line at the end if the file ends with - * one), and its contents hashes to 'hash'. - */ -struct line { - size_t len; - unsigned hash : 24; - unsigned flag : 8; -#define LINE_COMMON 1 -#define LINE_PATCHED 2 -}; - -/* - * This represents a "file", which is an array of "lines". - */ -struct image { - char *buf; - size_t len; - size_t nr; - size_t alloc; - struct line *line_allocated; - struct line *line; -}; - -/* - * Records filenames that have been touched, in order to handle - * the case where more than one patches touch the same file. - */ - -static struct string_list fn_table; - -static uint32_t hash_line(const char *cp, size_t len) -{ - size_t i; - uint32_t h; - for (i = 0, h = 0; i < len; i++) { - if (!isspace(cp[i])) { - h = h * 3 + (cp[i] & 0xff); - } - } - return h; -} - -/* - * Compare lines s1 of length n1 and s2 of length n2, ignoring - * whitespace difference. Returns 1 if they match, 0 otherwise - */ -static int fuzzy_matchlines(const char *s1, size_t n1, - const char *s2, size_t n2) -{ - const char *last1 = s1 + n1 - 1; - const char *last2 = s2 + n2 - 1; - int result = 0; - - /* ignore line endings */ - while ((*last1 == '\r') || (*last1 == '\n')) - last1--; - while ((*last2 == '\r') || (*last2 == '\n')) - last2--; - - /* skip leading whitespaces, if both begin with whitespace */ - if (s1 <= last1 && s2 <= last2 && isspace(*s1) && isspace(*s2)) { - while (isspace(*s1) && (s1 <= last1)) - s1++; - while (isspace(*s2) && (s2 <= last2)) - s2++; - } - /* early return if both lines are empty */ - if ((s1 > last1) && (s2 > last2)) - return 1; - while (!result) { - result = *s1++ - *s2++; - /* - * Skip whitespace inside. We check for whitespace on - * both buffers because we don't want "a b" to match - * "ab" - */ - if (isspace(*s1) && isspace(*s2)) { - while (isspace(*s1) && s1 <= last1) - s1++; - while (isspace(*s2) && s2 <= last2) - s2++; - } - /* - * If we reached the end on one side only, - * lines don't match - */ - if ( - ((s2 > last2) && (s1 <= last1)) || - ((s1 > last1) && (s2 <= last2))) - return 0; - if ((s1 > last1) && (s2 > last2)) - break; - } - - return !result; -} - -static void add_line_info(struct image *img, const char *bol, size_t len, unsigned flag) -{ - ALLOC_GROW(img->line_allocated, img->nr + 1, img->alloc); - img->line_allocated[img->nr].len = len; - img->line_allocated[img->nr].hash = hash_line(bol, len); - img->line_allocated[img->nr].flag = flag; - img->nr++; -} - -/* - * "buf" has the file contents to be patched (read from various sources). - * attach it to "image" and add line-based index to it. - * "image" now owns the "buf". - */ -static void prepare_image(struct image *image, char *buf, size_t len, - int prepare_linetable) -{ - const char *cp, *ep; - - memset(image, 0, sizeof(*image)); - image->buf = buf; - image->len = len; - - if (!prepare_linetable) - return; - - ep = image->buf + image->len; - cp = image->buf; - while (cp < ep) { - const char *next; - for (next = cp; next < ep && *next != '\n'; next++) - ; - if (next < ep) - next++; - add_line_info(image, cp, next - cp, 0); - cp = next; - } - image->line = image->line_allocated; -} - -static void clear_image(struct image *image) -{ - free(image->buf); - free(image->line_allocated); - memset(image, 0, sizeof(*image)); -} - -/* fmt must contain _one_ %s and no other substitution */ -static void say_patch_name(FILE *output, const char *fmt, struct patch *patch) -{ - struct strbuf sb = STRBUF_INIT; - - if (patch->old_name && patch->new_name && - strcmp(patch->old_name, patch->new_name)) { - quote_c_style(patch->old_name, &sb, NULL, 0); - strbuf_addstr(&sb, " => "); - quote_c_style(patch->new_name, &sb, NULL, 0); - } else { - const char *n = patch->new_name; - if (!n) - n = patch->old_name; - quote_c_style(n, &sb, NULL, 0); - } - fprintf(output, fmt, sb.buf); - fputc('\n', output); - strbuf_release(&sb); -} - -#define SLOP (16) - -static void read_patch_file(struct strbuf *sb, int fd) -{ - if (strbuf_read(sb, fd, 0) < 0) - die_errno("git apply: failed to read"); - - /* - * Make sure that we have some slop in the buffer - * so that we can do speculative "memcmp" etc, and - * see to it that it is NUL-filled. - */ - strbuf_grow(sb, SLOP); - memset(sb->buf + sb->len, 0, SLOP); -} - -static unsigned long linelen(const char *buffer, unsigned long size) -{ - unsigned long len = 0; - while (size--) { - len++; - if (*buffer++ == '\n') - break; - } - return len; -} - -static int is_dev_null(const char *str) -{ - return skip_prefix(str, "/dev/null", &str) && isspace(*str); -} - -#define TERM_SPACE 1 -#define TERM_TAB 2 - -static int name_terminate(const char *name, int namelen, int c, int terminate) -{ - if (c == ' ' && !(terminate & TERM_SPACE)) - return 0; - if (c == '\t' && !(terminate & TERM_TAB)) - return 0; - - return 1; -} - -/* remove double slashes to make --index work with such filenames */ -static char *squash_slash(char *name) -{ - int i = 0, j = 0; - - if (!name) - return NULL; - - while (name[i]) { - if ((name[j++] = name[i++]) == '/') - while (name[i] == '/') - i++; - } - name[j] = '\0'; - return name; -} - -static char *find_name_gnu(const char *line, const char *def, int p_value) -{ - struct strbuf name = STRBUF_INIT; - char *cp; - - /* - * Proposed "new-style" GNU patch/diff format; see - * http://marc.info/?l=git&m=112927316408690&w=2 - */ - if (unquote_c_style(&name, line, NULL)) { - strbuf_release(&name); - return NULL; - } - - for (cp = name.buf; p_value; p_value--) { - cp = strchr(cp, '/'); - if (!cp) { - strbuf_release(&name); - return NULL; - } - cp++; - } - - strbuf_remove(&name, 0, cp - name.buf); - if (root) - strbuf_insert(&name, 0, root, root_len); - return squash_slash(strbuf_detach(&name, NULL)); -} - -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); - } - - /* 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, const 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 (!end && isspace(c)) { - if (c == '\n') - break; - if (name_terminate(start, line-start, c, terminate)) - break; - } - line++; - if (c == '/' && !--p_value) - start = line; - } - if (!start) - return squash_slash(xstrdup_or_null(def)); - len = line - start; - if (!len) - return squash_slash(xstrdup_or_null(def)); - - /* - * Generally we prefer the shorter name, especially - * if the other one is just a variation of that with - * something else tacked on to the end (ie "file.orig" - * or "file~"). - */ - if (def) { - int deflen = strlen(def); - if (deflen < len && !strncmp(start, def, deflen)) - return squash_slash(xstrdup(def)); - } - - if (root) { - char *ret = xmalloc(root_len + len + 1); - strcpy(ret, root); - memcpy(ret + root_len, start, len); - ret[root_len + len] = '\0'; - return squash_slash(ret); - } - - return squash_slash(xmemdupz(start, len)); -} - -static 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; - 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; - char ch; - - while ((ch = *cp++)) - if (ch == '/') - cnt++; - return cnt; -} - -/* - * Given the string after "--- " or "+++ ", guess the appropriate - * p_value for the given patch. - */ -static int guess_p_value(const char *nameline) -{ - char *name, *cp; - int val = -1; - - if (is_dev_null(nameline)) - return -1; - name = find_name_traditional(nameline, NULL, 0); - if (!name) - return -1; - cp = strchr(name, '/'); - if (!cp) - val = 0; - else if (prefix) { - /* - * Does it begin with "a/$our-prefix" and such? Then this is - * very likely to apply to our directory. - */ - if (!strncmp(name, prefix, prefix_length)) - val = count_slashes(prefix); - else { - cp++; - if (!strncmp(cp, prefix, prefix_length)) - val = count_slashes(prefix) + 1; - } - } - free(name); - return val; -} - -/* - * Does the ---/+++ line has the POSIX timestamp after the last HT? - * GNU diff puts epoch there to signal a creation/deletion event. Is - * this such a timestamp? - */ -static int has_epoch_timestamp(const char *nameline) -{ - /* - * We are only interested in epoch timestamp; any non-zero - * fraction cannot be one, hence "(\.0+)?" in the regexp below. - * For the same reason, the date must be either 1969-12-31 or - * 1970-01-01, and the seconds part must be "00". - */ - const char stamp_regexp[] = - "^(1969-12-31|1970-01-01)" - " " - "[0-2][0-9]:[0-5][0-9]:00(\\.0+)?" - " " - "([-+][0-2][0-9]:?[0-5][0-9])\n"; - const char *timestamp = NULL, *cp, *colon; - static regex_t *stamp; - regmatch_t m[10]; - int zoneoffset; - int hourminute; - int status; - - for (cp = nameline; *cp != '\n'; cp++) { - if (*cp == '\t') - timestamp = cp + 1; - } - if (!timestamp) - return 0; - if (!stamp) { - stamp = xmalloc(sizeof(*stamp)); - if (regcomp(stamp, stamp_regexp, REG_EXTENDED)) { - warning(_("Cannot prepare timestamp regexp %s"), - stamp_regexp); - return 0; - } - } - - status = regexec(stamp, timestamp, ARRAY_SIZE(m), m, 0); - if (status) { - if (status != REG_NOMATCH) - warning(_("regexec returned %d for input: %s"), - status, timestamp); - return 0; - } - - zoneoffset = strtol(timestamp + m[3].rm_so + 1, (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; - - /* - * YYYY-MM-DD hh:mm:ss must be from either 1969-12-31 - * (west of GMT) or 1970-01-01 (east of GMT) - */ - if ((zoneoffset < 0 && memcmp(timestamp, "1969-12-31", 10)) || - (0 <= zoneoffset && memcmp(timestamp, "1970-01-01", 10))) - return 0; - - hourminute = (strtol(timestamp + 11, NULL, 10) * 60 + - strtol(timestamp + 14, NULL, 10) - - zoneoffset); - - return ((zoneoffset < 0 && hourminute == 1440) || - (0 <= zoneoffset && !hourminute)); -} - -/* - * Get the name etc info from the ---/+++ lines of a traditional patch header - * - * FIXME! The end-of-filename heuristics are kind of screwy. For existing - * files, we can happily check the index for a match, but for creating a - * new file we should try to match whatever "patch" does. I have no idea. - */ -static void parse_traditional_patch(const char *first, const char *second, struct patch *patch) -{ - char *name; - - first += 4; /* skip "--- " */ - second += 4; /* skip "+++ " */ - if (!p_value_known) { - int p, q; - p = guess_p_value(first); - q = guess_p_value(second); - if (p < 0) p = q; - if (0 <= p && p == q) { - p_value = p; - p_value_known = 1; - } - } - if (is_dev_null(first)) { - patch->is_new = 1; - patch->is_delete = 0; - name = find_name_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_traditional(first, NULL, p_value); - patch->old_name = name; - } else { - char *first_name; - first_name = find_name_traditional(first, NULL, p_value); - name = find_name_traditional(second, first_name, p_value); - free(first_name); - if (has_epoch_timestamp(first)) { - patch->is_new = 1; - patch->is_delete = 0; - patch->new_name = name; - } else if (has_epoch_timestamp(second)) { - patch->is_new = 0; - patch->is_delete = 1; - patch->old_name = name; - } else { - patch->old_name = name; - patch->new_name = xstrdup_or_null(name); - } - } - if (!name) - die(_("unable to find filename in patch at line %d"), linenr); -} - -static int gitdiff_hdrend(const char *line, struct patch *patch) -{ - return -1; -} - -/* - * We're anal about diff header consistency, to make - * sure that we don't end up having strange ambiguous - * patches floating around. - * - * As a result, gitdiff_{old|new}name() will check - * their names against any previous information, just - * to make sure.. - */ -#define DIFF_OLD_NAME 0 -#define DIFF_NEW_NAME 1 - -static char *gitdiff_verify_name(const char *line, int isnull, char *orig_name, int side) -{ - if (!orig_name && !isnull) - return find_name(line, NULL, p_value, TERM_TAB); - - if (orig_name) { - int len; - const char *name; - char *another; - name = orig_name; - len = strlen(name); - if (isnull) - die(_("git apply: bad git-diff - expected /dev/null, got %s on line %d"), name, linenr); - another = find_name(line, NULL, p_value, TERM_TAB); - if (!another || memcmp(another, name, len + 1)) - die((side == DIFF_NEW_NAME) ? - _("git apply: bad git-diff - inconsistent new filename on line %d") : - _("git apply: bad git-diff - inconsistent old filename on line %d"), linenr); - free(another); - return orig_name; - } - else { - /* expect "/dev/null" */ - if (memcmp("/dev/null", line, 9) || line[9] != '\n') - die(_("git apply: bad git-diff - expected /dev/null on line %d"), linenr); - return NULL; - } -} - -static int gitdiff_oldname(const char *line, struct patch *patch) -{ - char *orig = patch->old_name; - patch->old_name = gitdiff_verify_name(line, patch->is_new, patch->old_name, - DIFF_OLD_NAME); - if (orig != patch->old_name) - free(orig); - return 0; -} - -static int gitdiff_newname(const char *line, struct patch *patch) -{ - char *orig = patch->new_name; - patch->new_name = gitdiff_verify_name(line, patch->is_delete, patch->new_name, - DIFF_NEW_NAME); - if (orig != patch->new_name) - free(orig); - return 0; -} - -static int gitdiff_oldmode(const char *line, struct patch *patch) -{ - patch->old_mode = strtoul(line, NULL, 8); - return 0; -} - -static int gitdiff_newmode(const char *line, struct patch *patch) -{ - patch->new_mode = strtoul(line, NULL, 8); - return 0; -} - -static int gitdiff_delete(const char *line, struct patch *patch) -{ - patch->is_delete = 1; - free(patch->old_name); - patch->old_name = xstrdup_or_null(patch->def_name); - return gitdiff_oldmode(line, patch); -} - -static int gitdiff_newfile(const char *line, struct patch *patch) -{ - patch->is_new = 1; - free(patch->new_name); - patch->new_name = xstrdup_or_null(patch->def_name); - return gitdiff_newmode(line, patch); -} - -static int gitdiff_copysrc(const char *line, struct patch *patch) -{ - patch->is_copy = 1; - free(patch->old_name); - patch->old_name = find_name(line, NULL, p_value ? p_value - 1 : 0, 0); - return 0; -} - -static int gitdiff_copydst(const char *line, struct patch *patch) -{ - patch->is_copy = 1; - free(patch->new_name); - patch->new_name = find_name(line, NULL, p_value ? p_value - 1 : 0, 0); - return 0; -} - -static int gitdiff_renamesrc(const char *line, struct patch *patch) -{ - patch->is_rename = 1; - free(patch->old_name); - patch->old_name = find_name(line, NULL, p_value ? p_value - 1 : 0, 0); - return 0; -} - -static int gitdiff_renamedst(const char *line, struct patch *patch) -{ - patch->is_rename = 1; - free(patch->new_name); - patch->new_name = find_name(line, NULL, p_value ? p_value - 1 : 0, 0); - return 0; -} - -static int gitdiff_similarity(const char *line, struct patch *patch) -{ - unsigned long val = strtoul(line, NULL, 10); - if (val <= 100) - patch->score = val; - return 0; -} - -static int gitdiff_dissimilarity(const char *line, struct patch *patch) -{ - unsigned long val = strtoul(line, NULL, 10); - if (val <= 100) - patch->score = val; - return 0; -} - -static int gitdiff_index(const char *line, struct patch *patch) -{ - /* - * index line is N hexadecimal, "..", N hexadecimal, - * and optional space with octal mode. - */ - const char *ptr, *eol; - int len; - - ptr = strchr(line, '.'); - if (!ptr || ptr[1] != '.' || 40 < ptr - line) - return 0; - len = ptr - line; - memcpy(patch->old_sha1_prefix, line, len); - patch->old_sha1_prefix[len] = 0; - - line = ptr + 2; - ptr = strchr(line, ' '); - eol = strchrnul(line, '\n'); - - if (!ptr || eol < ptr) - ptr = eol; - len = ptr - line; - - if (40 < len) - return 0; - memcpy(patch->new_sha1_prefix, line, len); - patch->new_sha1_prefix[len] = 0; - if (*ptr == ' ') - patch->old_mode = strtoul(ptr+1, NULL, 8); - return 0; -} - -/* - * This is normal for a diff that doesn't change anything: we'll fall through - * into the next diff. Tell the parser to break out. - */ -static int gitdiff_unrecognized(const char *line, struct patch *patch) -{ - return -1; -} - -/* - * Skip p_value leading components from "line"; as we do not accept - * absolute paths, return NULL in that case. - */ -static const char *skip_tree_prefix(const char *line, int llen) -{ - int nslash; - int i; - - if (!p_value) - return (llen && line[0] == '/') ? NULL : line; - - nslash = p_value; - for (i = 0; i < llen; i++) { - int ch = line[i]; - if (ch == '/' && --nslash <= 0) - return (i == 0) ? NULL : &line[i + 1]; - } - return NULL; -} - -/* - * This is to extract the same name that appears on "diff --git" - * line. We do not find and return anything if it is a rename - * patch, and it is OK because we will find the name elsewhere. - * We need to reliably find name only when it is mode-change only, - * creation or deletion of an empty file. In any of these cases, - * both sides are the same name under a/ and b/ respectively. - */ -static char *git_header_name(const char *line, int llen) -{ - const char *name; - const char *second = NULL; - size_t len, line_len; - - line += strlen("diff --git "); - llen -= strlen("diff --git "); - - if (*line == '"') { - const char *cp; - struct strbuf first = STRBUF_INIT; - struct strbuf sp = STRBUF_INIT; - - if (unquote_c_style(&first, line, &second)) - goto free_and_fail1; - - /* strip the a/b prefix including trailing slash */ - cp = skip_tree_prefix(first.buf, first.len); - if (!cp) - goto free_and_fail1; - strbuf_remove(&first, 0, cp - first.buf); - - /* - * second points at one past closing dq of name. - * find the second name. - */ - while ((second < line + llen) && isspace(*second)) - second++; - - if (line + llen <= second) - goto free_and_fail1; - if (*second == '"') { - if (unquote_c_style(&sp, second, NULL)) - goto free_and_fail1; - cp = skip_tree_prefix(sp.buf, sp.len); - if (!cp) - goto free_and_fail1; - /* They must match, otherwise ignore */ - if (strcmp(cp, first.buf)) - goto free_and_fail1; - strbuf_release(&sp); - return strbuf_detach(&first, NULL); - } - - /* unquoted second */ - cp = skip_tree_prefix(second, line + llen - second); - if (!cp) - goto free_and_fail1; - if (line + llen - cp != first.len || - memcmp(first.buf, cp, first.len)) - goto free_and_fail1; - return strbuf_detach(&first, NULL); - - free_and_fail1: - strbuf_release(&first); - strbuf_release(&sp); - return NULL; - } - - /* unquoted first name */ - name = skip_tree_prefix(line, llen); - if (!name) - return NULL; - - /* - * since the first name is unquoted, a dq if exists must be - * the beginning of the second name. - */ - for (second = name; second < line + llen; second++) { - if (*second == '"') { - struct strbuf sp = STRBUF_INIT; - const char *np; - - if (unquote_c_style(&sp, second, NULL)) - goto free_and_fail2; - - np = skip_tree_prefix(sp.buf, sp.len); - if (!np) - goto free_and_fail2; - - len = sp.buf + sp.len - np; - if (len < second - name && - !strncmp(np, name, len) && - isspace(name[len])) { - /* Good */ - strbuf_remove(&sp, 0, np - sp.buf); - return strbuf_detach(&sp, NULL); - } - - free_and_fail2: - strbuf_release(&sp); - return NULL; - } - } - - /* - * Accept a name only if it shows up twice, exactly the same - * form. - */ - second = strchr(name, '\n'); - if (!second) - return NULL; - line_len = second - name; - for (len = 0 ; ; len++) { - switch (name[len]) { - default: - continue; - case '\n': - return NULL; - case '\t': case ' ': - /* - * Is this the separator between the preimage - * and the postimage pathname? Again, we are - * only interested in the case where there is - * no rename, as this is only to set def_name - * and a rename patch has the names elsewhere - * in an unambiguous form. - */ - if (!name[len + 1]) - return NULL; /* no postimage name */ - second = skip_tree_prefix(name + len + 1, - line_len - (len + 1)); - if (!second) - return NULL; - /* - * Does len bytes starting at "name" and "second" - * (that are separated by one HT or SP we just - * found) exactly match? - */ - if (second[len] == '\n' && !strncmp(name, second, len)) - return xmemdupz(name, len); - } - } -} - -/* Verify that we recognize the lines following a git header */ -static int parse_git_header(const char *line, int len, unsigned int size, struct patch *patch) -{ - unsigned long offset; - - /* A git diff has explicit new/delete information, so we don't guess */ - patch->is_new = 0; - patch->is_delete = 0; - - /* - * Some things may not have the old name in the - * rest of the headers anywhere (pure mode changes, - * or removing or adding empty files), so we get - * the default name from the header. - */ - patch->def_name = git_header_name(line, len); - if (patch->def_name && root) { - char *s = xstrfmt("%s%s", root, patch->def_name); - free(patch->def_name); - patch->def_name = s; - } - - line += len; - size -= len; - linenr++; - for (offset = len ; size > 0 ; offset += len, size -= len, line += len, linenr++) { - static const struct opentry { - const char *str; - int (*fn)(const char *, struct patch *); - } optable[] = { - { "@@ -", gitdiff_hdrend }, - { "--- ", gitdiff_oldname }, - { "+++ ", gitdiff_newname }, - { "old mode ", gitdiff_oldmode }, - { "new mode ", gitdiff_newmode }, - { "deleted file mode ", gitdiff_delete }, - { "new file mode ", gitdiff_newfile }, - { "copy from ", gitdiff_copysrc }, - { "copy to ", gitdiff_copydst }, - { "rename old ", gitdiff_renamesrc }, - { "rename new ", gitdiff_renamedst }, - { "rename from ", gitdiff_renamesrc }, - { "rename to ", gitdiff_renamedst }, - { "similarity index ", gitdiff_similarity }, - { "dissimilarity index ", gitdiff_dissimilarity }, - { "index ", gitdiff_index }, - { "", gitdiff_unrecognized }, - }; - int i; - - len = linelen(line, size); - if (!len || line[len-1] != '\n') - break; - for (i = 0; i < ARRAY_SIZE(optable); i++) { - const struct opentry *p = optable + i; - int oplen = strlen(p->str); - if (len < oplen || memcmp(p->str, line, oplen)) - continue; - if (p->fn(line + oplen, patch) < 0) - return offset; - break; - } - } - - return offset; -} - -static int parse_num(const char *line, unsigned long *p) -{ - char *ptr; - - if (!isdigit(*line)) - return 0; - *p = strtoul(line, &ptr, 10); - return ptr - line; -} - -static int parse_range(const char *line, int len, int offset, const char *expect, - unsigned long *p1, unsigned long *p2) -{ - int digits, ex; - - if (offset < 0 || offset >= len) - return -1; - line += offset; - len -= offset; - - digits = parse_num(line, p1); - if (!digits) - return -1; - - offset += digits; - line += digits; - len -= digits; - - *p2 = 1; - if (*line == ',') { - digits = parse_num(line+1, p2); - if (!digits) - return -1; - - offset += digits+1; - line += digits+1; - len -= digits+1; - } - - ex = strlen(expect); - if (ex > len) - return -1; - if (memcmp(line, expect, ex)) - return -1; - - return offset + ex; -} - -static void recount_diff(const char *line, int size, struct fragment *fragment) -{ - int oldlines = 0, newlines = 0, ret = 0; - - if (size < 1) { - warning("recount: ignore empty hunk"); - return; - } - - for (;;) { - int len = linelen(line, size); - size -= len; - line += len; - - if (size < 1) - break; - - switch (*line) { - case ' ': case '\n': - newlines++; - /* fall through */ - case '-': - oldlines++; - continue; - case '+': - newlines++; - continue; - case '\\': - continue; - case '@': - ret = size < 3 || !starts_with(line, "@@ "); - break; - case 'd': - ret = size < 5 || !starts_with(line, "diff "); - break; - default: - ret = -1; - break; - } - if (ret) { - warning(_("recount: unexpected line: %.*s"), - (int)linelen(line, size), line); - return; - } - break; - } - fragment->oldlines = oldlines; - fragment->newlines = newlines; -} - -/* - * Parse a unified diff fragment header of the - * form "@@ -a,b +c,d @@" - */ -static int parse_fragment_header(const char *line, int len, struct fragment *fragment) -{ - int offset; - - if (!len || line[len-1] != '\n') - return -1; - - /* Figure out the number of lines in a fragment */ - offset = parse_range(line, len, 4, " +", &fragment->oldpos, &fragment->oldlines); - offset = parse_range(line, len, offset, " @@", &fragment->newpos, &fragment->newlines); - - return offset; -} - -static int find_header(const char *line, unsigned long size, int *hdrsize, struct patch *patch) -{ - unsigned long offset, len; - - patch->is_toplevel_relative = 0; - patch->is_rename = patch->is_copy = 0; - patch->is_new = patch->is_delete = -1; - patch->old_mode = patch->new_mode = 0; - patch->old_name = patch->new_name = NULL; - for (offset = 0; size > 0; offset += len, size -= len, line += len, linenr++) { - unsigned long nextlen; - - len = linelen(line, size); - if (!len) - break; - - /* Testing this early allows us to take a few shortcuts.. */ - if (len < 6) - continue; - - /* - * Make sure we don't find any unconnected patch fragments. - * That's a sign that we didn't find a header, and that a - * patch has become corrupted/broken up. - */ - if (!memcmp("@@ -", line, 4)) { - struct fragment dummy; - if (parse_fragment_header(line, len, &dummy) < 0) - continue; - die(_("patch fragment without header at line %d: %.*s"), - linenr, (int)len-1, line); - } - - if (size < len + 6) - break; - - /* - * Git patch? It might not have a real patch, just a rename - * or mode change, so we handle that specially - */ - if (!memcmp("diff --git ", line, 11)) { - int git_hdr_len = parse_git_header(line, len, size, patch); - if (git_hdr_len <= len) - continue; - if (!patch->old_name && !patch->new_name) { - if (!patch->def_name) - die(Q_("git diff header lacks filename information when removing " - "%d leading pathname component (line %d)", - "git diff header lacks filename information when removing " - "%d leading pathname components (line %d)", - p_value), - p_value, linenr); - patch->old_name = xstrdup(patch->def_name); - patch->new_name = xstrdup(patch->def_name); - } - if (!patch->is_delete && !patch->new_name) - die("git diff header lacks filename information " - "(line %d)", linenr); - patch->is_toplevel_relative = 1; - *hdrsize = git_hdr_len; - return offset; - } - - /* --- followed by +++ ? */ - if (memcmp("--- ", line, 4) || memcmp("+++ ", line + len, 4)) - continue; - - /* - * We only accept unified patches, so we want it to - * at least have "@@ -a,b +c,d @@\n", which is 14 chars - * minimum ("@@ -0,0 +1 @@\n" is the shortest). - */ - nextlen = linelen(line + len, size - len); - if (size < nextlen + 14 || memcmp("@@ -", line + len + nextlen, 4)) - continue; - - /* Ok, we'll consider it a patch */ - parse_traditional_patch(line, line+len, patch); - *hdrsize = len + nextlen; - linenr += 2; - return offset; - } - return -1; -} - -static void record_ws_error(unsigned result, const char *line, int len, int linenr) -{ - char *err; - - if (!result) - return; - - whitespace_error++; - if (squelch_whitespace_errors && - squelch_whitespace_errors < whitespace_error) - return; - - err = whitespace_error_string(result); - fprintf(stderr, "%s:%d: %s.\n%.*s\n", - patch_input_file, linenr, err, len, line); - free(err); -} - -static void check_whitespace(const char *line, int len, unsigned ws_rule) -{ - unsigned result = ws_check(line + 1, len - 1, ws_rule); - - record_ws_error(result, line + 1, len - 2, linenr); -} - -/* - * Parse a unified diff. Note that this really needs to parse each - * fragment separately, since the only way to know the difference - * between a "---" that is part of a patch, and a "---" that starts - * the next patch is to look at the line counts.. - */ -static int parse_fragment(const char *line, unsigned long size, - struct patch *patch, struct fragment *fragment) -{ - int added, deleted; - int len = linelen(line, size), offset; - unsigned long oldlines, newlines; - unsigned long leading, trailing; - - offset = parse_fragment_header(line, len, fragment); - if (offset < 0) - return -1; - if (offset > 0 && patch->recount) - recount_diff(line + offset, size - offset, fragment); - oldlines = fragment->oldlines; - newlines = fragment->newlines; - leading = 0; - trailing = 0; - - /* Parse the thing.. */ - line += len; - size -= len; - linenr++; - added = deleted = 0; - for (offset = len; - 0 < size; - offset += len, size -= len, line += len, linenr++) { - if (!oldlines && !newlines) - break; - len = linelen(line, size); - if (!len || line[len-1] != '\n') - return -1; - switch (*line) { - default: - return -1; - case '\n': /* newer GNU diff, an empty context line */ - case ' ': - oldlines--; - newlines--; - if (!deleted && !added) - leading++; - trailing++; - if (!apply_in_reverse && - ws_error_action == correct_ws_error) - check_whitespace(line, len, patch->ws_rule); - break; - case '-': - if (apply_in_reverse && - ws_error_action != nowarn_ws_error) - check_whitespace(line, len, patch->ws_rule); - deleted++; - oldlines--; - trailing = 0; - break; - case '+': - if (!apply_in_reverse && - ws_error_action != nowarn_ws_error) - check_whitespace(line, len, patch->ws_rule); - added++; - newlines--; - trailing = 0; - break; - - /* - * We allow "\ No newline at end of file". Depending - * on locale settings when the patch was produced we - * don't know what this line looks like. The only - * thing we do know is that it begins with "\ ". - * Checking for 12 is just for sanity check -- any - * l10n of "\ No newline..." is at least that long. - */ - case '\\': - if (len < 12 || memcmp(line, "\\ ", 2)) - return -1; - break; - } - } - if (oldlines || newlines) - return -1; - fragment->leading = leading; - fragment->trailing = trailing; - - /* - * If a fragment ends with an incomplete line, we failed to include - * it in the above loop because we hit oldlines == newlines == 0 - * before seeing it. - */ - if (12 < size && !memcmp(line, "\\ ", 2)) - offset += linelen(line, size); - - patch->lines_added += added; - patch->lines_deleted += deleted; - - if (0 < patch->is_new && oldlines) - return error(_("new file depends on old contents")); - if (0 < patch->is_delete && newlines) - return error(_("deleted file still has contents")); - return offset; -} - -/* - * We have seen "diff --git a/... b/..." header (or a traditional patch - * header). Read hunks that belong to this patch into fragments and hang - * them to the given patch structure. - * - * The (fragment->patch, fragment->size) pair points into the memory given - * by the caller, not a copy, when we return. - */ -static int parse_single_patch(const char *line, unsigned long size, struct patch *patch) -{ - unsigned long offset = 0; - unsigned long oldlines = 0, newlines = 0, context = 0; - struct fragment **fragp = &patch->fragments; - - while (size > 4 && !memcmp(line, "@@ -", 4)) { - struct fragment *fragment; - int len; - - fragment = xcalloc(1, sizeof(*fragment)); - fragment->linenr = linenr; - len = parse_fragment(line, size, patch, fragment); - if (len <= 0) - die(_("corrupt patch at line %d"), linenr); - fragment->patch = line; - fragment->size = len; - oldlines += fragment->oldlines; - newlines += fragment->newlines; - context += fragment->leading + fragment->trailing; - - *fragp = fragment; - fragp = &fragment->next; - - offset += len; - line += len; - size -= len; - } - - /* - * If something was removed (i.e. we have old-lines) it cannot - * be creation, and if something was added it cannot be - * deletion. However, the reverse is not true; --unified=0 - * patches that only add are not necessarily creation even - * though they do not have any old lines, and ones that only - * delete are not necessarily deletion. - * - * Unfortunately, a real creation/deletion patch do _not_ have - * any context line by definition, so we cannot safely tell it - * apart with --unified=0 insanity. At least if the patch has - * more than one hunk it is not creation or deletion. - */ - if (patch->is_new < 0 && - (oldlines || (patch->fragments && patch->fragments->next))) - patch->is_new = 0; - if (patch->is_delete < 0 && - (newlines || (patch->fragments && patch->fragments->next))) - patch->is_delete = 0; - - if (0 < patch->is_new && oldlines) - die(_("new file %s depends on old contents"), patch->new_name); - if (0 < patch->is_delete && newlines) - die(_("deleted file %s still has contents"), patch->old_name); - if (!patch->is_delete && !newlines && context) - fprintf_ln(stderr, - _("** warning: " - "file %s becomes empty but is not deleted"), - patch->new_name); - - return offset; -} - -static inline int metadata_changes(struct patch *patch) -{ - return patch->is_rename > 0 || - patch->is_copy > 0 || - patch->is_new > 0 || - patch->is_delete || - (patch->old_mode && patch->new_mode && - patch->old_mode != patch->new_mode); -} - -static char *inflate_it(const void *data, unsigned long size, - unsigned long inflated_size) -{ - git_zstream stream; - void *out; - int st; - - memset(&stream, 0, sizeof(stream)); - - stream.next_in = (unsigned char *)data; - stream.avail_in = size; - stream.next_out = out = xmalloc(inflated_size); - stream.avail_out = inflated_size; - git_inflate_init(&stream); - st = git_inflate(&stream, Z_FINISH); - git_inflate_end(&stream); - if ((st != Z_STREAM_END) || stream.total_out != inflated_size) { - free(out); - return NULL; - } - return out; -} - -/* - * Read a binary hunk and return a new fragment; fragment->patch - * points at an allocated memory that the caller must free, so - * it is marked as "->free_patch = 1". - */ -static struct fragment *parse_binary_hunk(char **buf_p, - unsigned long *sz_p, - int *status_p, - int *used_p) -{ - /* - * Expect a line that begins with binary patch method ("literal" - * or "delta"), followed by the length of data before deflating. - * a sequence of 'length-byte' followed by base-85 encoded data - * should follow, terminated by a newline. - * - * Each 5-byte sequence of base-85 encodes up to 4 bytes, - * and we would limit the patch line to 66 characters, - * so one line can fit up to 13 groups that would decode - * to 52 bytes max. The length byte 'A'-'Z' corresponds - * to 1-26 bytes, and 'a'-'z' corresponds to 27-52 bytes. - */ - int llen, used; - unsigned long size = *sz_p; - char *buffer = *buf_p; - int patch_method; - unsigned long origlen; - char *data = NULL; - int hunk_size = 0; - struct fragment *frag; - - llen = linelen(buffer, size); - used = llen; - - *status_p = 0; - - if (starts_with(buffer, "delta ")) { - patch_method = BINARY_DELTA_DEFLATED; - origlen = strtoul(buffer + 6, NULL, 10); - } - else if (starts_with(buffer, "literal ")) { - patch_method = BINARY_LITERAL_DEFLATED; - origlen = strtoul(buffer + 8, NULL, 10); - } - else - return NULL; - - linenr++; - buffer += llen; - while (1) { - int byte_length, max_byte_length, newsize; - llen = linelen(buffer, size); - used += llen; - linenr++; - if (llen == 1) { - /* consume the blank line */ - buffer++; - size--; - break; - } - /* - * Minimum line is "A00000\n" which is 7-byte long, - * and the line length must be multiple of 5 plus 2. - */ - if ((llen < 7) || (llen-2) % 5) - goto corrupt; - max_byte_length = (llen - 2) / 5 * 4; - byte_length = *buffer; - if ('A' <= byte_length && byte_length <= 'Z') - byte_length = byte_length - 'A' + 1; - else if ('a' <= byte_length && byte_length <= 'z') - byte_length = byte_length - 'a' + 27; - else - goto corrupt; - /* if the input length was not multiple of 4, we would - * have filler at the end but the filler should never - * exceed 3 bytes - */ - if (max_byte_length < byte_length || - byte_length <= max_byte_length - 4) - goto corrupt; - newsize = hunk_size + byte_length; - data = xrealloc(data, newsize); - if (decode_85(data + hunk_size, buffer + 1, byte_length)) - goto corrupt; - hunk_size = newsize; - buffer += llen; - size -= llen; - } - - frag = xcalloc(1, sizeof(*frag)); - frag->patch = inflate_it(data, hunk_size, origlen); - frag->free_patch = 1; - if (!frag->patch) - goto corrupt; - free(data); - frag->size = origlen; - *buf_p = buffer; - *sz_p = size; - *used_p = used; - frag->binary_patch_method = patch_method; - return frag; - - corrupt: - free(data); - *status_p = -1; - error(_("corrupt binary patch at line %d: %.*s"), - linenr-1, llen-1, buffer); - return NULL; -} - -static int parse_binary(char *buffer, unsigned long size, struct patch *patch) -{ - /* - * We have read "GIT binary patch\n"; what follows is a line - * that says the patch method (currently, either "literal" or - * "delta") and the length of data before deflating; a - * sequence of 'length-byte' followed by base-85 encoded data - * follows. - * - * When a binary patch is reversible, there is another binary - * hunk in the same format, starting with patch method (either - * "literal" or "delta") with the length of data, and a sequence - * of length-byte + base-85 encoded data, terminated with another - * empty line. This data, when applied to the postimage, produces - * the preimage. - */ - struct fragment *forward; - struct fragment *reverse; - int status; - int used, used_1; - - forward = parse_binary_hunk(&buffer, &size, &status, &used); - if (!forward && !status) - /* there has to be one hunk (forward hunk) */ - return error(_("unrecognized binary patch at line %d"), linenr-1); - if (status) - /* otherwise we already gave an error message */ - return status; - - reverse = parse_binary_hunk(&buffer, &size, &status, &used_1); - if (reverse) - used += used_1; - else if (status) { - /* - * Not having reverse hunk is not an error, but having - * a corrupt reverse hunk is. - */ - free((void*) forward->patch); - free(forward); - return status; - } - forward->next = reverse; - patch->fragments = forward; - patch->is_binary = 1; - return used; -} - -static void prefix_one(char **name) -{ - char *old_name = *name; - if (!old_name) - return; - *name = xstrdup(prefix_filename(prefix, prefix_length, *name)); - free(old_name); -} - -static void prefix_patch(struct patch *p) -{ - if (!prefix || p->is_toplevel_relative) - return; - prefix_one(&p->new_name); - prefix_one(&p->old_name); -} - -/* - * include/exclude - */ - -static struct string_list limit_by_name; -static int has_include; -static void add_name_limit(const char *name, int exclude) -{ - struct string_list_item *it; - - it = string_list_append(&limit_by_name, name); - it->util = exclude ? NULL : (void *) 1; -} - -static int use_patch(struct patch *p) -{ - const char *pathname = p->new_name ? p->new_name : p->old_name; - int i; - - /* Paths outside are not touched regardless of "--include" */ - if (0 < prefix_length) { - int pathlen = strlen(pathname); - if (pathlen <= prefix_length || - memcmp(prefix, pathname, prefix_length)) - return 0; - } - - /* See if it matches any of exclude/include rule */ - for (i = 0; i < limit_by_name.nr; i++) { - struct string_list_item *it = &limit_by_name.items[i]; - if (!wildmatch(it->string, pathname, 0, NULL)) - return (it->util != NULL); - } - - /* - * If we had any include, a path that does not match any rule is - * not used. Otherwise, we saw bunch of exclude rules (or none) - * and such a path is used. - */ - return !has_include; -} - - -/* - * Read the patch text in "buffer" that extends for "size" bytes; stop - * reading after seeing a single patch (i.e. changes to a single file). - * Create fragments (i.e. patch hunks) and hang them to the given patch. - * Return the number of bytes consumed, so that the caller can call us - * again for the next patch. - */ -static int parse_chunk(char *buffer, unsigned long size, struct patch *patch) -{ - int hdrsize, patchsize; - int offset = find_header(buffer, size, &hdrsize, patch); - - if (offset < 0) - return offset; - - prefix_patch(patch); - - if (!use_patch(patch)) - patch->ws_rule = 0; - else - patch->ws_rule = whitespace_rule(patch->new_name - ? patch->new_name - : patch->old_name); - - patchsize = parse_single_patch(buffer + offset + hdrsize, - size - offset - hdrsize, patch); - - if (!patchsize) { - static const char git_binary[] = "GIT binary patch\n"; - int hd = hdrsize + offset; - unsigned long llen = linelen(buffer + hd, size - hd); - - if (llen == sizeof(git_binary) - 1 && - !memcmp(git_binary, buffer + hd, llen)) { - int used; - linenr++; - used = parse_binary(buffer + hd + llen, - size - hd - llen, patch); - if (used) - patchsize = used + llen; - else - patchsize = 0; - } - else if (!memcmp(" differ\n", buffer + hd + llen - 8, 8)) { - static const char *binhdr[] = { - "Binary files ", - "Files ", - NULL, - }; - int i; - for (i = 0; binhdr[i]; i++) { - int len = strlen(binhdr[i]); - if (len < size - hd && - !memcmp(binhdr[i], buffer + hd, len)) { - linenr++; - patch->is_binary = 1; - patchsize = llen; - break; - } - } - } - - /* Empty patch cannot be applied if it is a text patch - * without metadata change. A binary patch appears - * empty to us here. - */ - if ((apply || check) && - (!patch->is_binary && !metadata_changes(patch))) - die(_("patch with only garbage at line %d"), linenr); - } - - return offset + hdrsize + patchsize; -} - -#define swap(a,b) myswap((a),(b),sizeof(a)) - -#define myswap(a, b, size) do { \ - unsigned char mytmp[size]; \ - memcpy(mytmp, &a, size); \ - memcpy(&a, &b, size); \ - memcpy(&b, mytmp, size); \ -} while (0) - -static void reverse_patches(struct patch *p) -{ - for (; p; p = p->next) { - struct fragment *frag = p->fragments; - - swap(p->new_name, p->old_name); - swap(p->new_mode, p->old_mode); - swap(p->is_new, p->is_delete); - swap(p->lines_added, p->lines_deleted); - swap(p->old_sha1_prefix, p->new_sha1_prefix); - - for (; frag; frag = frag->next) { - swap(frag->newpos, frag->oldpos); - swap(frag->newlines, frag->oldlines); - } - } -} - -static const char pluses[] = -"++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++"; -static const char minuses[]= -"----------------------------------------------------------------------"; - -static void show_stats(struct patch *patch) -{ - struct strbuf qname = STRBUF_INIT; - char *cp = patch->new_name ? patch->new_name : patch->old_name; - int max, add, del; - - quote_c_style(cp, &qname, NULL, 0); - - /* - * "scale" the filename - */ - max = max_len; - if (max > 50) - max = 50; - - if (qname.len > max) { - cp = strchr(qname.buf + qname.len + 3 - max, '/'); - if (!cp) - cp = qname.buf + qname.len + 3 - max; - strbuf_splice(&qname, 0, cp - qname.buf, "...", 3); - } - - if (patch->is_binary) { - printf(" %-*s | Bin\n", max, qname.buf); - strbuf_release(&qname); - return; - } - - printf(" %-*s |", max, qname.buf); - strbuf_release(&qname); - - /* - * scale the add/delete - */ - max = max + max_change > 70 ? 70 - max : max_change; - add = patch->lines_added; - del = patch->lines_deleted; - - if (max_change > 0) { - int total = ((add + del) * max + max_change / 2) / max_change; - add = (add * max + max_change / 2) / max_change; - del = total - add; - } - printf("%5d %.*s%.*s\n", patch->lines_added + patch->lines_deleted, - add, pluses, del, minuses); -} - -static int read_old_data(struct stat *st, const char *path, struct strbuf *buf) -{ - switch (st->st_mode & S_IFMT) { - case S_IFLNK: - if (strbuf_readlink(buf, path, st->st_size) < 0) - return error(_("unable to read symlink %s"), path); - return 0; - case S_IFREG: - if (strbuf_read_file(buf, path, st->st_size) != st->st_size) - return error(_("unable to open or read %s"), path); - convert_to_git(path, buf->buf, buf->len, buf, 0); - return 0; - default: - return -1; - } -} - -/* - * Update the preimage, and the common lines in postimage, - * from buffer buf of length len. If postlen is 0 the postimage - * is updated in place, otherwise it's updated on a new buffer - * of length postlen - */ - -static void update_pre_post_images(struct image *preimage, - struct image *postimage, - char *buf, - size_t len, size_t postlen) -{ - int i, ctx, reduced; - char *new, *old, *fixed; - struct image fixed_preimage; - - /* - * Update the preimage with whitespace fixes. Note that we - * are not losing preimage->buf -- apply_one_fragment() will - * free "oldlines". - */ - prepare_image(&fixed_preimage, buf, len, 1); - assert(postlen - ? fixed_preimage.nr == preimage->nr - : fixed_preimage.nr <= preimage->nr); - for (i = 0; i < fixed_preimage.nr; i++) - fixed_preimage.line[i].flag = preimage->line[i].flag; - free(preimage->line_allocated); - *preimage = fixed_preimage; - - /* - * Adjust the common context lines in postimage. This can be - * done in-place when we are shrinking it with whitespace - * fixing, but needs a new buffer when ignoring whitespace or - * expanding leading tabs to spaces. - * - * We trust the caller to tell us if the update can be done - * in place (postlen==0) or not. - */ - old = postimage->buf; - if (postlen) - new = postimage->buf = xmalloc(postlen); - else - new = old; - fixed = preimage->buf; - - for (i = reduced = ctx = 0; i < postimage->nr; i++) { - size_t len = postimage->line[i].len; - if (!(postimage->line[i].flag & LINE_COMMON)) { - /* an added line -- no counterparts in preimage */ - memmove(new, old, len); - old += len; - new += len; - continue; - } - - /* a common context -- skip it in the original postimage */ - old += len; - - /* and find the corresponding one in the fixed preimage */ - while (ctx < preimage->nr && - !(preimage->line[ctx].flag & LINE_COMMON)) { - fixed += preimage->line[ctx].len; - ctx++; - } - - /* - * preimage is expected to run out, if the caller - * fixed addition of trailing blank lines. - */ - if (preimage->nr <= ctx) { - reduced++; - continue; - } - - /* and copy it in, while fixing the line length */ - len = preimage->line[ctx].len; - memcpy(new, fixed, len); - new += len; - fixed += len; - postimage->line[i].len = len; - ctx++; - } - - if (postlen - ? postlen < new - postimage->buf - : postimage->len < new - postimage->buf) - die("BUG: caller miscounted postlen: asked %d, orig = %d, used = %d", - (int)postlen, (int) postimage->len, (int)(new - postimage->buf)); - - /* Fix the length of the whole thing */ - postimage->len = new - postimage->buf; - postimage->nr -= reduced; -} - -static int match_fragment(struct image *img, - struct image *preimage, - struct image *postimage, - unsigned long try, - int try_lno, - unsigned ws_rule, - int match_beginning, int match_end) -{ - int i; - char *fixed_buf, *buf, *orig, *target; - struct strbuf fixed; - size_t fixed_len, postlen; - int preimage_limit; - - if (preimage->nr + try_lno <= img->nr) { - /* - * The hunk falls within the boundaries of img. - */ - preimage_limit = preimage->nr; - if (match_end && (preimage->nr + try_lno != img->nr)) - return 0; - } else if (ws_error_action == correct_ws_error && - (ws_rule & WS_BLANK_AT_EOF)) { - /* - * This hunk extends beyond the end of img, and we are - * removing blank lines at the end of the file. This - * many lines from the beginning of the preimage must - * match with img, and the remainder of the preimage - * must be blank. - */ - preimage_limit = img->nr - try_lno; - } else { - /* - * The hunk extends beyond the end of the img and - * we are not removing blanks at the end, so we - * should reject the hunk at this position. - */ - return 0; - } - - if (match_beginning && try_lno) - return 0; - - /* Quick hash check */ - for (i = 0; i < preimage_limit; i++) - 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) { - /* - * Do we have an exact match? If we were told to match - * at the end, size must be exactly at try+fragsize, - * otherwise try+fragsize must be still within the preimage, - * and either case, the old piece should match the preimage - * exactly. - */ - if ((match_end - ? (try + preimage->len == img->len) - : (try + preimage->len <= img->len)) && - !memcmp(img->buf + try, preimage->buf, preimage->len)) - return 1; - } else { - /* - * The preimage extends beyond the end of img, so - * there cannot be an exact match. - * - * There must be one non-blank context line that match - * a line before the end of img. - */ - char *buf_end; - - buf = preimage->buf; - buf_end = buf; - for (i = 0; i < preimage_limit; i++) - buf_end += preimage->line[i].len; - - for ( ; buf < buf_end; buf++) - if (!isspace(*buf)) - break; - if (buf == buf_end) - return 0; - } - - /* - * No exact match. If we are ignoring whitespace, run a line-by-line - * fuzzy matching. We collect all the line length information because - * we need it to adjust whitespace if we match. - */ - if (ws_ignore_action == ignore_ws_change) { - size_t imgoff = 0; - size_t preoff = 0; - size_t postlen = postimage->len; - size_t extra_chars; - char *preimage_eof; - char *preimage_end; - for (i = 0; i < preimage_limit; i++) { - size_t prelen = preimage->line[i].len; - size_t imglen = img->line[try_lno+i].len; - - if (!fuzzy_matchlines(img->buf + try + imgoff, imglen, - preimage->buf + preoff, prelen)) - return 0; - if (preimage->line[i].flag & LINE_COMMON) - postlen += imglen - prelen; - imgoff += imglen; - preoff += prelen; - } - - /* - * Ok, the preimage matches with whitespace fuzz. - * - * imgoff now holds the true length of the target that - * matches the preimage before the end of the file. - * - * Count the number of characters in the preimage that fall - * beyond the end of the file and make sure that all of them - * are whitespace characters. (This can only happen if - * we are removing blank lines at the end of the file.) - */ - buf = preimage_eof = preimage->buf + preoff; - for ( ; i < preimage->nr; i++) - preoff += preimage->line[i].len; - preimage_end = preimage->buf + preoff; - for ( ; buf < preimage_end; buf++) - if (!isspace(*buf)) - return 0; - - /* - * Update the preimage and the common postimage context - * lines to use the same whitespace as the target. - * If whitespace is missing in the target (i.e. - * if the preimage extends beyond the end of the file), - * use the whitespace from the preimage. - */ - extra_chars = preimage_end - preimage_eof; - strbuf_init(&fixed, imgoff + extra_chars); - strbuf_add(&fixed, img->buf + try, imgoff); - strbuf_add(&fixed, preimage_eof, extra_chars); - fixed_buf = strbuf_detach(&fixed, &fixed_len); - update_pre_post_images(preimage, postimage, - fixed_buf, fixed_len, postlen); - return 1; - } - - if (ws_error_action != correct_ws_error) - return 0; - - /* - * The hunk does not apply byte-by-byte, but the hash says - * it might with whitespace fuzz. We weren't asked to - * ignore whitespace, we were asked to correct whitespace - * errors, so let's try matching after whitespace correction. - * - * While checking the preimage against the target, whitespace - * errors in both fixed, we count how large the corresponding - * postimage needs to be. The postimage prepared by - * apply_one_fragment() has whitespace errors fixed on added - * lines already, but the common lines were propagated as-is, - * which may become longer when their whitespace errors are - * fixed. - */ - - /* First count added lines in postimage */ - postlen = 0; - for (i = 0; i < postimage->nr; i++) { - if (!(postimage->line[i].flag & LINE_COMMON)) - postlen += postimage->line[i].len; - } - - /* - * The preimage may extend beyond the end of the file, - * but in this loop we will only handle the part of the - * preimage that falls within the file. - */ - strbuf_init(&fixed, preimage->len + 1); - orig = preimage->buf; - target = img->buf + try; - for (i = 0; i < preimage_limit; i++) { - size_t oldlen = preimage->line[i].len; - size_t tgtlen = img->line[try_lno + i].len; - size_t fixstart = fixed.len; - struct strbuf tgtfix; - int match; - - /* Try fixing the line in the preimage */ - ws_fix_copy(&fixed, orig, oldlen, ws_rule, NULL); - - /* Try fixing the line in the target */ - strbuf_init(&tgtfix, tgtlen); - ws_fix_copy(&tgtfix, target, tgtlen, ws_rule, NULL); - - /* - * If they match, either the preimage was based on - * a version before our tree fixed whitespace breakage, - * or we are lacking a whitespace-fix patch the tree - * the preimage was based on already had (i.e. target - * has whitespace breakage, the preimage doesn't). - * In either case, we are fixing the whitespace breakages - * so we might as well take the fix together with their - * real change. - */ - match = (tgtfix.len == fixed.len - fixstart && - !memcmp(tgtfix.buf, fixed.buf + fixstart, - fixed.len - fixstart)); - - /* Add the length if this is common with the postimage */ - if (preimage->line[i].flag & LINE_COMMON) - postlen += tgtfix.len; - - strbuf_release(&tgtfix); - if (!match) - goto unmatch_exit; - - orig += oldlen; - target += tgtlen; - } - - - /* - * Now handle the lines in the preimage that falls beyond the - * end of the file (if any). They will only match if they are - * empty or only contain whitespace (if WS_BLANK_AT_EOL is - * false). - */ - for ( ; i < preimage->nr; i++) { - size_t fixstart = fixed.len; /* start of the fixed preimage */ - size_t oldlen = preimage->line[i].len; - int j; - - /* Try fixing the line in the preimage */ - ws_fix_copy(&fixed, orig, oldlen, ws_rule, NULL); - - for (j = fixstart; j < fixed.len; j++) - if (!isspace(fixed.buf[j])) - goto unmatch_exit; - - orig += oldlen; - } - - /* - * Yes, the preimage is based on an older version that still - * has whitespace breakages unfixed, and fixing them makes the - * hunk match. Update the context lines in the postimage. - */ - fixed_buf = strbuf_detach(&fixed, &fixed_len); - if (postlen < postimage->len) - postlen = 0; - update_pre_post_images(preimage, postimage, - fixed_buf, fixed_len, postlen); - return 1; - - unmatch_exit: - strbuf_release(&fixed); - return 0; -} - -static int find_pos(struct image *img, - struct image *preimage, - struct image *postimage, - int line, - unsigned ws_rule, - int match_beginning, int match_end) -{ - int i; - unsigned long backwards, forwards, try; - int backwards_lno, forwards_lno, try_lno; - - /* - * If match_beginning or match_end is specified, there is no - * point starting from a wrong line that will never match and - * wander around and wait for a match at the specified end. - */ - if (match_beginning) - line = 0; - else if (match_end) - line = img->nr - preimage->nr; - - /* - * Because the comparison is unsigned, the following test - * will also take care of a negative line number that can - * result when match_end and preimage is larger than the target. - */ - if ((size_t) line > img->nr) - line = img->nr; - - try = 0; - for (i = 0; i < line; i++) - try += img->line[i].len; - - /* - * There's probably some smart way to do this, but I'll leave - * that to the smart and beautiful people. I'm simple and stupid. - */ - backwards = try; - backwards_lno = line; - forwards = try; - forwards_lno = line; - try_lno = line; - - for (i = 0; ; i++) { - if (match_fragment(img, preimage, postimage, - try, try_lno, ws_rule, - match_beginning, match_end)) - return try_lno; - - again: - if (backwards_lno == 0 && forwards_lno == img->nr) - break; - - if (i & 1) { - if (backwards_lno == 0) { - i++; - goto again; - } - backwards_lno--; - backwards -= img->line[backwards_lno].len; - try = backwards; - try_lno = backwards_lno; - } else { - if (forwards_lno == img->nr) { - i++; - goto again; - } - forwards += img->line[forwards_lno].len; - forwards_lno++; - try = forwards; - try_lno = forwards_lno; - } - - } - return -1; -} - -static void remove_first_line(struct image *img) -{ - img->buf += img->line[0].len; - img->len -= img->line[0].len; - img->line++; - img->nr--; -} - -static void remove_last_line(struct image *img) -{ - img->len -= img->line[--img->nr].len; -} - -/* - * The change from "preimage" and "postimage" has been found to - * apply at applied_pos (counts in line numbers) in "img". - * Update "img" to remove "preimage" and replace it with "postimage". - */ -static void update_image(struct image *img, - int applied_pos, - struct image *preimage, - struct image *postimage) -{ - /* - * remove the copy of preimage at offset in img - * and replace it with postimage - */ - int i, nr; - size_t remove_count, insert_count, applied_at = 0; - char *result; - int preimage_limit; - - /* - * If we are removing blank lines at the end of img, - * the preimage may extend beyond the end. - * If that is the case, we must be careful only to - * remove the part of the preimage that falls within - * the boundaries of img. Initialize preimage_limit - * to the number of lines in the preimage that falls - * within the boundaries. - */ - preimage_limit = preimage->nr; - if (preimage_limit > img->nr - applied_pos) - preimage_limit = img->nr - applied_pos; - - for (i = 0; i < applied_pos; i++) - applied_at += img->line[i].len; - - remove_count = 0; - for (i = 0; i < preimage_limit; i++) - remove_count += img->line[applied_pos + i].len; - insert_count = postimage->len; - - /* Adjust the contents */ - result = xmalloc(img->len + insert_count - remove_count + 1); - memcpy(result, img->buf, applied_at); - memcpy(result + applied_at, postimage->buf, postimage->len); - memcpy(result + applied_at + postimage->len, - img->buf + (applied_at + remove_count), - img->len - (applied_at + remove_count)); - free(img->buf); - img->buf = result; - img->len += insert_count - remove_count; - result[img->len] = '\0'; - - /* Adjust the line table */ - nr = img->nr + postimage->nr - preimage_limit; - if (preimage_limit < postimage->nr) { - /* - * NOTE: this knows that we never call remove_first_line() - * on anything other than pre/post image. - */ - REALLOC_ARRAY(img->line, nr); - img->line_allocated = img->line; - } - if (preimage_limit != postimage->nr) - memmove(img->line + applied_pos + postimage->nr, - img->line + applied_pos + preimage_limit, - (img->nr - (applied_pos + preimage_limit)) * - sizeof(*img->line)); - memcpy(img->line + applied_pos, - postimage->line, - postimage->nr * sizeof(*img->line)); - if (!allow_overlap) - for (i = 0; i < postimage->nr; i++) - img->line[applied_pos + i].flag |= LINE_PATCHED; - img->nr = nr; -} - -/* - * Use the patch-hunk text in "frag" to prepare two images (preimage and - * postimage) for the hunk. Find lines that match "preimage" in "img" and - * replace the part of "img" with "postimage" text. - */ -static int apply_one_fragment(struct image *img, struct fragment *frag, - int inaccurate_eof, unsigned ws_rule, - int nth_fragment) -{ - int match_beginning, match_end; - const char *patch = frag->patch; - int size = frag->size; - char *old, *oldlines; - struct strbuf newlines; - int new_blank_lines_at_end = 0; - int found_new_blank_lines_at_end = 0; - int hunk_linenr = frag->linenr; - unsigned long leading, trailing; - int pos, applied_pos; - struct image preimage; - struct image postimage; - - memset(&preimage, 0, sizeof(preimage)); - memset(&postimage, 0, sizeof(postimage)); - oldlines = xmalloc(size); - strbuf_init(&newlines, size); - - old = oldlines; - while (size > 0) { - char first; - int len = linelen(patch, size); - int plen; - int added_blank_line = 0; - int is_blank_context = 0; - size_t start; - - if (!len) - break; - - /* - * "plen" is how much of the line we should use for - * the actual patch data. Normally we just remove the - * first character on the line, but if the line is - * followed by "\ No newline", then we also remove the - * last one (which is the newline, of course). - */ - plen = len - 1; - if (len < size && patch[len] == '\\') - plen--; - first = *patch; - if (apply_in_reverse) { - if (first == '-') - first = '+'; - else if (first == '+') - first = '-'; - } - - switch (first) { - case '\n': - /* Newer GNU diff, empty context line */ - if (plen < 0) - /* ... followed by '\No newline'; nothing */ - break; - *old++ = '\n'; - strbuf_addch(&newlines, '\n'); - add_line_info(&preimage, "\n", 1, LINE_COMMON); - add_line_info(&postimage, "\n", 1, LINE_COMMON); - is_blank_context = 1; - break; - case ' ': - if (plen && (ws_rule & WS_BLANK_AT_EOF) && - ws_blank_line(patch + 1, plen, ws_rule)) - is_blank_context = 1; - case '-': - memcpy(old, patch + 1, plen); - add_line_info(&preimage, old, plen, - (first == ' ' ? LINE_COMMON : 0)); - old += plen; - if (first == '-') - break; - /* Fall-through for ' ' */ - case '+': - /* --no-add does not add new lines */ - if (first == '+' && no_add) - break; - - start = newlines.len; - if (first != '+' || - !whitespace_error || - ws_error_action != correct_ws_error) { - strbuf_add(&newlines, patch + 1, plen); - } - else { - ws_fix_copy(&newlines, patch + 1, plen, ws_rule, &applied_after_fixing_ws); - } - add_line_info(&postimage, newlines.buf + start, newlines.len - start, - (first == '+' ? 0 : LINE_COMMON)); - if (first == '+' && - (ws_rule & WS_BLANK_AT_EOF) && - ws_blank_line(patch + 1, plen, ws_rule)) - added_blank_line = 1; - break; - case '@': case '\\': - /* Ignore it, we already handled it */ - break; - default: - if (apply_verbosely) - error(_("invalid start of line: '%c'"), first); - applied_pos = -1; - goto out; - } - if (added_blank_line) { - if (!new_blank_lines_at_end) - found_new_blank_lines_at_end = hunk_linenr; - new_blank_lines_at_end++; - } - else if (is_blank_context) - ; - else - new_blank_lines_at_end = 0; - patch += len; - size -= len; - hunk_linenr++; - } - if (inaccurate_eof && - old > oldlines && old[-1] == '\n' && - newlines.len > 0 && newlines.buf[newlines.len - 1] == '\n') { - old--; - strbuf_setlen(&newlines, newlines.len - 1); - } - - leading = frag->leading; - trailing = frag->trailing; - - /* - * A hunk to change lines at the beginning would begin with - * @@ -1,L +N,M @@ - * but we need to be careful. -U0 that inserts before the second - * line also has this pattern. - * - * And a hunk to add to an empty file would begin with - * @@ -0,0 +N,M @@ - * - * In other words, a hunk that is (frag->oldpos <= 1) with or - * without leading context must match at the beginning. - */ - match_beginning = (!frag->oldpos || - (frag->oldpos == 1 && !unidiff_zero)); - - /* - * A hunk without trailing lines must match at the end. - * However, we simply cannot tell if a hunk must match end - * from the lack of trailing lines if the patch was generated - * with unidiff without any context. - */ - match_end = !unidiff_zero && !trailing; - - pos = frag->newpos ? (frag->newpos - 1) : 0; - preimage.buf = oldlines; - preimage.len = old - oldlines; - postimage.buf = newlines.buf; - postimage.len = newlines.len; - preimage.line = preimage.line_allocated; - postimage.line = postimage.line_allocated; - - for (;;) { - - applied_pos = find_pos(img, &preimage, &postimage, pos, - ws_rule, match_beginning, match_end); - - if (applied_pos >= 0) - break; - - /* Am I at my context limits? */ - if ((leading <= p_context) && (trailing <= p_context)) - break; - if (match_beginning || match_end) { - match_beginning = match_end = 0; - continue; - } - - /* - * Reduce the number of context lines; reduce both - * leading and trailing if they are equal otherwise - * just reduce the larger context. - */ - if (leading >= trailing) { - remove_first_line(&preimage); - remove_first_line(&postimage); - pos--; - leading--; - } - if (trailing > leading) { - remove_last_line(&preimage); - remove_last_line(&postimage); - trailing--; - } - } - - if (applied_pos >= 0) { - if (new_blank_lines_at_end && - preimage.nr + applied_pos >= img->nr && - (ws_rule & WS_BLANK_AT_EOF) && - ws_error_action != nowarn_ws_error) { - record_ws_error(WS_BLANK_AT_EOF, "+", 1, - found_new_blank_lines_at_end); - if (ws_error_action == correct_ws_error) { - while (new_blank_lines_at_end--) - remove_last_line(&postimage); - } - /* - * We would want to prevent write_out_results() - * from taking place in apply_patch() that follows - * the callchain led us here, which is: - * apply_patch->check_patch_list->check_patch-> - * apply_data->apply_fragments->apply_one_fragment - */ - if (ws_error_action == die_on_ws_error) - apply = 0; - } - - if (apply_verbosely && applied_pos != pos) { - int offset = applied_pos - pos; - if (apply_in_reverse) - offset = 0 - offset; - fprintf_ln(stderr, - Q_("Hunk #%d succeeded at %d (offset %d line).", - "Hunk #%d succeeded at %d (offset %d lines).", - offset), - nth_fragment, applied_pos + 1, offset); - } - - /* - * Warn if it was necessary to reduce the number - * of context lines. - */ - if ((leading != frag->leading) || - (trailing != frag->trailing)) - fprintf_ln(stderr, _("Context reduced to (%ld/%ld)" - " to apply fragment at %d"), - leading, trailing, applied_pos+1); - update_image(img, applied_pos, &preimage, &postimage); - } else { - if (apply_verbosely) - error(_("while searching for:\n%.*s"), - (int)(old - oldlines), oldlines); - } - -out: - free(oldlines); - strbuf_release(&newlines); - free(preimage.line_allocated); - free(postimage.line_allocated); - - return (applied_pos < 0); -} - -static int apply_binary_fragment(struct image *img, struct patch *patch) -{ - struct fragment *fragment = patch->fragments; - unsigned long len; - void *dst; - - 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) - return error("cannot reverse-apply a binary patch " - "without the reverse hunk to '%s'", - patch->new_name - ? patch->new_name : patch->old_name); - fragment = fragment->next; - } - switch (fragment->binary_patch_method) { - case BINARY_DELTA_DEFLATED: - dst = patch_delta(img->buf, img->len, fragment->patch, - fragment->size, &len); - if (!dst) - return -1; - clear_image(img); - img->buf = dst; - img->len = len; - return 0; - case BINARY_LITERAL_DEFLATED: - clear_image(img); - img->len = fragment->size; - img->buf = xmemdupz(fragment->patch, img->len); - return 0; - } - return -1; -} - -/* - * Replace "img" with the result of applying the binary patch. - * The binary patch data itself in patch->fragment is still kept - * but the preimage prepared by the caller in "img" is freed here - * or in the helper function apply_binary_fragment() this calls. - */ -static int apply_binary(struct image *img, struct patch *patch) -{ - const char *name = patch->old_name ? patch->old_name : patch->new_name; - unsigned char sha1[20]; - - /* - * For safety, we require patch index line to contain - * full 40-byte textual SHA1 for old and new, at least for now. - */ - if (strlen(patch->old_sha1_prefix) != 40 || - strlen(patch->new_sha1_prefix) != 40 || - get_sha1_hex(patch->old_sha1_prefix, sha1) || - get_sha1_hex(patch->new_sha1_prefix, sha1)) - return error("cannot apply binary patch to '%s' " - "without full index line", name); - - if (patch->old_name) { - /* - * See if the old one matches what the patch - * applies to. - */ - hash_sha1_file(img->buf, img->len, blob_type, sha1); - if (strcmp(sha1_to_hex(sha1), patch->old_sha1_prefix)) - return error("the patch applies to '%s' (%s), " - "which does not match the " - "current contents.", - name, sha1_to_hex(sha1)); - } - else { - /* Otherwise, the old one must be empty. */ - if (img->len) - return error("the patch applies to an empty " - "'%s' but it is not empty", name); - } - - get_sha1_hex(patch->new_sha1_prefix, sha1); - if (is_null_sha1(sha1)) { - clear_image(img); - return 0; /* deletion patch */ - } - - if (has_sha1_file(sha1)) { - /* We already have the postimage */ - enum object_type type; - unsigned long size; - char *result; - - result = read_sha1_file(sha1, &type, &size); - if (!result) - return error("the necessary postimage %s for " - "'%s' cannot be read", - patch->new_sha1_prefix, name); - clear_image(img); - img->buf = result; - img->len = size; - } else { - /* - * We have verified buf matches the preimage; - * apply the patch data to it, which is stored - * in the patch->fragments->{patch,size}. - */ - if (apply_binary_fragment(img, patch)) - return error(_("binary patch does not apply to '%s'"), - name); - - /* verify that the result matches */ - hash_sha1_file(img->buf, img->len, blob_type, sha1); - if (strcmp(sha1_to_hex(sha1), patch->new_sha1_prefix)) - return error(_("binary patch to '%s' creates incorrect result (expecting %s, got %s)"), - name, patch->new_sha1_prefix, sha1_to_hex(sha1)); - } - - return 0; -} - -static int apply_fragments(struct image *img, struct patch *patch) -{ - struct fragment *frag = patch->fragments; - const char *name = patch->old_name ? patch->old_name : patch->new_name; - unsigned ws_rule = patch->ws_rule; - unsigned inaccurate_eof = patch->inaccurate_eof; - int nth = 0; - - if (patch->is_binary) - return apply_binary(img, patch); - - while (frag) { - nth++; - if (apply_one_fragment(img, frag, inaccurate_eof, ws_rule, nth)) { - error(_("patch failed: %s:%ld"), name, frag->oldpos); - if (!apply_with_reject) - return -1; - frag->rejected = 1; - } - frag = frag->next; - } - return 0; -} - -static int read_blob_object(struct strbuf *buf, const unsigned char *sha1, unsigned mode) -{ - if (S_ISGITLINK(mode)) { - strbuf_grow(buf, 100); - strbuf_addf(buf, "Subproject commit %s\n", sha1_to_hex(sha1)); - } else { - enum object_type type; - unsigned long sz; - char *result; - - result = read_sha1_file(sha1, &type, &sz); - if (!result) - return -1; - /* XXX read_sha1_file NUL-terminates */ - strbuf_attach(buf, result, sz, sz + 1); - } - return 0; -} - -static int read_file_or_gitlink(const struct cache_entry *ce, struct strbuf *buf) -{ - if (!ce) - return 0; - return read_blob_object(buf, ce->sha1, ce->ce_mode); -} - -static struct patch *in_fn_table(const char *name) -{ - struct string_list_item *item; - - if (name == NULL) - return NULL; - - item = string_list_lookup(&fn_table, name); - if (item != NULL) - return (struct patch *)item->util; - - return NULL; -} - -/* - * item->util in the filename table records the status of the path. - * Usually it points at a patch (whose result records the contents - * of it after applying it), but it could be PATH_WAS_DELETED for a - * path that a previously applied patch has already removed, or - * PATH_TO_BE_DELETED for a path that a later patch would remove. - * - * The latter is needed to deal with a case where two paths A and B - * are swapped by first renaming A to B and then renaming B to A; - * moving A to B should not be prevented due to presence of B as we - * will remove it in a later patch. - */ -#define PATH_TO_BE_DELETED ((struct patch *) -2) -#define PATH_WAS_DELETED ((struct patch *) -1) - -static int to_be_deleted(struct patch *patch) -{ - return patch == PATH_TO_BE_DELETED; -} - -static int was_deleted(struct patch *patch) -{ - return patch == PATH_WAS_DELETED; -} - -static void add_to_fn_table(struct patch *patch) -{ - struct string_list_item *item; - - /* - * Always add new_name unless patch is a deletion - * This should cover the cases for normal diffs, - * file creations and copies - */ - if (patch->new_name != NULL) { - item = string_list_insert(&fn_table, patch->new_name); - item->util = patch; - } - - /* - * store a failure on rename/deletion cases because - * later chunks shouldn't patch old names - */ - if ((patch->new_name == NULL) || (patch->is_rename)) { - item = string_list_insert(&fn_table, patch->old_name); - item->util = PATH_WAS_DELETED; - } -} - -static void prepare_fn_table(struct patch *patch) -{ - /* - * store information about incoming file deletion - */ - while (patch) { - if ((patch->new_name == NULL) || (patch->is_rename)) { - struct string_list_item *item; - item = string_list_insert(&fn_table, patch->old_name); - item->util = PATH_TO_BE_DELETED; - } - patch = patch->next; - } -} - -static int checkout_target(struct index_state *istate, - struct cache_entry *ce, struct stat *st) -{ - struct checkout costate; - - memset(&costate, 0, sizeof(costate)); - costate.base_dir = ""; - costate.refresh_cache = 1; - costate.istate = istate; - if (checkout_entry(ce, &costate, NULL) || lstat(ce->name, st)) - return error(_("cannot checkout %s"), ce->name); - return 0; -} - -static struct patch *previous_patch(struct patch *patch, int *gone) -{ - struct patch *previous; - - *gone = 0; - if (patch->is_copy || patch->is_rename) - return NULL; /* "git" patches do not depend on the order */ - - previous = in_fn_table(patch->old_name); - if (!previous) - return NULL; - - if (to_be_deleted(previous)) - return NULL; /* the deletion hasn't happened yet */ - - if (was_deleted(previous)) - *gone = 1; - - return previous; -} - -static int verify_index_match(const struct cache_entry *ce, struct stat *st) -{ - if (S_ISGITLINK(ce->ce_mode)) { - if (!S_ISDIR(st->st_mode)) - return -1; - return 0; - } - return ce_match_stat(ce, st, CE_MATCH_IGNORE_VALID|CE_MATCH_IGNORE_SKIP_WORKTREE); -} - -#define SUBMODULE_PATCH_WITHOUT_INDEX 1 - -static int load_patch_target(struct strbuf *buf, - const struct cache_entry *ce, - struct stat *st, - const char *name, - unsigned expected_mode) -{ - if (cached || check_index) { - if (read_file_or_gitlink(ce, buf)) - return error(_("read of %s failed"), name); - } else if (name) { - if (S_ISGITLINK(expected_mode)) { - if (ce) - return read_file_or_gitlink(ce, buf); - else - return SUBMODULE_PATCH_WITHOUT_INDEX; - } else if (has_symlink_leading_path(name, strlen(name))) { - return error(_("reading from '%s' beyond a symbolic link"), name); - } else { - if (read_old_data(st, name, buf)) - return error(_("read of %s failed"), name); - } - } - return 0; -} - -/* - * We are about to apply "patch"; populate the "image" with the - * current version we have, from the working tree or from the index, - * depending on the situation e.g. --cached/--index. If we are - * applying a non-git patch that incrementally updates the tree, - * we read from the result of a previous diff. - */ -static int load_preimage(struct image *image, - struct patch *patch, struct stat *st, - const struct cache_entry *ce) -{ - struct strbuf buf = STRBUF_INIT; - size_t len; - char *img; - struct patch *previous; - int status; - - previous = previous_patch(patch, &status); - if (status) - return error(_("path %s has been renamed/deleted"), - patch->old_name); - if (previous) { - /* We have a patched copy in memory; use that. */ - strbuf_add(&buf, previous->result, previous->resultsize); - } else { - status = load_patch_target(&buf, ce, st, - patch->old_name, patch->old_mode); - if (status < 0) - return status; - else if (status == SUBMODULE_PATCH_WITHOUT_INDEX) { - /* - * There is no way to apply subproject - * patch without looking at the index. - * NEEDSWORK: shouldn't this be flagged - * as an error??? - */ - free_fragment_list(patch->fragments); - patch->fragments = NULL; - } else if (status) { - return error(_("read of %s failed"), patch->old_name); - } - } - - img = strbuf_detach(&buf, &len); - prepare_image(image, img, len, !patch->is_binary); - return 0; -} - -static int three_way_merge(struct image *image, - char *path, - const unsigned char *base, - const unsigned char *ours, - const unsigned char *theirs) -{ - mmfile_t base_file, our_file, their_file; - mmbuffer_t result = { NULL }; - int status; - - read_mmblob(&base_file, base); - read_mmblob(&our_file, ours); - read_mmblob(&their_file, theirs); - status = ll_merge(&result, path, - &base_file, "base", - &our_file, "ours", - &their_file, "theirs", NULL); - free(base_file.ptr); - free(our_file.ptr); - free(their_file.ptr); - if (status < 0 || !result.ptr) { - free(result.ptr); - return -1; - } - clear_image(image); - image->buf = result.ptr; - image->len = result.size; - - return status; -} - -/* - * When directly falling back to add/add three-way merge, we read from - * the current contents of the new_name. In no cases other than that - * this function will be called. - */ -static int load_current(struct image *image, struct patch *patch) -{ - struct strbuf buf = STRBUF_INIT; - int status, pos; - size_t len; - char *img; - struct stat st; - struct cache_entry *ce; - char *name = patch->new_name; - unsigned mode = patch->new_mode; - - if (!patch->is_new) - die("BUG: patch to %s is not a creation", patch->old_name); - - pos = cache_name_pos(name, strlen(name)); - if (pos < 0) - return error(_("%s: does not exist in index"), name); - ce = active_cache[pos]; - if (lstat(name, &st)) { - if (errno != ENOENT) - return error(_("%s: %s"), name, strerror(errno)); - if (checkout_target(&the_index, ce, &st)) - return -1; - } - if (verify_index_match(ce, &st)) - return error(_("%s: does not match index"), name); - - status = load_patch_target(&buf, ce, &st, name, mode); - if (status < 0) - return status; - else if (status) - return -1; - img = strbuf_detach(&buf, &len); - prepare_image(image, img, len, !patch->is_binary); - return 0; -} - -static int try_threeway(struct image *image, struct patch *patch, - struct stat *st, const struct cache_entry *ce) -{ - unsigned char pre_sha1[20], post_sha1[20], our_sha1[20]; - struct strbuf buf = STRBUF_INIT; - size_t len; - int status; - char *img; - struct image tmp_image; - - /* No point falling back to 3-way merge in these cases */ - if (patch->is_delete || - S_ISGITLINK(patch->old_mode) || S_ISGITLINK(patch->new_mode)) - return -1; - - /* Preimage the patch was prepared for */ - if (patch->is_new) - write_sha1_file("", 0, blob_type, pre_sha1); - else if (get_sha1(patch->old_sha1_prefix, pre_sha1) || - read_blob_object(&buf, pre_sha1, patch->old_mode)) - return error("repository lacks the necessary blob to fall back on 3-way merge."); - - fprintf(stderr, "Falling back to three-way merge...\n"); - - img = strbuf_detach(&buf, &len); - prepare_image(&tmp_image, img, len, 1); - /* Apply the patch to get the post image */ - if (apply_fragments(&tmp_image, patch) < 0) { - clear_image(&tmp_image); - return -1; - } - /* post_sha1[] is theirs */ - write_sha1_file(tmp_image.buf, tmp_image.len, blob_type, post_sha1); - clear_image(&tmp_image); - - /* our_sha1[] is ours */ - if (patch->is_new) { - if (load_current(&tmp_image, patch)) - return error("cannot read the current contents of '%s'", - patch->new_name); - } else { - if (load_preimage(&tmp_image, patch, st, ce)) - return error("cannot read the current contents of '%s'", - patch->old_name); - } - write_sha1_file(tmp_image.buf, tmp_image.len, blob_type, our_sha1); - clear_image(&tmp_image); - - /* in-core three-way merge between post and our using pre as base */ - status = three_way_merge(image, patch->new_name, - pre_sha1, our_sha1, post_sha1); - if (status < 0) { - fprintf(stderr, "Failed to fall back on three-way merge...\n"); - return status; - } - - if (status) { - patch->conflicted_threeway = 1; - if (patch->is_new) - oidclr(&patch->threeway_stage[0]); - else - hashcpy(patch->threeway_stage[0].hash, pre_sha1); - hashcpy(patch->threeway_stage[1].hash, our_sha1); - hashcpy(patch->threeway_stage[2].hash, post_sha1); - fprintf(stderr, "Applied patch to '%s' with conflicts.\n", patch->new_name); - } else { - fprintf(stderr, "Applied patch to '%s' cleanly.\n", patch->new_name); - } - return 0; -} - -static int apply_data(struct patch *patch, struct stat *st, const struct cache_entry *ce) -{ - struct image image; - - if (load_preimage(&image, patch, st, ce) < 0) - return -1; - - if (patch->direct_to_threeway || - apply_fragments(&image, patch) < 0) { - /* Note: with --reject, apply_fragments() returns 0 */ - if (!threeway || try_threeway(&image, patch, st, ce) < 0) - return -1; - } - patch->result = image.buf; - patch->resultsize = image.len; - add_to_fn_table(patch); - free(image.line_allocated); - - if (0 < patch->is_delete && patch->resultsize) - return error(_("removal patch leaves file contents")); - - return 0; -} - -/* - * If "patch" that we are looking at modifies or deletes what we have, - * we would want it not to lose any local modification we have, either - * in the working tree or in the index. - * - * This also decides if a non-git patch is a creation patch or a - * modification to an existing empty file. We do not check the state - * of the current tree for a creation patch in this function; the caller - * check_patch() separately makes sure (and errors out otherwise) that - * the path the patch creates does not exist in the current tree. - */ -static int check_preimage(struct patch *patch, struct cache_entry **ce, struct stat *st) -{ - const char *old_name = patch->old_name; - struct patch *previous = NULL; - int stat_ret = 0, status; - unsigned st_mode = 0; - - if (!old_name) - return 0; - - assert(patch->is_new <= 0); - previous = previous_patch(patch, &status); - - if (status) - return error(_("path %s has been renamed/deleted"), old_name); - if (previous) { - st_mode = previous->new_mode; - } else if (!cached) { - stat_ret = lstat(old_name, st); - if (stat_ret && errno != ENOENT) - return error(_("%s: %s"), old_name, strerror(errno)); - } - - if (check_index && !previous) { - int pos = cache_name_pos(old_name, strlen(old_name)); - if (pos < 0) { - if (patch->is_new < 0) - goto is_new; - return error(_("%s: does not exist in index"), old_name); - } - *ce = active_cache[pos]; - if (stat_ret < 0) { - if (checkout_target(&the_index, *ce, st)) - return -1; - } - if (!cached && verify_index_match(*ce, st)) - return error(_("%s: does not match index"), old_name); - if (cached) - st_mode = (*ce)->ce_mode; - } else if (stat_ret < 0) { - if (patch->is_new < 0) - goto is_new; - return error(_("%s: %s"), old_name, strerror(errno)); - } - - if (!cached && !previous) - st_mode = ce_mode_from_stat(*ce, st->st_mode); - - if (patch->is_new < 0) - patch->is_new = 0; - if (!patch->old_mode) - patch->old_mode = st_mode; - if ((st_mode ^ patch->old_mode) & S_IFMT) - return error(_("%s: wrong type"), old_name); - if (st_mode != patch->old_mode) - warning(_("%s has type %o, expected %o"), - old_name, st_mode, patch->old_mode); - if (!patch->new_mode && !patch->is_delete) - patch->new_mode = st_mode; - return 0; - - is_new: - patch->is_new = 1; - patch->is_delete = 0; - free(patch->old_name); - patch->old_name = NULL; - return 0; -} - - -#define EXISTS_IN_INDEX 1 -#define EXISTS_IN_WORKTREE 2 - -static int check_to_create(const char *new_name, int ok_if_exists) -{ - struct stat nst; - - if (check_index && - cache_name_pos(new_name, strlen(new_name)) >= 0 && - !ok_if_exists) - return EXISTS_IN_INDEX; - if (cached) - return 0; - - if (!lstat(new_name, &nst)) { - if (S_ISDIR(nst.st_mode) || ok_if_exists) - return 0; - /* - * A leading component of new_name might be a symlink - * that is going to be removed with this patch, but - * still pointing at somewhere that has the path. - * In such a case, path "new_name" does not exist as - * far as git is concerned. - */ - if (has_symlink_leading_path(new_name, strlen(new_name))) - return 0; - - return EXISTS_IN_WORKTREE; - } else if ((errno != ENOENT) && (errno != ENOTDIR)) { - return error("%s: %s", new_name, strerror(errno)); - } - return 0; -} - -/* - * We need to keep track of how symlinks in the preimage are - * manipulated by the patches. A patch to add a/b/c where a/b - * is a symlink should not be allowed to affect the directory - * the symlink points at, but if the same patch removes a/b, - * it is perfectly fine, as the patch removes a/b to make room - * to create a directory a/b so that a/b/c can be created. - */ -static struct string_list symlink_changes; -#define SYMLINK_GOES_AWAY 01 -#define SYMLINK_IN_RESULT 02 - -static uintptr_t register_symlink_changes(const char *path, uintptr_t what) -{ - struct string_list_item *ent; - - ent = string_list_lookup(&symlink_changes, path); - if (!ent) { - ent = string_list_insert(&symlink_changes, path); - ent->util = (void *)0; - } - ent->util = (void *)(what | ((uintptr_t)ent->util)); - return (uintptr_t)ent->util; -} - -static uintptr_t check_symlink_changes(const char *path) -{ - struct string_list_item *ent; - - ent = string_list_lookup(&symlink_changes, path); - if (!ent) - return 0; - return (uintptr_t)ent->util; -} - -static void prepare_symlink_changes(struct patch *patch) -{ - for ( ; patch; patch = patch->next) { - if ((patch->old_name && S_ISLNK(patch->old_mode)) && - (patch->is_rename || patch->is_delete)) - /* the symlink at patch->old_name is removed */ - register_symlink_changes(patch->old_name, SYMLINK_GOES_AWAY); - - if (patch->new_name && S_ISLNK(patch->new_mode)) - /* the symlink at patch->new_name is created or remains */ - register_symlink_changes(patch->new_name, SYMLINK_IN_RESULT); - } -} - -static int path_is_beyond_symlink_1(struct strbuf *name) -{ - do { - unsigned int change; - - while (--name->len && name->buf[name->len] != '/') - ; /* scan backwards */ - if (!name->len) - break; - name->buf[name->len] = '\0'; - change = check_symlink_changes(name->buf); - if (change & SYMLINK_IN_RESULT) - return 1; - if (change & SYMLINK_GOES_AWAY) - /* - * This cannot be "return 0", because we may - * see a new one created at a higher level. - */ - continue; - - /* otherwise, check the preimage */ - if (check_index) { - struct cache_entry *ce; - - ce = cache_file_exists(name->buf, name->len, ignore_case); - if (ce && S_ISLNK(ce->ce_mode)) - return 1; - } else { - struct stat st; - if (!lstat(name->buf, &st) && S_ISLNK(st.st_mode)) - return 1; - } - } while (1); - return 0; -} - -static int path_is_beyond_symlink(const char *name_) -{ - int ret; - struct strbuf name = STRBUF_INIT; - - assert(*name_ != '\0'); - strbuf_addstr(&name, name_); - ret = path_is_beyond_symlink_1(&name); - strbuf_release(&name); - - return ret; -} - -static void die_on_unsafe_path(struct patch *patch) -{ - const char *old_name = NULL; - const char *new_name = NULL; - if (patch->is_delete) - old_name = patch->old_name; - else if (!patch->is_new && !patch->is_copy) - old_name = patch->old_name; - if (!patch->is_delete) - new_name = patch->new_name; - - if (old_name && !verify_path(old_name)) - die(_("invalid path '%s'"), old_name); - if (new_name && !verify_path(new_name)) - die(_("invalid path '%s'"), new_name); -} - -/* - * Check and apply the patch in-core; leave the result in patch->result - * for the caller to write it out to the final destination. - */ -static int check_patch(struct patch *patch) -{ - struct stat st; - const char *old_name = patch->old_name; - const char *new_name = patch->new_name; - const char *name = old_name ? old_name : new_name; - struct cache_entry *ce = NULL; - struct patch *tpatch; - int ok_if_exists; - int status; - - patch->rejected = 1; /* we will drop this after we succeed */ - - status = check_preimage(patch, &ce, &st); - if (status) - return status; - old_name = patch->old_name; - - /* - * A type-change diff is always split into a patch to delete - * old, immediately followed by a patch to create new (see - * diff.c::run_diff()); in such a case it is Ok that the entry - * to be deleted by the previous patch is still in the working - * tree and in the index. - * - * A patch to swap-rename between A and B would first rename A - * to B and then rename B to A. While applying the first one, - * the presence of B should not stop A from getting renamed to - * B; ask to_be_deleted() about the later rename. Removal of - * B and rename from A to B is handled the same way by asking - * was_deleted(). - */ - if ((tpatch = in_fn_table(new_name)) && - (was_deleted(tpatch) || to_be_deleted(tpatch))) - ok_if_exists = 1; - else - ok_if_exists = 0; - - if (new_name && - ((0 < patch->is_new) || patch->is_rename || patch->is_copy)) { - int err = check_to_create(new_name, ok_if_exists); - - if (err && threeway) { - patch->direct_to_threeway = 1; - } else switch (err) { - case 0: - break; /* happy */ - case EXISTS_IN_INDEX: - return error(_("%s: already exists in index"), new_name); - break; - case EXISTS_IN_WORKTREE: - return error(_("%s: already exists in working directory"), - new_name); - default: - return err; - } - - if (!patch->new_mode) { - if (0 < patch->is_new) - patch->new_mode = S_IFREG | 0644; - else - patch->new_mode = patch->old_mode; - } - } - - if (new_name && old_name) { - int same = !strcmp(old_name, new_name); - if (!patch->new_mode) - patch->new_mode = patch->old_mode; - if ((patch->old_mode ^ patch->new_mode) & S_IFMT) { - if (same) - return error(_("new mode (%o) of %s does not " - "match old mode (%o)"), - patch->new_mode, new_name, - patch->old_mode); - else - return error(_("new mode (%o) of %s does not " - "match old mode (%o) of %s"), - patch->new_mode, new_name, - patch->old_mode, old_name); - } - } - - if (!unsafe_paths) - die_on_unsafe_path(patch); - - /* - * An attempt to read from or delete a path that is beyond a - * symbolic link will be prevented by load_patch_target() that - * is called at the beginning of apply_data() so we do not - * have to worry about a patch marked with "is_delete" bit - * here. We however need to make sure that the patch result - * is not deposited to a path that is beyond a symbolic link - * here. - */ - if (!patch->is_delete && path_is_beyond_symlink(patch->new_name)) - return error(_("affected file '%s' is beyond a symbolic link"), - patch->new_name); - - if (apply_data(patch, &st, ce) < 0) - return error(_("%s: patch does not apply"), name); - patch->rejected = 0; - return 0; -} - -static int check_patch_list(struct patch *patch) -{ - int err = 0; - - prepare_symlink_changes(patch); - prepare_fn_table(patch); - while (patch) { - if (apply_verbosely) - say_patch_name(stderr, - _("Checking patch %s..."), patch); - err |= check_patch(patch); - patch = patch->next; - } - return err; -} - -/* This function tries to read the sha1 from the current index */ -static int get_current_sha1(const char *path, unsigned char *sha1) -{ - int pos; - - if (read_cache() < 0) - return -1; - pos = cache_name_pos(path, strlen(path)); - if (pos < 0) - return -1; - hashcpy(sha1, active_cache[pos]->sha1); - return 0; -} - -static int preimage_sha1_in_gitlink_patch(struct patch *p, unsigned char sha1[20]) -{ - /* - * A usable gitlink patch has only one fragment (hunk) that looks like: - * @@ -1 +1 @@ - * -Subproject commit <old sha1> - * +Subproject commit <new sha1> - * or - * @@ -1 +0,0 @@ - * -Subproject commit <old sha1> - * for a removal patch. - */ - struct fragment *hunk = p->fragments; - static const char heading[] = "-Subproject commit "; - char *preimage; - - if (/* does the patch have only one hunk? */ - hunk && !hunk->next && - /* is its preimage one line? */ - hunk->oldpos == 1 && hunk->oldlines == 1 && - /* does preimage begin with the heading? */ - (preimage = memchr(hunk->patch, '\n', hunk->size)) != NULL && - starts_with(++preimage, heading) && - /* does it record full SHA-1? */ - !get_sha1_hex(preimage + sizeof(heading) - 1, sha1) && - preimage[sizeof(heading) + 40 - 1] == '\n' && - /* does the abbreviated name on the index line agree with it? */ - starts_with(preimage + sizeof(heading) - 1, p->old_sha1_prefix)) - return 0; /* it all looks fine */ - - /* we may have full object name on the index line */ - return get_sha1_hex(p->old_sha1_prefix, sha1); -} - -/* Build an index that contains the just the files needed for a 3way merge */ -static void build_fake_ancestor(struct patch *list, const char *filename) -{ - struct patch *patch; - struct index_state result = { NULL }; - static struct lock_file lock; - - /* Once we start supporting the reverse patch, it may be - * worth showing the new sha1 prefix, but until then... - */ - for (patch = list; patch; patch = patch->next) { - unsigned char sha1[20]; - struct cache_entry *ce; - const char *name; - - name = patch->old_name ? patch->old_name : patch->new_name; - if (0 < patch->is_new) - continue; - - if (S_ISGITLINK(patch->old_mode)) { - if (!preimage_sha1_in_gitlink_patch(patch, sha1)) - ; /* ok, the textual part looks sane */ - else - die("sha1 information is lacking or useless for submodule %s", - name); - } else if (!get_sha1_blob(patch->old_sha1_prefix, sha1)) { - ; /* ok */ - } else if (!patch->lines_added && !patch->lines_deleted) { - /* mode-only change: update the current */ - if (get_current_sha1(patch->old_name, sha1)) - die("mode change for %s, which is not " - "in current HEAD", name); - } else - die("sha1 information is lacking or useless " - "(%s).", name); - - ce = make_cache_entry(patch->old_mode, sha1, name, 0, 0); - if (!ce) - die(_("make_cache_entry failed for path '%s'"), name); - if (add_index_entry(&result, ce, ADD_CACHE_OK_TO_ADD)) - die ("Could not add %s to temporary index", name); - } - - hold_lock_file_for_update(&lock, filename, LOCK_DIE_ON_ERROR); - if (write_locked_index(&result, &lock, COMMIT_LOCK)) - die ("Could not write temporary index to %s", filename); - - discard_index(&result); -} - -static void stat_patch_list(struct patch *patch) -{ - int files, adds, dels; - - for (files = adds = dels = 0 ; patch ; patch = patch->next) { - files++; - adds += patch->lines_added; - dels += patch->lines_deleted; - show_stats(patch); - } - - print_stat_summary(stdout, files, adds, dels); -} - -static void numstat_patch_list(struct patch *patch) -{ - for ( ; patch; patch = patch->next) { - const char *name; - name = patch->new_name ? patch->new_name : patch->old_name; - if (patch->is_binary) - printf("-\t-\t"); - else - printf("%d\t%d\t", patch->lines_added, patch->lines_deleted); - write_name_quoted(name, stdout, line_termination); - } -} - -static void show_file_mode_name(const char *newdelete, unsigned int mode, const char *name) -{ - if (mode) - printf(" %s mode %06o %s\n", newdelete, mode, name); - else - printf(" %s %s\n", newdelete, name); -} - -static void show_mode_change(struct patch *p, int show_name) -{ - if (p->old_mode && p->new_mode && p->old_mode != p->new_mode) { - if (show_name) - printf(" mode change %06o => %06o %s\n", - p->old_mode, p->new_mode, p->new_name); - else - printf(" mode change %06o => %06o\n", - p->old_mode, p->new_mode); - } -} - -static void show_rename_copy(struct patch *p) -{ - const char *renamecopy = p->is_rename ? "rename" : "copy"; - const char *old, *new; - - /* Find common prefix */ - old = p->old_name; - new = p->new_name; - while (1) { - const char *slash_old, *slash_new; - slash_old = strchr(old, '/'); - slash_new = strchr(new, '/'); - if (!slash_old || - !slash_new || - slash_old - old != slash_new - new || - memcmp(old, new, slash_new - new)) - break; - old = slash_old + 1; - new = slash_new + 1; - } - /* p->old_name thru old is the common prefix, and old and new - * through the end of names are renames - */ - if (old != p->old_name) - printf(" %s %.*s{%s => %s} (%d%%)\n", renamecopy, - (int)(old - p->old_name), p->old_name, - old, new, p->score); - else - printf(" %s %s => %s (%d%%)\n", renamecopy, - p->old_name, p->new_name, p->score); - show_mode_change(p, 0); -} - -static void summary_patch_list(struct patch *patch) -{ - struct patch *p; - - for (p = patch; p; p = p->next) { - if (p->is_new) - show_file_mode_name("create", p->new_mode, p->new_name); - else if (p->is_delete) - show_file_mode_name("delete", p->old_mode, p->old_name); - else { - if (p->is_rename || p->is_copy) - show_rename_copy(p); - else { - if (p->score) { - printf(" rewrite %s (%d%%)\n", - p->new_name, p->score); - show_mode_change(p, 0); - } - else - show_mode_change(p, 1); - } - } - } -} - -static void patch_stats(struct patch *patch) -{ - int lines = patch->lines_added + patch->lines_deleted; - - if (lines > max_change) - max_change = lines; - if (patch->old_name) { - int len = quote_c_style(patch->old_name, NULL, NULL, 0); - if (!len) - len = strlen(patch->old_name); - if (len > max_len) - max_len = len; - } - if (patch->new_name) { - int len = quote_c_style(patch->new_name, NULL, NULL, 0); - if (!len) - len = strlen(patch->new_name); - if (len > max_len) - max_len = len; - } -} - -static void remove_file(struct patch *patch, int rmdir_empty) -{ - if (update_index) { - if (remove_file_from_cache(patch->old_name) < 0) - die(_("unable to remove %s from index"), patch->old_name); - } - if (!cached) { - if (!remove_or_warn(patch->old_mode, patch->old_name) && rmdir_empty) { - remove_path(patch->old_name); - } - } -} - -static void add_index_file(const char *path, unsigned mode, void *buf, unsigned long size) -{ - struct stat st; - struct cache_entry *ce; - int namelen = strlen(path); - unsigned ce_size = cache_entry_size(namelen); - - if (!update_index) - return; - - ce = xcalloc(1, ce_size); - memcpy(ce->name, path, namelen); - ce->ce_mode = create_ce_mode(mode); - ce->ce_flags = create_ce_flags(0); - ce->ce_namelen = namelen; - if (S_ISGITLINK(mode)) { - const char *s; - - if (!skip_prefix(buf, "Subproject commit ", &s) || - get_sha1_hex(s, ce->sha1)) - die(_("corrupt patch for submodule %s"), path); - } else { - if (!cached) { - if (lstat(path, &st) < 0) - die_errno(_("unable to stat newly created file '%s'"), - path); - fill_stat_cache_info(ce, &st); - } - if (write_sha1_file(buf, size, blob_type, ce->sha1) < 0) - die(_("unable to create backing store for newly created file %s"), path); - } - if (add_cache_entry(ce, ADD_CACHE_OK_TO_ADD) < 0) - die(_("unable to add cache entry for %s"), path); -} - -static int try_create_file(const char *path, unsigned int mode, const char *buf, unsigned long size) -{ - int fd; - struct strbuf nbuf = STRBUF_INIT; - - if (S_ISGITLINK(mode)) { - struct stat st; - if (!lstat(path, &st) && S_ISDIR(st.st_mode)) - return 0; - return mkdir(path, 0777); - } - - if (has_symlinks && S_ISLNK(mode)) - /* Although buf:size is counted string, it also is NUL - * terminated. - */ - return symlink(buf, path); - - fd = open(path, O_CREAT | O_EXCL | O_WRONLY, (mode & 0100) ? 0777 : 0666); - if (fd < 0) - return -1; - - if (convert_to_working_tree(path, buf, size, &nbuf)) { - size = nbuf.len; - buf = nbuf.buf; - } - write_or_die(fd, buf, size); - strbuf_release(&nbuf); - - if (close(fd) < 0) - die_errno(_("closing file '%s'"), path); - return 0; -} - -/* - * We optimistically assume that the directories exist, - * which is true 99% of the time anyway. If they don't, - * we create them and try again. - */ -static void create_one_file(char *path, unsigned mode, const char *buf, unsigned long size) -{ - if (cached) - return; - if (!try_create_file(path, mode, buf, size)) - return; - - if (errno == ENOENT) { - if (safe_create_leading_directories(path)) - return; - if (!try_create_file(path, mode, buf, size)) - return; - } - - if (errno == EEXIST || errno == EACCES) { - /* We may be trying to create a file where a directory - * used to be. - */ - struct stat st; - if (!lstat(path, &st) && (!S_ISDIR(st.st_mode) || !rmdir(path))) - errno = EEXIST; - } - - if (errno == EEXIST) { - unsigned int nr = getpid(); - - for (;;) { - char newpath[PATH_MAX]; - mksnpath(newpath, sizeof(newpath), "%s~%u", path, nr); - if (!try_create_file(newpath, mode, buf, size)) { - if (!rename(newpath, path)) - return; - unlink_or_warn(newpath); - break; - } - if (errno != EEXIST) - break; - ++nr; - } - } - die_errno(_("unable to write file '%s' mode %o"), path, mode); -} - -static void add_conflicted_stages_file(struct patch *patch) -{ - int stage, namelen; - unsigned ce_size, mode; - struct cache_entry *ce; - - if (!update_index) - return; - namelen = strlen(patch->new_name); - ce_size = cache_entry_size(namelen); - mode = patch->new_mode ? patch->new_mode : (S_IFREG | 0644); - - remove_file_from_cache(patch->new_name); - for (stage = 1; stage < 4; stage++) { - if (is_null_oid(&patch->threeway_stage[stage - 1])) - continue; - ce = xcalloc(1, ce_size); - memcpy(ce->name, patch->new_name, namelen); - ce->ce_mode = create_ce_mode(mode); - ce->ce_flags = create_ce_flags(stage); - ce->ce_namelen = namelen; - hashcpy(ce->sha1, patch->threeway_stage[stage - 1].hash); - if (add_cache_entry(ce, ADD_CACHE_OK_TO_ADD) < 0) - die(_("unable to add cache entry for %s"), patch->new_name); - } -} - -static void create_file(struct patch *patch) -{ - char *path = patch->new_name; - unsigned mode = patch->new_mode; - unsigned long size = patch->resultsize; - char *buf = patch->result; - - if (!mode) - mode = S_IFREG | 0644; - create_one_file(path, mode, buf, size); - - if (patch->conflicted_threeway) - add_conflicted_stages_file(patch); - else - add_index_file(path, mode, buf, size); -} - -/* phase zero is to remove, phase one is to create */ -static void write_out_one_result(struct patch *patch, int phase) -{ - if (patch->is_delete > 0) { - if (phase == 0) - remove_file(patch, 1); - return; - } - if (patch->is_new > 0 || patch->is_copy) { - if (phase == 1) - create_file(patch); - return; - } - /* - * Rename or modification boils down to the same - * thing: remove the old, write the new - */ - if (phase == 0) - remove_file(patch, patch->is_rename); - if (phase == 1) - create_file(patch); -} - -static int write_out_one_reject(struct patch *patch) -{ - FILE *rej; - char namebuf[PATH_MAX]; - struct fragment *frag; - int cnt = 0; - struct strbuf sb = STRBUF_INIT; - - for (cnt = 0, frag = patch->fragments; frag; frag = frag->next) { - if (!frag->rejected) - continue; - cnt++; - } - - if (!cnt) { - if (apply_verbosely) - say_patch_name(stderr, - _("Applied patch %s cleanly."), patch); - return 0; - } - - /* This should not happen, because a removal patch that leaves - * contents are marked "rejected" at the patch level. - */ - if (!patch->new_name) - die(_("internal error")); - - /* Say this even without --verbose */ - strbuf_addf(&sb, Q_("Applying patch %%s with %d reject...", - "Applying patch %%s with %d rejects...", - cnt), - cnt); - say_patch_name(stderr, sb.buf, patch); - strbuf_release(&sb); - - cnt = strlen(patch->new_name); - if (ARRAY_SIZE(namebuf) <= cnt + 5) { - cnt = ARRAY_SIZE(namebuf) - 5; - warning(_("truncating .rej filename to %.*s.rej"), - cnt - 1, patch->new_name); - } - memcpy(namebuf, patch->new_name, cnt); - memcpy(namebuf + cnt, ".rej", 5); - - rej = fopen(namebuf, "w"); - if (!rej) - return error(_("cannot open %s: %s"), namebuf, strerror(errno)); - - /* Normal git tools never deal with .rej, so do not pretend - * this is a git patch by saying --git or giving extended - * headers. While at it, maybe please "kompare" that wants - * the trailing TAB and some garbage at the end of line ;-). - */ - fprintf(rej, "diff a/%s b/%s\t(rejected hunks)\n", - patch->new_name, patch->new_name); - for (cnt = 1, frag = patch->fragments; - frag; - cnt++, frag = frag->next) { - if (!frag->rejected) { - fprintf_ln(stderr, _("Hunk #%d applied cleanly."), cnt); - continue; - } - fprintf_ln(stderr, _("Rejected hunk #%d."), cnt); - fprintf(rej, "%.*s", frag->size, frag->patch); - if (frag->patch[frag->size-1] != '\n') - fputc('\n', rej); - } - fclose(rej); - return -1; -} - -static int write_out_results(struct patch *list) -{ - int phase; - int errs = 0; - struct patch *l; - struct string_list cpath = STRING_LIST_INIT_DUP; - - for (phase = 0; phase < 2; phase++) { - l = list; - while (l) { - if (l->rejected) - errs = 1; - else { - write_out_one_result(l, phase); - if (phase == 1) { - if (write_out_one_reject(l)) - errs = 1; - if (l->conflicted_threeway) { - string_list_append(&cpath, l->new_name); - errs = 1; - } - } - } - l = l->next; - } - } - - if (cpath.nr) { - struct string_list_item *item; - - string_list_sort(&cpath); - for_each_string_list_item(item, &cpath) - fprintf(stderr, "U %s\n", item->string); - string_list_clear(&cpath, 0); - - rerere(0); - } - - return errs; -} - static struct lock_file lock_file; -#define INACCURATE_EOF (1<<0) -#define RECOUNT (1<<1) - -static int apply_patch(int fd, const char *filename, int options) -{ - size_t offset; - struct strbuf buf = STRBUF_INIT; /* owns the patch text */ - struct patch *list = NULL, **listp = &list; - int skipped_patch = 0; - - patch_input_file = filename; - read_patch_file(&buf, fd); - offset = 0; - while (offset < buf.len) { - struct patch *patch; - int nr; - - patch = xcalloc(1, sizeof(*patch)); - patch->inaccurate_eof = !!(options & INACCURATE_EOF); - patch->recount = !!(options & RECOUNT); - nr = parse_chunk(buf.buf + offset, buf.len - offset, patch); - if (nr < 0) - break; - if (apply_in_reverse) - reverse_patches(patch); - if (use_patch(patch)) { - patch_stats(patch); - *listp = patch; - listp = &patch->next; - } - else { - free_patch(patch); - skipped_patch++; - } - offset += nr; - } - - if (!list && !skipped_patch) - die(_("unrecognized input")); - - if (whitespace_error && (ws_error_action == die_on_ws_error)) - apply = 0; - - update_index = check_index && apply; - if (update_index && newfd < 0) - newfd = hold_locked_index(&lock_file, 1); - - if (check_index) { - if (read_cache() < 0) - die(_("unable to read index file")); - } - - if ((check || apply) && - check_patch_list(list) < 0 && - !apply_with_reject) - exit(1); - - if (apply && write_out_results(list)) { - if (apply_with_reject) - exit(1); - /* with --3way, we still need to write the index out */ - return 1; - } - - if (fake_ancestor) - build_fake_ancestor(list, fake_ancestor); - - if (diffstat) - stat_patch_list(list); - - if (numstat) - numstat_patch_list(list); - - if (summary) - summary_patch_list(list); - - free_patch_list(list); - strbuf_release(&buf); - string_list_clear(&fn_table, 0); - return 0; -} - -static void git_apply_config(void) -{ - git_config_get_string_const("apply.whitespace", &apply_default_whitespace); - git_config_get_string_const("apply.ignorewhitespace", &apply_default_ignorewhitespace); - git_config(git_default_config, NULL); -} - -static int option_parse_exclude(const struct option *opt, - const char *arg, int unset) -{ - add_name_limit(arg, 1); - return 0; -} - -static int option_parse_include(const struct option *opt, - const char *arg, int unset) +int cmd_apply(int argc, const char **argv, const char *prefix) { - add_name_limit(arg, 0); - has_include = 1; - return 0; -} - -static int option_parse_p(const struct option *opt, - const char *arg, int unset) -{ - p_value = atoi(arg); - p_value_known = 1; - return 0; -} - -static int option_parse_z(const struct option *opt, - const char *arg, int unset) -{ - if (unset) - line_termination = '\n'; - else - line_termination = 0; - return 0; -} - -static int option_parse_space_change(const struct option *opt, - const char *arg, int unset) -{ - if (unset) - ws_ignore_action = ignore_ws_none; - else - ws_ignore_action = ignore_ws_change; - return 0; -} - -static int option_parse_whitespace(const struct option *opt, - const char *arg, int unset) -{ - const char **whitespace_option = opt->value; - - *whitespace_option = arg; - parse_whitespace_option(arg); - return 0; -} - -static int option_parse_directory(const struct option *opt, - const char *arg, int unset) -{ - root_len = strlen(arg); - if (root_len && arg[root_len - 1] != '/') { - char *new_root; - root = new_root = xmalloc(root_len + 2); - strcpy(new_root, arg); - strcpy(new_root + root_len++, "/"); - } else - root = arg; - return 0; -} - -int cmd_apply(int argc, const char **argv, const char *prefix_) -{ - int i; - int errs = 0; - int is_not_gitdir = !startup_info->have_repository; int force_apply = 0; + int options = 0; + int ret; + struct apply_state state; - const char *whitespace_option = NULL; - - struct option builtin_apply_options[] = { - { OPTION_CALLBACK, 0, "exclude", NULL, N_("path"), - N_("don't apply changes matching the given path"), - 0, option_parse_exclude }, - { OPTION_CALLBACK, 0, "include", NULL, N_("path"), - N_("apply changes matching the given path"), - 0, option_parse_include }, - { OPTION_CALLBACK, 'p', NULL, NULL, N_("num"), - N_("remove <num> leading slashes from traditional diff paths"), - 0, option_parse_p }, - OPT_BOOL(0, "no-add", &no_add, - N_("ignore additions made by the patch")), - OPT_BOOL(0, "stat", &diffstat, - N_("instead of applying the patch, output diffstat for the input")), - OPT_NOOP_NOARG(0, "allow-binary-replacement"), - OPT_NOOP_NOARG(0, "binary"), - OPT_BOOL(0, "numstat", &numstat, - N_("show number of added and deleted lines in decimal notation")), - OPT_BOOL(0, "summary", &summary, - N_("instead of applying the patch, output a summary for the input")), - OPT_BOOL(0, "check", &check, - N_("instead of applying the patch, see if the patch is applicable")), - OPT_BOOL(0, "index", &check_index, - N_("make sure the patch is applicable to the current index")), - OPT_BOOL(0, "cached", &cached, - N_("apply a patch without touching the working tree")), - OPT_BOOL(0, "unsafe-paths", &unsafe_paths, - N_("accept a patch that touches outside the working area")), - OPT_BOOL(0, "apply", &force_apply, - N_("also apply the patch (use with --stat/--summary/--check)")), - OPT_BOOL('3', "3way", &threeway, - N_( "attempt three-way merge if a patch does not apply")), - OPT_FILENAME(0, "build-fake-ancestor", &fake_ancestor, - N_("build a temporary index based on embedded index information")), - { OPTION_CALLBACK, 'z', NULL, NULL, NULL, - N_("paths are separated with NUL character"), - PARSE_OPT_NOARG, option_parse_z }, - OPT_INTEGER('C', NULL, &p_context, - N_("ensure at least <n> lines of context match")), - { OPTION_CALLBACK, 0, "whitespace", &whitespace_option, N_("action"), - N_("detect new or modified lines that have whitespace errors"), - 0, option_parse_whitespace }, - { OPTION_CALLBACK, 0, "ignore-space-change", NULL, NULL, - N_("ignore changes in whitespace when finding context"), - PARSE_OPT_NOARG, option_parse_space_change }, - { OPTION_CALLBACK, 0, "ignore-whitespace", NULL, NULL, - N_("ignore changes in whitespace when finding context"), - PARSE_OPT_NOARG, option_parse_space_change }, - OPT_BOOL('R', "reverse", &apply_in_reverse, - N_("apply the patch in reverse")), - OPT_BOOL(0, "unidiff-zero", &unidiff_zero, - N_("don't expect at least one line of context")), - OPT_BOOL(0, "reject", &apply_with_reject, - N_("leave the rejected hunks in corresponding *.rej files")), - OPT_BOOL(0, "allow-overlap", &allow_overlap, - N_("allow overlapping hunks")), - OPT__VERBOSE(&apply_verbosely, N_("be verbose")), - OPT_BIT(0, "inaccurate-eof", &options, - N_("tolerate incorrectly detected missing new-line at the end of file"), - INACCURATE_EOF), - OPT_BIT(0, "recount", &options, - N_("do not trust the line counts in the hunk headers"), - RECOUNT), - { OPTION_CALLBACK, 0, "directory", NULL, N_("root"), - N_("prepend <root> to all filenames"), - 0, option_parse_directory }, - OPT_END() - }; - - prefix = prefix_; - prefix_length = prefix ? strlen(prefix) : 0; - git_apply_config(); - if (apply_default_whitespace) - parse_whitespace_option(apply_default_whitespace); - if (apply_default_ignorewhitespace) - parse_ignorewhitespace_option(apply_default_ignorewhitespace); - - argc = parse_options(argc, argv, prefix, builtin_apply_options, - apply_usage, 0); - - if (apply_with_reject && threeway) - die("--reject and --3way cannot be used together."); - if (cached && threeway) - die("--cached and --3way cannot be used together."); - if (threeway) { - if (is_not_gitdir) - die(_("--3way outside a repository")); - check_index = 1; - } - if (apply_with_reject) - apply = apply_verbosely = 1; - if (!force_apply && (diffstat || numstat || summary || check || fake_ancestor)) - apply = 0; - if (check_index && is_not_gitdir) - die(_("--index outside a repository")); - if (cached) { - if (is_not_gitdir) - die(_("--cached outside a repository")); - check_index = 1; - } - if (check_index) - unsafe_paths = 0; + if (init_apply_state(&state, prefix, &lock_file)) + exit(128); - for (i = 0; i < argc; i++) { - const char *arg = argv[i]; - int fd; + argc = apply_parse_options(argc, argv, + &state, &force_apply, &options, + apply_usage); - if (!strcmp(arg, "-")) { - errs |= apply_patch(0, "<stdin>", options); - read_stdin = 0; - continue; - } else if (0 < prefix_length) - arg = prefix_filename(prefix, prefix_length, arg); + if (check_apply_state(&state, force_apply)) + exit(128); - fd = open(arg, O_RDONLY); - if (fd < 0) - die_errno(_("can't open patch '%s'"), arg); - read_stdin = 0; - set_default_whitespace_mode(whitespace_option); - errs |= apply_patch(fd, arg, options); - close(fd); - } - set_default_whitespace_mode(whitespace_option); - if (read_stdin) - errs |= apply_patch(0, "<stdin>", options); - if (whitespace_error) { - if (squelch_whitespace_errors && - squelch_whitespace_errors < whitespace_error) { - int squelched = - whitespace_error - squelch_whitespace_errors; - warning(Q_("squelched %d whitespace error", - "squelched %d whitespace errors", - squelched), - squelched); - } - if (ws_error_action == die_on_ws_error) - die(Q_("%d line adds whitespace errors.", - "%d lines add whitespace errors.", - whitespace_error), - whitespace_error); - if (applied_after_fixing_ws && apply) - warning("%d line%s applied after" - " fixing whitespace errors.", - applied_after_fixing_ws, - applied_after_fixing_ws == 1 ? "" : "s"); - else if (whitespace_error) - warning(Q_("%d line adds whitespace errors.", - "%d lines add whitespace errors.", - whitespace_error), - whitespace_error); - } + ret = apply_all_patches(&state, argc, argv, options); - if (update_index) { - if (write_locked_index(&the_index, &lock_file, COMMIT_LOCK)) - die(_("Unable to write new index file")); - } + clear_apply_state(&state); - return !!errs; + return ret; } diff --git a/builtin/archive.c b/builtin/archive.c index a1e3b940c2..f863465a0f 100644 --- a/builtin/archive.c +++ b/builtin/archive.c @@ -47,10 +47,10 @@ static int run_remote_archiver(int argc, const char **argv, if (name_hint) { const char *format = archive_format_from_filename(name_hint); if (format) - packet_write(fd[1], "argument --format=%s\n", format); + packet_write_fmt(fd[1], "argument --format=%s\n", format); } for (i = 1; i < argc; i++) - packet_write(fd[1], "argument %s\n", argv[i]); + packet_write_fmt(fd[1], "argument %s\n", argv[i]); packet_flush(fd[1]); buf = packet_read_line(fd[0], NULL); @@ -85,8 +85,8 @@ int cmd_archive(int argc, const char **argv, const char *prefix) const char *output = NULL; const char *remote = NULL; struct option local_opts[] = { - OPT_STRING('o', "output", &output, N_("file"), - N_("write the archive to this file")), + OPT_FILENAME('o', "output", &output, + N_("write the archive to this file")), OPT_STRING(0, "remote", &remote, N_("repo"), N_("retrieve the archive from remote repository <repo>")), OPT_STRING(0, "exec", &exec, N_("command"), @@ -105,5 +105,5 @@ int cmd_archive(int argc, const char **argv, const char *prefix) setvbuf(stderr, NULL, _IOLBF, BUFSIZ); - return write_archive(argc, argv, prefix, 1, output, 0); + return write_archive(argc, argv, prefix, output, 0); } diff --git a/builtin/blame.c b/builtin/blame.c index b3e948e757..3aae19a0f9 100644 --- a/builtin/blame.c +++ b/builtin/blame.c @@ -6,6 +6,7 @@ */ #include "cache.h" +#include "refs.h" #include "builtin.h" #include "blob.h" #include "commit.h" @@ -27,6 +28,7 @@ #include "line-range.h" #include "line-log.h" #include "dir.h" +#include "progress.h" static char blame_usage[] = N_("git blame [<options>] [<rev-opts>] [<rev>] [--] <file>"); @@ -49,11 +51,12 @@ static int incremental; static int xdl_opts; static int abbrev = -1; static int no_whole_file_rename; +static int show_progress; -static enum date_mode blame_date_mode = DATE_ISO8601; +static struct date_mode blame_date_mode = { DATE_ISO8601 }; static size_t blame_date_width; -static struct string_list mailmap; +static struct string_list mailmap = STRING_LIST_INIT_NODUP; #ifndef DEBUG #define DEBUG 0 @@ -117,7 +120,7 @@ struct origin { */ struct blame_entry *suspects; mmfile_t file; - unsigned char blob_sha1[20]; + struct object_id blob_oid; unsigned mode; /* guilty gets set when shipping any suspects to the final * blame list instead of other commits @@ -126,7 +129,12 @@ struct origin { char path[FLEX_ARRAY]; }; -static int diff_hunks(mmfile_t *file_a, mmfile_t *file_b, long ctxlen, +struct progress_info { + struct progress *progress; + int blamed_lines; +}; + +static int diff_hunks(mmfile_t *file_a, mmfile_t *file_b, xdl_emit_hunk_consume_func_t hunk_func, void *cb_data) { xpparam_t xpp = {0}; @@ -134,7 +142,6 @@ static int diff_hunks(mmfile_t *file_a, mmfile_t *file_b, long ctxlen, xdemitcb_t ecb = {NULL}; xpp.flags = xdl_opts; - xecfg.ctxlen = ctxlen; xecfg.hunk_func = hunk_func; ecb.priv = cb_data; return xdi_diff(file_a, file_b, &xpp, &xecfg, &ecb); @@ -147,8 +154,8 @@ static int diff_hunks(mmfile_t *file_a, mmfile_t *file_b, long ctxlen, */ int textconv_object(const char *path, unsigned mode, - const unsigned char *sha1, - int sha1_valid, + const struct object_id *oid, + int oid_valid, char **buf, unsigned long *buf_size) { @@ -156,7 +163,7 @@ int textconv_object(const char *path, struct userdiff_driver *textconv; df = alloc_filespec(path); - fill_filespec(df, sha1, sha1_valid, mode); + fill_filespec(df, oid->hash, oid_valid, mode); textconv = get_textconv(df); if (!textconv) { free_filespec(df); @@ -181,15 +188,16 @@ static void fill_origin_blob(struct diff_options *opt, num_read_blob++; if (DIFF_OPT_TST(opt, ALLOW_TEXTCONV) && - textconv_object(o->path, o->mode, o->blob_sha1, 1, &file->ptr, &file_size)) + textconv_object(o->path, o->mode, &o->blob_oid, 1, &file->ptr, &file_size)) ; else - file->ptr = read_sha1_file(o->blob_sha1, &type, &file_size); + file->ptr = read_sha1_file(o->blob_oid.hash, &type, + &file_size); file->size = file_size; if (!file->ptr) die("Cannot read blob %s for path %s", - sha1_to_hex(o->blob_sha1), + oid_to_hex(&o->blob_oid), o->path); o->file = *file; } @@ -458,12 +466,11 @@ static void queue_blames(struct scoreboard *sb, struct origin *porigin, static struct origin *make_origin(struct commit *commit, const char *path) { struct origin *o; - o = xcalloc(1, sizeof(*o) + strlen(path) + 1); + FLEX_ALLOC_STR(o, path, path); o->commit = commit; o->refcnt = 1; o->next = commit->util; commit->util = o; - strcpy(o->path, path); return o; } @@ -502,17 +509,17 @@ static struct origin *get_origin(struct scoreboard *sb, */ static int fill_blob_sha1_and_mode(struct origin *origin) { - if (!is_null_sha1(origin->blob_sha1)) + if (!is_null_oid(&origin->blob_oid)) return 0; - if (get_tree_entry(origin->commit->object.sha1, + if (get_tree_entry(origin->commit->object.oid.hash, origin->path, - origin->blob_sha1, &origin->mode)) + origin->blob_oid.hash, &origin->mode)) goto error_out; - if (sha1_object_info(origin->blob_sha1, NULL) != OBJ_BLOB) + if (sha1_object_info(origin->blob_oid.hash, NULL) != OBJ_BLOB) goto error_out; return 0; error_out: - hashclr(origin->blob_sha1); + oidclr(&origin->blob_oid); origin->mode = S_IFINVALID; return -1; } @@ -555,18 +562,18 @@ static struct origin *find_origin(struct scoreboard *sb, PATHSPEC_LITERAL_PATH, "", paths); diff_setup_done(&diff_opts); - if (is_null_sha1(origin->commit->object.sha1)) - do_diff_cache(parent->tree->object.sha1, &diff_opts); + if (is_null_oid(&origin->commit->object.oid)) + do_diff_cache(parent->tree->object.oid.hash, &diff_opts); else - diff_tree_sha1(parent->tree->object.sha1, - origin->commit->tree->object.sha1, + diff_tree_sha1(parent->tree->object.oid.hash, + origin->commit->tree->object.oid.hash, "", &diff_opts); diffcore_std(&diff_opts); if (!diff_queued_diff.nr) { /* The path is the same as parent */ porigin = get_origin(sb, parent, origin->path); - hashcpy(porigin->blob_sha1, origin->blob_sha1); + oidcpy(&porigin->blob_oid, &origin->blob_oid); porigin->mode = origin->mode; } else { /* @@ -592,7 +599,7 @@ static struct origin *find_origin(struct scoreboard *sb, p->status); case 'M': porigin = get_origin(sb, parent, origin->path); - hashcpy(porigin->blob_sha1, p->one->sha1); + oidcpy(&porigin->blob_oid, &p->one->oid); porigin->mode = p->one->mode; break; case 'A': @@ -602,7 +609,7 @@ static struct origin *find_origin(struct scoreboard *sb, } } diff_flush(&diff_opts); - free_pathspec(&diff_opts.pathspec); + clear_pathspec(&diff_opts.pathspec); return porigin; } @@ -625,11 +632,11 @@ static struct origin *find_rename(struct scoreboard *sb, diff_opts.single_follow = origin->path; diff_setup_done(&diff_opts); - if (is_null_sha1(origin->commit->object.sha1)) - do_diff_cache(parent->tree->object.sha1, &diff_opts); + if (is_null_oid(&origin->commit->object.oid)) + do_diff_cache(parent->tree->object.oid.hash, &diff_opts); else - diff_tree_sha1(parent->tree->object.sha1, - origin->commit->tree->object.sha1, + diff_tree_sha1(parent->tree->object.oid.hash, + origin->commit->tree->object.oid.hash, "", &diff_opts); diffcore_std(&diff_opts); @@ -638,13 +645,13 @@ static struct origin *find_rename(struct scoreboard *sb, if ((p->status == 'R' || p->status == 'C') && !strcmp(p->two->path, origin->path)) { porigin = get_origin(sb, parent, p->one->path); - hashcpy(porigin->blob_sha1, p->one->sha1); + oidcpy(&porigin->blob_oid, &p->one->oid); porigin->mode = p->one->mode; break; } } diff_flush(&diff_opts); - free_pathspec(&diff_opts.pathspec); + clear_pathspec(&diff_opts.pathspec); return porigin; } @@ -973,7 +980,10 @@ static void pass_blame_to_parent(struct scoreboard *sb, fill_origin_blob(&sb->revs->diffopt, target, &file_o); num_get_patch++; - diff_hunks(&file_p, &file_o, 0, blame_chunk_cb, &d); + if (diff_hunks(&file_p, &file_o, blame_chunk_cb, &d)) + die("unable to generate diff (%s -> %s)", + oid_to_hex(&parent->commit->object.oid), + oid_to_hex(&target->commit->object.oid)); /* The rest are the same as the parent */ blame_chunk(&d.dstq, &d.srcq, INT_MAX, d.offset, INT_MAX, parent); *d.dstq = NULL; @@ -1119,7 +1129,9 @@ static void find_copy_in_blob(struct scoreboard *sb, * file_p partially may match that image. */ memset(split, 0, sizeof(struct blame_entry [3])); - diff_hunks(file_p, &file_o, 1, handle_split_cb, &d); + if (diff_hunks(file_p, &file_o, handle_split_cb, &d)) + die("unable to generate diff (%s)", + oid_to_hex(&parent->commit->object.oid)); /* remainder, if any, all match the preimage */ handle_split(sb, ent, d.tlno, d.plno, ent->num_lines, parent, split); } @@ -1268,11 +1280,11 @@ static void find_copy_in_parent(struct scoreboard *sb, && (!porigin || strcmp(target->path, porigin->path)))) DIFF_OPT_SET(&diff_opts, FIND_COPIES_HARDER); - if (is_null_sha1(target->commit->object.sha1)) - do_diff_cache(parent->tree->object.sha1, &diff_opts); + if (is_null_oid(&target->commit->object.oid)) + do_diff_cache(parent->tree->object.oid.hash, &diff_opts); else - diff_tree_sha1(parent->tree->object.sha1, - target->commit->tree->object.sha1, + diff_tree_sha1(parent->tree->object.oid.hash, + target->commit->tree->object.oid.hash, "", &diff_opts); if (!DIFF_OPT_TST(&diff_opts, FIND_COPIES_HARDER)) @@ -1297,7 +1309,7 @@ static void find_copy_in_parent(struct scoreboard *sb, continue; norigin = get_origin(sb, parent, p->one->path); - hashcpy(norigin->blob_sha1, p->one->sha1); + oidcpy(&norigin->blob_oid, &p->one->oid); norigin->mode = p->one->mode; fill_origin_blob(&sb->revs->diffopt, norigin, &file_p); if (!file_p.ptr) @@ -1331,7 +1343,7 @@ static void find_copy_in_parent(struct scoreboard *sb, } while (unblamed); target->suspects = reverse_blame(leftover, NULL); diff_flush(&diff_opts); - free_pathspec(&diff_opts.pathspec); + clear_pathspec(&diff_opts.pathspec); } /* @@ -1365,8 +1377,15 @@ static void pass_whole_blame(struct scoreboard *sb, */ static struct commit_list *first_scapegoat(struct rev_info *revs, struct commit *commit) { - if (!reverse) + if (!reverse) { + if (revs->first_parent_only && + commit->parents && + commit->parents->next) { + free_commit_list(commit->parents->next); + commit->parents->next = NULL; + } return commit->parents; + } return lookup_decoration(&revs->children, &commit->object); } @@ -1440,15 +1459,14 @@ static void pass_blame(struct scoreboard *sb, struct origin *origin, int opt) porigin = find(sb, p, origin); if (!porigin) continue; - if (!hashcmp(porigin->blob_sha1, origin->blob_sha1)) { + if (!oidcmp(&porigin->blob_oid, &origin->blob_oid)) { pass_whole_blame(sb, origin, porigin); origin_decref(porigin); goto finish; } for (j = same = 0; j < i; j++) if (sg_origin[j] && - !hashcmp(sg_origin[j]->blob_sha1, - porigin->blob_sha1)) { + !oidcmp(&sg_origin[j]->blob_oid, &porigin->blob_oid)) { same = 1; break; } @@ -1676,19 +1694,29 @@ static void get_commit_info(struct commit *commit, if (len) strbuf_add(&ret->summary, subject, len); else - strbuf_addf(&ret->summary, "(%s)", sha1_to_hex(commit->object.sha1)); + strbuf_addf(&ret->summary, "(%s)", oid_to_hex(&commit->object.oid)); unuse_commit_buffer(commit, message); } /* + * Write out any suspect information which depends on the path. This must be + * handled separately from emit_one_suspect_detail(), because a given commit + * may have changes in multiple paths. So this needs to appear each time + * we mention a new group. + * * To allow LF and other nonportable characters in pathnames, * they are c-style quoted as needed. */ -static void write_filename_info(const char *path) +static void write_filename_info(struct origin *suspect) { + if (suspect->previous) { + struct origin *prev = suspect->previous; + printf("previous %s ", oid_to_hex(&prev->commit->object.oid)); + write_name_quoted(prev->path, stdout, '\n'); + } printf("filename "); - write_name_quoted(path, stdout, '\n'); + write_name_quoted(suspect->path, stdout, '\n'); } /* @@ -1717,11 +1745,6 @@ static int emit_one_suspect_detail(struct origin *suspect, int repeat) printf("summary %s\n", ci.summary.buf); if (suspect->commit->object.flags & UNINTERESTING) printf("boundary\n"); - if (suspect->previous) { - struct origin *prev = suspect->previous; - printf("previous %s ", sha1_to_hex(prev->commit->object.sha1)); - write_name_quoted(prev->path, stdout, '\n'); - } commit_info_destroy(&ci); @@ -1732,18 +1755,21 @@ static int emit_one_suspect_detail(struct origin *suspect, int repeat) * The blame_entry is found to be guilty for the range. * Show it in incremental output. */ -static void found_guilty_entry(struct blame_entry *ent) +static void found_guilty_entry(struct blame_entry *ent, + struct progress_info *pi) { if (incremental) { struct origin *suspect = ent->suspect; printf("%s %d %d %d\n", - sha1_to_hex(suspect->commit->object.sha1), + oid_to_hex(&suspect->commit->object.oid), ent->s_lno + 1, ent->lno + 1, ent->num_lines); emit_one_suspect_detail(suspect, 0); - write_filename_info(suspect->path); + write_filename_info(suspect); maybe_flush_or_die(stdout, "stdout"); } + pi->blamed_lines += ent->num_lines; + display_progress(pi->progress, pi->blamed_lines); } /* @@ -1754,6 +1780,11 @@ static void assign_blame(struct scoreboard *sb, int opt) { struct rev_info *revs = sb->revs; struct commit *commit = prio_queue_get(&sb->commits); + struct progress_info pi = { NULL, 0 }; + + if (show_progress) + pi.progress = start_progress_delay(_("Blaming lines"), + sb->num_lines, 50, 1); while (commit) { struct blame_entry *ent; @@ -1795,7 +1826,7 @@ static void assign_blame(struct scoreboard *sb, int opt) suspect->guilty = 1; for (;;) { struct blame_entry *next = ent->next; - found_guilty_entry(ent); + found_guilty_entry(ent, &pi); if (next) { ent = next; continue; @@ -1811,6 +1842,8 @@ static void assign_blame(struct scoreboard *sb, int opt) if (DEBUG) /* sanity */ sanity_check_refcnt(sb); } + + stop_progress(&pi.progress); } static const char *format_time(unsigned long time, const char *tz_str, @@ -1827,7 +1860,7 @@ static const char *format_time(unsigned long time, const char *tz_str, size_t time_width; int tz; tz = atoi(tz_str); - time_str = show_date(time, tz, blame_date_mode); + time_str = show_date(time, tz, &blame_date_mode); strbuf_addstr(&time_buf, time_str); /* * Add space paddings to time_buf to display a fixed width @@ -1856,7 +1889,7 @@ 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); + write_filename_info(suspect); } static void emit_porcelain(struct scoreboard *sb, struct blame_entry *ent, @@ -1866,9 +1899,9 @@ static void emit_porcelain(struct scoreboard *sb, struct blame_entry *ent, int cnt; const char *cp; struct origin *suspect = ent->suspect; - char hex[41]; + char hex[GIT_SHA1_HEXSZ + 1]; - strcpy(hex, sha1_to_hex(suspect->commit->object.sha1)); + sha1_to_hex_r(hex, suspect->commit->object.oid.hash); printf("%s %d %d %d\n", hex, ent->s_lno + 1, @@ -1904,16 +1937,16 @@ static void emit_other(struct scoreboard *sb, struct blame_entry *ent, int opt) const char *cp; struct origin *suspect = ent->suspect; struct commit_info ci; - char hex[41]; + char hex[GIT_SHA1_HEXSZ + 1]; int show_raw_time = !!(opt & OUTPUT_RAW_TIMESTAMP); get_commit_info(suspect->commit, &ci, 1); - strcpy(hex, sha1_to_hex(suspect->commit->object.sha1)); + sha1_to_hex_r(hex, suspect->commit->object.oid.hash); cp = nth_line(sb, ent->lno); for (cnt = 0; cnt < ent->num_lines; cnt++) { char ch; - int length = (opt & OUTPUT_LONG_OBJECT_NAME) ? 40 : abbrev; + int length = (opt & OUTPUT_LONG_OBJECT_NAME) ? GIT_SHA1_HEXSZ : abbrev; if (suspect->commit->object.flags & UNINTERESTING) { if (blank_boundary) @@ -2028,7 +2061,8 @@ static int prepare_lines(struct scoreboard *sb) for (p = buf; p < end; p = get_next_line(p, end)) num++; - sb->lineno = lineno = xmalloc(sizeof(*sb->lineno) * (num + 1)); + ALLOC_ARRAY(sb->lineno, num + 1); + lineno = sb->lineno; for (p = buf; p < end; p = get_next_line(p, end)) *lineno++ = p - buf; @@ -2063,7 +2097,7 @@ static int read_ancestry(const char *graft_file) static int update_auto_abbrev(int auto_abbrev, struct origin *suspect) { - const char *uniq = find_unique_abbrev(suspect->commit->object.sha1, + const char *uniq = find_unique_abbrev(suspect->commit->object.oid.hash, auto_abbrev); int len = strlen(uniq); if (auto_abbrev < len) @@ -2082,7 +2116,7 @@ static void find_alignment(struct scoreboard *sb, int *option) unsigned largest_score = 0; struct blame_entry *e; int compute_auto_abbrev = (abbrev < 0); - int auto_abbrev = default_abbrev; + int auto_abbrev = DEFAULT_ABBREV; for (e = sb->ent; e; e = e->next) { struct origin *suspect = e->suspect; @@ -2139,7 +2173,7 @@ static void sanity_check_refcnt(struct scoreboard *sb) if (ent->suspect->refcnt <= 0) { fprintf(stderr, "%s in %s has negative refcnt %d\n", ent->suspect->path, - sha1_to_hex(ent->suspect->commit->object.sha1), + oid_to_hex(&ent->suspect->commit->object.oid), ent->suspect->refcnt); baa = 1; } @@ -2176,13 +2210,23 @@ static int git_blame_config(const char *var, const char *value, void *cb) blank_boundary = git_config_bool(var, value); return 0; } + if (!strcmp(var, "blame.showemail")) { + int *output_option = cb; + if (git_config_bool(var, value)) + *output_option |= OUTPUT_SHOW_EMAIL; + else + *output_option &= ~OUTPUT_SHOW_EMAIL; + return 0; + } if (!strcmp(var, "blame.date")) { if (!value) return config_error_nonbool(var); - blame_date_mode = parse_date_format(value); + parse_date_format(value, &blame_date_mode); return 0; } + if (git_diff_heuristic_config(var, value, cb) < 0) + return -1; if (userdiff_config(var, value) < 0) return -1; @@ -2192,47 +2236,55 @@ static int git_blame_config(const char *var, const char *value, void *cb) static void verify_working_tree_path(struct commit *work_tree, const char *path) { struct commit_list *parents; + int pos; for (parents = work_tree->parents; parents; parents = parents->next) { - const unsigned char *commit_sha1 = parents->item->object.sha1; - unsigned char blob_sha1[20]; + const struct object_id *commit_oid = &parents->item->object.oid; + struct object_id blob_oid; unsigned mode; - if (!get_tree_entry(commit_sha1, path, blob_sha1, &mode) && - sha1_object_info(blob_sha1, NULL) == OBJ_BLOB) + if (!get_tree_entry(commit_oid->hash, path, blob_oid.hash, &mode) && + sha1_object_info(blob_oid.hash, NULL) == OBJ_BLOB) return; } - die("no such path '%s' in HEAD", path); + + pos = cache_name_pos(path, strlen(path)); + if (pos >= 0) + ; /* path is in the index */ + else if (-1 - pos < active_nr && + !strcmp(active_cache[-1 - pos]->name, path)) + ; /* path is in the index, unmerged */ + else + die("no such path '%s' in HEAD", path); } -static struct commit_list **append_parent(struct commit_list **tail, const unsigned char *sha1) +static struct commit_list **append_parent(struct commit_list **tail, const struct object_id *oid) { struct commit *parent; - parent = lookup_commit_reference(sha1); + parent = lookup_commit_reference(oid->hash); if (!parent) - die("no such commit %s", sha1_to_hex(sha1)); + die("no such commit %s", oid_to_hex(oid)); return &commit_list_insert(parent, tail)->next; } static void append_merge_parents(struct commit_list **tail) { int merge_head; - const char *merge_head_file = git_path("MERGE_HEAD"); struct strbuf line = STRBUF_INIT; - merge_head = open(merge_head_file, O_RDONLY); + merge_head = open(git_path_merge_head(), O_RDONLY); if (merge_head < 0) { if (errno == ENOENT) return; - die("cannot open '%s' for reading", merge_head_file); + die("cannot open '%s' for reading", git_path_merge_head()); } while (!strbuf_getwholeline_fd(&line, merge_head, '\n')) { - unsigned char sha1[20]; - if (line.len < 40 || get_sha1_hex(line.buf, sha1)) - die("unknown line in '%s': %s", merge_head_file, line.buf); - tail = append_parent(tail, sha1); + struct object_id oid; + if (line.len < GIT_SHA1_HEXSZ || get_oid_hex(line.buf, &oid)) + die("unknown line in '%s': %s", git_path_merge_head(), line.buf); + tail = append_parent(tail, &oid); } close(merge_head); strbuf_release(&line); @@ -2261,7 +2313,7 @@ static struct commit *fake_working_tree_commit(struct diff_options *opt, struct commit *commit; struct origin *origin; struct commit_list **parent_tail, *parent; - unsigned char head_sha1[20]; + struct object_id head_oid; struct strbuf buf = STRBUF_INIT; const char *ident; time_t now; @@ -2270,16 +2322,17 @@ static struct commit *fake_working_tree_commit(struct diff_options *opt, unsigned mode; struct strbuf msg = STRBUF_INIT; + read_cache(); time(&now); commit = alloc_commit_node(); commit->object.parsed = 1; commit->date = now; parent_tail = &commit->parents; - if (!resolve_ref_unsafe("HEAD", RESOLVE_REF_READING, head_sha1, NULL)) + if (!resolve_ref_unsafe("HEAD", RESOLVE_REF_READING, head_oid.hash, NULL)) die("no such ref: HEAD"); - parent_tail = append_parent(parent_tail, head_sha1); + parent_tail = append_parent(parent_tail, &head_oid); append_merge_parents(parent_tail); verify_working_tree_path(commit, path); @@ -2289,7 +2342,7 @@ static struct commit *fake_working_tree_commit(struct diff_options *opt, strbuf_addstr(&msg, "tree 0000000000000000000000000000000000000000\n"); for (parent = commit->parents; parent; parent = parent->next) strbuf_addf(&msg, "parent %s\n", - sha1_to_hex(parent->item->object.sha1)); + oid_to_hex(&parent->item->object.oid)); strbuf_addf(&msg, "author %s\n" "committer %s\n\n" @@ -2320,7 +2373,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, mode, null_sha1, 0, &buf_ptr, &buf_len)) + textconv_object(read_from, mode, &null_oid, 0, &buf_ptr, &buf_len)) strbuf_attach(&buf, buf_ptr, buf_len, buf_len + 1); else if (strbuf_read_file(&buf, read_from, st.st_size) != st.st_size) die_errno("cannot open or read '%s'", read_from); @@ -2342,7 +2395,7 @@ static struct commit *fake_working_tree_commit(struct diff_options *opt, convert_to_git(path, buf.buf, buf.len, &buf, 0); origin->file.ptr = buf.buf; origin->file.size = buf.len; - pretend_sha1_file(buf.buf, buf.len, OBJ_BLOB, origin->blob_sha1); + pretend_sha1_file(buf.buf, buf.len, OBJ_BLOB, origin->blob_oid.hash); /* * Read the current index, replace the path entry with @@ -2364,49 +2417,83 @@ static struct commit *fake_working_tree_commit(struct diff_options *opt, } size = cache_entry_size(len); ce = xcalloc(1, size); - hashcpy(ce->sha1, origin->blob_sha1); + oidcpy(&ce->oid, &origin->blob_oid); memcpy(ce->name, path, len); ce->ce_flags = create_ce_flags(0); ce->ce_namelen = len; ce->ce_mode = create_ce_mode(mode); add_cache_entry(ce, ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE); - /* - * We are not going to write this out, so this does not matter - * right now, but someday we might optimize diff-index --cached - * with cache-tree information. - */ cache_tree_invalidate_path(&the_index, path); return commit; } -static char *prepare_final(struct scoreboard *sb) +static struct commit *find_single_final(struct rev_info *revs, + const char **name_p) { int i; - const char *final_commit_name = NULL; - struct rev_info *revs = sb->revs; + struct commit *found = NULL; + const char *name = NULL; - /* - * There must be one and only one positive commit in the - * revs->pending array. - */ for (i = 0; i < revs->pending.nr; i++) { struct object *obj = revs->pending.objects[i].item; if (obj->flags & UNINTERESTING) continue; - while (obj->type == OBJ_TAG) - obj = deref_tag(obj, NULL, 0); + obj = deref_tag(obj, NULL, 0); if (obj->type != OBJ_COMMIT) die("Non commit %s?", revs->pending.objects[i].name); - if (sb->final) + if (found) die("More than one commit to dig from %s and %s?", - revs->pending.objects[i].name, - final_commit_name); - sb->final = (struct commit *) obj; - final_commit_name = revs->pending.objects[i].name; + revs->pending.objects[i].name, name); + found = (struct commit *)obj; + name = revs->pending.objects[i].name; } - return xstrdup_or_null(final_commit_name); + if (name_p) + *name_p = name; + return found; +} + +static char *prepare_final(struct scoreboard *sb) +{ + const char *name; + sb->final = find_single_final(sb->revs, &name); + return xstrdup_or_null(name); +} + +static const char *dwim_reverse_initial(struct scoreboard *sb) +{ + /* + * DWIM "git blame --reverse ONE -- PATH" as + * "git blame --reverse ONE..HEAD -- PATH" but only do so + * when it makes sense. + */ + struct object *obj; + struct commit *head_commit; + unsigned char head_sha1[20]; + + if (sb->revs->pending.nr != 1) + return NULL; + + /* Is that sole rev a committish? */ + obj = sb->revs->pending.objects[0].item; + obj = deref_tag(obj, NULL, 0); + if (obj->type != OBJ_COMMIT) + return NULL; + + /* Do we have HEAD? */ + if (!resolve_ref_unsafe("HEAD", RESOLVE_REF_READING, head_sha1, NULL)) + return NULL; + head_commit = lookup_commit_reference_gently(head_sha1, 1); + if (!head_commit) + return NULL; + + /* Turn "ONE" into "ONE..HEAD" then */ + obj->flags |= UNINTERESTING; + add_pending_object(sb->revs, &head_commit->object, "HEAD"); + + sb->final = (struct commit *)obj; + return sb->revs->pending.objects[0].name; } static char *prepare_initial(struct scoreboard *sb) @@ -2423,19 +2510,21 @@ static char *prepare_initial(struct scoreboard *sb) struct object *obj = revs->pending.objects[i].item; if (!(obj->flags & UNINTERESTING)) continue; - while (obj->type == OBJ_TAG) - obj = deref_tag(obj, NULL, 0); + obj = deref_tag(obj, NULL, 0); if (obj->type != OBJ_COMMIT) die("Non commit %s?", revs->pending.objects[i].name); if (sb->final) - die("More than one commit to dig down to %s and %s?", + die("More than one commit to dig up from, %s and %s?", revs->pending.objects[i].name, final_commit_name); sb->final = (struct commit *) obj; final_commit_name = revs->pending.objects[i].name; } + + if (!final_commit_name) + final_commit_name = dwim_reverse_initial(sb); if (!final_commit_name) - die("No commit to dig down to?"); + die("No commit to dig up from?"); return xstrdup(final_commit_name); } @@ -2482,17 +2571,19 @@ int cmd_blame(int argc, const char **argv, const char *prefix) long dashdash_pos, lno; char *final_commit_name = NULL; enum object_type type; - - static struct string_list range_list; - static int output_option = 0, opt = 0; - static int show_stats = 0; - static const char *revs_file = NULL; - static const char *contents_from = NULL; - static const struct option options[] = { + struct commit *final_commit = NULL; + + struct string_list range_list = STRING_LIST_INIT_NODUP; + int output_option = 0, opt = 0; + int show_stats = 0; + const char *revs_file = NULL; + const char *contents_from = NULL; + const struct option options[] = { OPT_BOOL(0, "incremental", &incremental, N_("Show blame entries as we find them, incrementally")), OPT_BOOL('b', NULL, &blank_boundary, N_("Show blank SHA-1 for boundary commits (Default: off)")), OPT_BOOL(0, "root", &show_root, N_("Do not treat root commits as boundaries (Default: off)")), OPT_BOOL(0, "show-stats", &show_stats, N_("Show work cost statistics")), + OPT_BOOL(0, "progress", &show_progress, N_("Force progress reporting")), OPT_BIT(0, "score-debug", &output_option, N_("Show output score for blame entries"), OUTPUT_SHOW_SCORE), OPT_BIT('f', "show-name", &output_option, N_("Show original filename (Default: auto)"), OUTPUT_SHOW_NAME), OPT_BIT('n', "show-number", &output_option, N_("Show original linenumber (Default: off)"), OUTPUT_SHOW_NUMBER), @@ -2504,6 +2595,15 @@ int cmd_blame(int argc, const char **argv, const char *prefix) OPT_BIT('s', NULL, &output_option, N_("Suppress author name and timestamp (Default: off)"), OUTPUT_NO_AUTHOR), OPT_BIT('e', "show-email", &output_option, N_("Show author email instead of name (Default: off)"), OUTPUT_SHOW_EMAIL), OPT_BIT('w', NULL, &xdl_opts, N_("Ignore whitespace differences"), XDF_IGNORE_WHITESPACE), + + /* + * The following two options are parsed by parse_revision_opt() + * and are only included here to get included in the "-h" + * output: + */ + { OPTION_LOWLEVEL_CALLBACK, 0, "indent-heuristic", NULL, NULL, N_("Use an experimental indent-based heuristic to improve diffs"), PARSE_OPT_NOARG, parse_opt_unknown_cb }, + { OPTION_LOWLEVEL_CALLBACK, 0, "compaction-heuristic", NULL, NULL, N_("Use an experimental blank-line-based heuristic to improve diffs"), PARSE_OPT_NOARG, parse_opt_unknown_cb }, + OPT_BIT(0, "minimal", &xdl_opts, N_("Spend extra cycles to find better match"), XDF_NEED_MINIMAL), OPT_STRING('S', NULL, &revs_file, N_("file"), N_("Use revisions from <file> instead of calling git-rev-list")), OPT_STRING(0, "contents", &contents_from, N_("file"), N_("Use <file>'s contents as the final image")), @@ -2520,7 +2620,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix) unsigned int range_i; long anchor; - git_config(git_blame_config, NULL); + git_config(git_blame_config, &output_option); init_revisions(&revs, NULL); revs.date_mode = blame_date_mode; DIFF_OPT_SET(&revs.diffopt, ALLOW_TEXTCONV); @@ -2528,6 +2628,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix) save_commit_buffer = 0; dashdash_pos = 0; + show_progress = -1; parse_options_start(&ctx, argc, argv, prefix, options, PARSE_OPT_KEEP_DASHDASH | PARSE_OPT_KEEP_ARGV0); @@ -2549,25 +2650,35 @@ int cmd_blame(int argc, const char **argv, const char *prefix) } parse_done: no_whole_file_rename = !DIFF_OPT_TST(&revs.diffopt, FOLLOW_RENAMES); + xdl_opts |= revs.diffopt.xdl_opts & (XDF_COMPACTION_HEURISTIC | XDF_INDENT_HEURISTIC); DIFF_OPT_CLR(&revs.diffopt, FOLLOW_RENAMES); argc = parse_options_end(&ctx); - if (0 < abbrev) + if (incremental || (output_option & OUTPUT_PORCELAIN)) { + if (show_progress > 0) + die(_("--progress can't be used with --incremental or porcelain formats")); + show_progress = 0; + } else if (show_progress < 0) + show_progress = isatty(2); + + if (0 < abbrev && abbrev < GIT_SHA1_HEXSZ) /* one more abbrev length is needed for the boundary commit */ abbrev++; + else if (!abbrev) + abbrev = GIT_SHA1_HEXSZ; if (revs_file && read_ancestry(revs_file)) die_errno("reading graft file '%s' failed", revs_file); if (cmd_is_annotate) { output_option |= OUTPUT_ANNOTATE_COMPAT; - blame_date_mode = DATE_ISO8601; + blame_date_mode.type = DATE_ISO8601; } else { blame_date_mode = revs.date_mode; } /* The maximum width used to show the dates */ - switch (blame_date_mode) { + switch (blame_date_mode.type) { case DATE_RFC2822: blame_date_width = sizeof("Thu, 19 Oct 2006 16:00:04 -0700"); break; @@ -2580,6 +2691,9 @@ parse_done: case DATE_RAW: blame_date_width = sizeof("1161298804 -0700"); break; + case DATE_UNIX: + blame_date_width = sizeof("1161298804"); + break; case DATE_SHORT: blame_date_width = sizeof("2006-10-19"); break; @@ -2592,10 +2706,12 @@ parse_done: fewer display columns. */ blame_date_width = utf8_strwidth(_("4 years, 11 months ago")) + 1; /* add the null */ break; - case DATE_LOCAL: case DATE_NORMAL: blame_date_width = sizeof("Thu Oct 19 16:00:04 2006 -0700"); break; + case DATE_STRFTIME: + blame_date_width = strlen(show_date(0, 0, &blame_date_mode)) + 1; /* add the null */ + break; } blame_date_width -= 1; /* strip the null */ @@ -2668,10 +2784,12 @@ parse_done: sb.commits.compare = compare_commits_by_commit_date; } else if (contents_from) - die("--contents and --children do not blend well."); + die(_("--contents and --reverse do not blend well.")); else { final_commit_name = prepare_initial(&sb); sb.commits.compare = compare_commits_by_reverse_commit_date; + if (revs.first_parent_only) + revs.children.name = NULL; } if (!sb.final) { @@ -2686,7 +2804,13 @@ parse_done: add_pending_object(&revs, &(sb.final->object), ":"); } else if (contents_from) - die("Cannot use --contents with final commit object name"); + die(_("cannot use --contents with final commit object name")); + + if (reverse && revs.first_parent_only) { + final_commit = find_single_final(sb.revs, NULL); + if (!final_commit) + die(_("--reverse and --first-parent together require specified latest commit")); + } /* * If we have bottom, this will mark the ancestors of the @@ -2696,7 +2820,26 @@ parse_done: if (prepare_revision_walk(&revs)) die(_("revision walk setup failed")); - if (is_null_sha1(sb.final->object.sha1)) { + if (reverse && revs.first_parent_only) { + struct commit *c = final_commit; + + sb.revs->children.name = "children"; + while (c->parents && + oidcmp(&c->object.oid, &sb.final->object.oid)) { + struct commit_list *l = xcalloc(1, sizeof(*l)); + + l->item = c; + if (add_decoration(&sb.revs->children, + &c->parents->item->object, l)) + die("BUG: not unique item in first-parent chain"); + c = c->parents->item; + } + + if (oidcmp(&c->object.oid, &sb.final->object.oid)) + die(_("--reverse --first-parent together require range along first-parent chain")); + } + + if (is_null_oid(&sb.final->object.oid)) { o = sb.final->util; sb.final_buf = xmemdupz(o->file.ptr, o->file.size); sb.final_buf_size = o->file.size; @@ -2704,26 +2847,26 @@ parse_done: else { o = get_origin(&sb, sb.final, path); if (fill_blob_sha1_and_mode(o)) - die("no such path %s in %s", path, final_commit_name); + die(_("no such path %s in %s"), path, final_commit_name); if (DIFF_OPT_TST(&sb.revs->diffopt, ALLOW_TEXTCONV) && - textconv_object(path, o->mode, o->blob_sha1, 1, (char **) &sb.final_buf, + textconv_object(path, o->mode, &o->blob_oid, 1, (char **) &sb.final_buf, &sb.final_buf_size)) ; else - sb.final_buf = read_sha1_file(o->blob_sha1, &type, + sb.final_buf = read_sha1_file(o->blob_oid.hash, &type, &sb.final_buf_size); if (!sb.final_buf) - die("Cannot read blob %s for path %s", - sha1_to_hex(o->blob_sha1), + die(_("cannot read blob %s for path %s"), + oid_to_hex(&o->blob_oid), path); } num_read_blob++; lno = prepare_lines(&sb); if (lno && !range_list.nr) - string_list_append(&range_list, xstrdup("1")); + string_list_append(&range_list, "1"); anchor = 1; range_set_init(&ranges, range_list.nr); @@ -2734,7 +2877,9 @@ parse_done: &bottom, &top, sb.path)) usage(blame_usage); if (lno < top || ((lno || bottom) && lno < bottom)) - die("file %s has only %lu lines", path, lno); + die(Q_("file %s has only %lu line", + "file %s has only %lu lines", + lno), path, lno); if (bottom < 1) bottom = 1; if (top < 1) @@ -2772,11 +2917,11 @@ parse_done: read_mailmap(&mailmap, NULL); + assign_blame(&sb, opt); + if (!incremental) setup_pager(); - assign_blame(&sb, opt); - free(final_commit_name); if (incremental) diff --git a/builtin/branch.c b/builtin/branch.c index b42e5b6dbc..475707528a 100644 --- a/builtin/branch.c +++ b/builtin/branch.c @@ -19,18 +19,18 @@ #include "column.h" #include "utf8.h" #include "wt-status.h" +#include "ref-filter.h" +#include "worktree.h" static const char * const builtin_branch_usage[] = { N_("git branch [<options>] [-r | -a] [--merged | --no-merged]"), N_("git branch [<options>] [-l] [-f] <branch-name> [<start-point>]"), N_("git branch [<options>] [-r] (-d | -D) <branch-name>..."), N_("git branch [<options>] (-m | -M) [<old-branch>] <new-branch>"), + N_("git branch [<options>] [-r | -a] [--points-at]"), NULL }; -#define REF_LOCAL_BRANCH 0x01 -#define REF_REMOTE_BRANCH 0x02 - static const char *head; static unsigned char head_sha1[20]; @@ -52,13 +52,6 @@ enum color_branch { BRANCH_COLOR_UPSTREAM = 5 }; -static enum merge_filter { - NO_FILTER = 0, - SHOW_NOT_MERGED, - SHOW_MERGED -} merge_filter; -static unsigned char merge_filter_ref[20]; - static struct string_list output = STRING_LIST_INIT_DUP; static unsigned int colopts; @@ -121,7 +114,7 @@ static int branch_merged(int kind, const char *name, void *reference_name_to_free = NULL; int merged; - if (kind == REF_LOCAL_BRANCH) { + if (kind == FILTER_REFS_BRANCHES) { struct branch *branch = branch_get(name); const char *upstream = branch_get_upstream(branch, NULL); unsigned char sha1[20]; @@ -160,7 +153,7 @@ static int branch_merged(int kind, const char *name, } static int check_branch_commit(const char *branchname, const char *refname, - unsigned char *sha1, struct commit *head_rev, + const unsigned char *sha1, struct commit *head_rev, int kinds, int force) { struct commit *rev = lookup_commit_reference(sha1); @@ -199,14 +192,14 @@ static int delete_branches(int argc, const char **argv, int force, int kinds, struct strbuf bname = STRBUF_INIT; switch (kinds) { - case REF_REMOTE_BRANCH: + case FILTER_REFS_REMOTES: fmt = "refs/remotes/%s"; /* For subsequent UI messages */ remote_branch = 1; force = 1; break; - case REF_LOCAL_BRANCH: + case FILTER_REFS_BRANCHES: fmt = "refs/heads/%s"; break; default: @@ -219,25 +212,30 @@ static int delete_branches(int argc, const char **argv, int force, int kinds, die(_("Couldn't look up commit object for HEAD")); } for (i = 0; i < argc; i++, strbuf_release(&bname)) { - const char *target; + char *target = NULL; int flags = 0; strbuf_branchname(&bname, argv[i]); - if (kinds == REF_LOCAL_BRANCH && !strcmp(head, bname.buf)) { - error(_("Cannot delete the branch '%s' " - "which you are currently on."), bname.buf); - ret = 1; - continue; - } - free(name); - name = mkpathdup(fmt, bname.buf); - target = resolve_ref_unsafe(name, - RESOLVE_REF_READING - | RESOLVE_REF_NO_RECURSE - | RESOLVE_REF_ALLOW_BAD_NAME, - sha1, &flags); + + if (kinds == FILTER_REFS_BRANCHES) { + const struct worktree *wt = + find_shared_symref("HEAD", name); + if (wt) { + error(_("Cannot delete branch '%s' " + "checked out at '%s'"), + bname.buf, wt->path); + ret = 1; + continue; + } + } + + target = resolve_refdup(name, + RESOLVE_REF_READING + | RESOLVE_REF_NO_RECURSE + | RESOLVE_REF_ALLOW_BAD_NAME, + sha1, &flags); if (!target) { error(remote_branch ? _("remote-tracking branch '%s' not found.") @@ -250,16 +248,17 @@ static int delete_branches(int argc, const char **argv, int force, int kinds, check_branch_commit(bname.buf, name, sha1, head_rev, kinds, force)) { ret = 1; - continue; + goto next; } - if (delete_ref(name, sha1, REF_NODEREF)) { + if (delete_ref(name, is_null_sha1(sha1) ? NULL : sha1, + REF_NODEREF)) { error(remote_branch ? _("Error deleting remote-tracking branch '%s'") : _("Error deleting branch '%s'"), bname.buf); ret = 1; - continue; + goto next; } if (!quiet) { printf(remote_branch @@ -271,6 +270,9 @@ static int delete_branches(int argc, const char **argv, int force, int kinds, : find_unique_abbrev(sha1, DEFAULT_ABBREV)); } delete_branch_config(bname.buf); + + next: + free(target); } free(name); @@ -278,147 +280,6 @@ static int delete_branches(int argc, const char **argv, int force, int kinds, return(ret); } -struct ref_item { - char *name; - char *dest; - unsigned int kind, width; - struct commit *commit; - int ignore; -}; - -struct ref_list { - struct rev_info revs; - int index, alloc, maxwidth, verbose, abbrev; - struct ref_item *list; - struct commit_list *with_commit; - int kinds; -}; - -static char *resolve_symref(const char *src, const char *prefix) -{ - unsigned char sha1[20]; - int flag; - const char *dst; - - dst = resolve_ref_unsafe(src, 0, sha1, &flag); - if (!(dst && (flag & REF_ISSYMREF))) - return NULL; - if (prefix) - skip_prefix(dst, prefix, &dst); - return xstrdup(dst); -} - -struct append_ref_cb { - struct ref_list *ref_list; - const char **pattern; - int ret; -}; - -static int match_patterns(const char **pattern, const char *refname) -{ - if (!*pattern) - return 1; /* no pattern always matches */ - while (*pattern) { - if (!wildmatch(*pattern, refname, 0, NULL)) - return 1; - pattern++; - } - return 0; -} - -static int append_ref(const char *refname, const struct object_id *oid, int flags, void *cb_data) -{ - struct append_ref_cb *cb = (struct append_ref_cb *)(cb_data); - struct ref_list *ref_list = cb->ref_list; - struct ref_item *newitem; - struct commit *commit; - int kind, i; - const char *prefix, *orig_refname = refname; - - static struct { - int kind; - const char *prefix; - } ref_kind[] = { - { REF_LOCAL_BRANCH, "refs/heads/" }, - { REF_REMOTE_BRANCH, "refs/remotes/" }, - }; - - /* Detect kind */ - for (i = 0; i < ARRAY_SIZE(ref_kind); i++) { - prefix = ref_kind[i].prefix; - if (skip_prefix(refname, prefix, &refname)) { - kind = ref_kind[i].kind; - break; - } - } - if (ARRAY_SIZE(ref_kind) <= i) - return 0; - - /* Don't add types the caller doesn't want */ - if ((kind & ref_list->kinds) == 0) - return 0; - - if (!match_patterns(cb->pattern, refname)) - return 0; - - commit = NULL; - if (ref_list->verbose || ref_list->with_commit || merge_filter != NO_FILTER) { - commit = lookup_commit_reference_gently(oid->hash, 1); - if (!commit) { - cb->ret = error(_("branch '%s' does not point at a commit"), refname); - return 0; - } - - /* Filter with with_commit if specified */ - if (!is_descendant_of(commit, ref_list->with_commit)) - return 0; - - if (merge_filter != NO_FILTER) - add_pending_object(&ref_list->revs, - (struct object *)commit, refname); - } - - ALLOC_GROW(ref_list->list, ref_list->index + 1, ref_list->alloc); - - /* Record the new item */ - newitem = &(ref_list->list[ref_list->index++]); - newitem->name = xstrdup(refname); - newitem->kind = kind; - newitem->commit = commit; - newitem->width = utf8_strwidth(refname); - newitem->dest = resolve_symref(orig_refname, prefix); - newitem->ignore = 0; - /* adjust for "remotes/" */ - if (newitem->kind == REF_REMOTE_BRANCH && - ref_list->kinds != REF_REMOTE_BRANCH) - newitem->width += 8; - if (newitem->width > ref_list->maxwidth) - ref_list->maxwidth = newitem->width; - - return 0; -} - -static void free_ref_list(struct ref_list *ref_list) -{ - int i; - - for (i = 0; i < ref_list->index; i++) { - free(ref_list->list[i].name); - free(ref_list->list[i].dest); - } - free(ref_list->list); -} - -static int ref_cmp(const void *r1, const void *r2) -{ - struct ref_item *c1 = (struct ref_item *)(r1); - struct ref_item *c2 = (struct ref_item *)(r2); - - if (c1->kind != c2->kind) - return c1->kind - c2->kind; - return strcmp(c1->name, c2->name); -} - static void fill_tracking_info(struct strbuf *stat, const char *branch_name, int show_upstream_ref) { @@ -481,8 +342,8 @@ static void fill_tracking_info(struct strbuf *stat, const char *branch_name, free(ref); } -static void add_verbose_info(struct strbuf *out, struct ref_item *item, - int verbose, int abbrev) +static void add_verbose_info(struct strbuf *out, struct ref_array_item *item, + struct ref_filter *filter, const char *refname) { struct strbuf subject = STRBUF_INIT, stat = STRBUF_INIT; const char *sub = _(" **** invalid ref ****"); @@ -493,32 +354,79 @@ static void add_verbose_info(struct strbuf *out, struct ref_item *item, sub = subject.buf; } - if (item->kind == REF_LOCAL_BRANCH) - fill_tracking_info(&stat, item->name, verbose > 1); + if (item->kind == FILTER_REFS_BRANCHES) + fill_tracking_info(&stat, refname, filter->verbose > 1); strbuf_addf(out, " %s %s%s", - find_unique_abbrev(item->commit->object.sha1, abbrev), + find_unique_abbrev(item->commit->object.oid.hash, filter->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) +static char *get_head_description(void) +{ + struct strbuf desc = STRBUF_INIT; + struct wt_status_state state; + memset(&state, 0, sizeof(state)); + wt_status_get_state(&state, 1); + if (state.rebase_in_progress || + state.rebase_interactive_in_progress) + strbuf_addf(&desc, _("(no branch, rebasing %s)"), + state.branch); + else if (state.bisect_in_progress) + strbuf_addf(&desc, _("(no branch, bisect started on %s)"), + state.branch); + else if (state.detached_from) { + if (state.detached_at) + /* TRANSLATORS: make sure this matches + "HEAD detached at " in wt-status.c */ + strbuf_addf(&desc, _("(HEAD detached at %s)"), + state.detached_from); + else + /* TRANSLATORS: make sure this matches + "HEAD detached from " in wt-status.c */ + strbuf_addf(&desc, _("(HEAD detached from %s)"), + state.detached_from); + } + else + strbuf_addstr(&desc, _("(no branch)")); + free(state.branch); + free(state.onto); + free(state.detached_from); + return strbuf_detach(&desc, NULL); +} + +static void format_and_print_ref_item(struct ref_array_item *item, int maxwidth, + struct ref_filter *filter, const char *remote_prefix) { char c; + int current = 0; int color; struct strbuf out = STRBUF_INIT, name = STRBUF_INIT; - - if (item->ignore) - return; + const char *prefix_to_show = ""; + const char *prefix_to_skip = NULL; + const char *desc = item->refname; + char *to_free = NULL; switch (item->kind) { - case REF_LOCAL_BRANCH: - color = BRANCH_COLOR_LOCAL; + case FILTER_REFS_BRANCHES: + prefix_to_skip = "refs/heads/"; + skip_prefix(desc, prefix_to_skip, &desc); + if (!filter->detached && !strcmp(desc, head)) + current = 1; + else + color = BRANCH_COLOR_LOCAL; break; - case REF_REMOTE_BRANCH: + case FILTER_REFS_REMOTES: + prefix_to_skip = "refs/remotes/"; + skip_prefix(desc, prefix_to_skip, &desc); color = BRANCH_COLOR_REMOTE; + prefix_to_show = remote_prefix; + break; + case FILTER_REFS_DETACHED_HEAD: + desc = to_free = get_head_description(); + current = 1; break; default: color = BRANCH_COLOR_PLAIN; @@ -531,8 +439,8 @@ static void print_ref_item(struct ref_item *item, int maxwidth, int verbose, color = BRANCH_COLOR_CURRENT; } - strbuf_addf(&name, "%s%s", prefix, item->name); - if (verbose) { + strbuf_addf(&name, "%s%s", prefix_to_show, desc); + if (filter->verbose) { int utf8_compensation = strlen(name.buf) - utf8_strwidth(name.buf); strbuf_addf(&out, "%c %s%-*s%s", c, branch_get_color(color), maxwidth + utf8_compensation, name.buf, @@ -541,155 +449,107 @@ static void print_ref_item(struct ref_item *item, int maxwidth, int verbose, strbuf_addf(&out, "%c %s%s%s", c, branch_get_color(color), name.buf, branch_get_color(BRANCH_COLOR_RESET)); - if (item->dest) - strbuf_addf(&out, " -> %s", item->dest); - else if (verbose) + if (item->symref) { + const char *symref = item->symref; + if (prefix_to_skip) + skip_prefix(symref, prefix_to_skip, &symref); + strbuf_addf(&out, " -> %s", symref); + } + else if (filter->verbose) /* " f7c0c00 [ahead 58, behind 197] vcs-svn: drop obj_pool.h" */ - add_verbose_info(&out, item, verbose, abbrev); + add_verbose_info(&out, item, filter, desc); if (column_active(colopts)) { - assert(!verbose && "--column and --verbose are incompatible"); + assert(!filter->verbose && "--column and --verbose are incompatible"); string_list_append(&output, out.buf); } else { printf("%s\n", out.buf); } strbuf_release(&name); strbuf_release(&out); + free(to_free); } -static int calc_maxwidth(struct ref_list *refs) +static int calc_maxwidth(struct ref_array *refs, int remote_bonus) { - int i, w = 0; - for (i = 0; i < refs->index; i++) { - if (refs->list[i].ignore) - continue; - if (refs->list[i].width > w) - w = refs->list[i].width; + int i, max = 0; + for (i = 0; i < refs->nr; i++) { + struct ref_array_item *it = refs->items[i]; + const char *desc = it->refname; + int w; + + skip_prefix(it->refname, "refs/heads/", &desc); + skip_prefix(it->refname, "refs/remotes/", &desc); + w = utf8_strwidth(desc); + + if (it->kind == FILTER_REFS_REMOTES) + w += remote_bonus; + if (w > max) + max = w; } - return w; + return max; } -static char *get_head_description(void) +static void print_ref_list(struct ref_filter *filter, struct ref_sorting *sorting) { - struct strbuf desc = STRBUF_INIT; - struct wt_status_state state; - memset(&state, 0, sizeof(state)); - wt_status_get_state(&state, 1); - if (state.rebase_in_progress || - state.rebase_interactive_in_progress) - strbuf_addf(&desc, _("(no branch, rebasing %s)"), - state.branch); - else if (state.bisect_in_progress) - strbuf_addf(&desc, _("(no branch, bisect started on %s)"), - state.branch); - else if (state.detached_from) { - /* TRANSLATORS: make sure these match _("HEAD detached at ") - and _("HEAD detached from ") in wt-status.c */ - if (state.detached_at) - strbuf_addf(&desc, _("(HEAD detached at %s)"), - state.detached_from); - else - strbuf_addf(&desc, _("(HEAD detached from %s)"), - state.detached_from); - } - else - strbuf_addstr(&desc, _("(no branch)")); - free(state.branch); - free(state.onto); - free(state.detached_from); - return strbuf_detach(&desc, NULL); -} + int i; + struct ref_array array; + int maxwidth = 0; + const char *remote_prefix = ""; -static void show_detached(struct ref_list *ref_list) -{ - struct commit *head_commit = lookup_commit_reference_gently(head_sha1, 1); - - if (head_commit && is_descendant_of(head_commit, ref_list->with_commit)) { - struct ref_item item; - item.name = get_head_description(); - item.width = utf8_strwidth(item.name); - item.kind = REF_LOCAL_BRANCH; - item.dest = NULL; - item.commit = head_commit; - item.ignore = 0; - if (item.width > ref_list->maxwidth) - ref_list->maxwidth = item.width; - print_ref_item(&item, ref_list->maxwidth, ref_list->verbose, ref_list->abbrev, 1, ""); - free(item.name); - } + /* + * If we are listing more than just remote branches, + * then remote branches will have a "remotes/" prefix. + * We need to account for this in the width. + */ + if (filter->kind != FILTER_REFS_REMOTES) + remote_prefix = "remotes/"; + + memset(&array, 0, sizeof(array)); + + verify_ref_format("%(refname)%(symref)"); + filter_refs(&array, filter, filter->kind | FILTER_REFS_INCLUDE_BROKEN); + + if (filter->verbose) + maxwidth = calc_maxwidth(&array, strlen(remote_prefix)); + + /* + * If no sorting parameter is given then we default to sorting + * by 'refname'. This would give us an alphabetically sorted + * array with the 'HEAD' ref at the beginning followed by + * local branches 'refs/heads/...' and finally remote-tacking + * branches 'refs/remotes/...'. + */ + if (!sorting) + sorting = ref_default_sorting(); + ref_array_sort(sorting, &array); + + for (i = 0; i < array.nr; i++) + format_and_print_ref_item(array.items[i], maxwidth, filter, remote_prefix); + + ref_array_clear(&array); } -static int print_ref_list(int kinds, int detached, int verbose, int abbrev, struct commit_list *with_commit, const char **pattern) +static void reject_rebase_or_bisect_branch(const char *target) { + struct worktree **worktrees = get_worktrees(0); int i; - struct append_ref_cb cb; - struct ref_list ref_list; - - memset(&ref_list, 0, sizeof(ref_list)); - ref_list.kinds = kinds; - ref_list.verbose = verbose; - ref_list.abbrev = abbrev; - ref_list.with_commit = with_commit; - if (merge_filter != NO_FILTER) - init_revisions(&ref_list.revs, NULL); - cb.ref_list = &ref_list; - cb.pattern = pattern; - cb.ret = 0; - for_each_rawref(append_ref, &cb); - if (merge_filter != NO_FILTER) { - struct commit *filter; - filter = lookup_commit_reference_gently(merge_filter_ref, 0); - if (!filter) - die(_("object '%s' does not point to a commit"), - sha1_to_hex(merge_filter_ref)); - - filter->object.flags |= UNINTERESTING; - add_pending_object(&ref_list.revs, - (struct object *) filter, ""); - ref_list.revs.limited = 1; - - if (prepare_revision_walk(&ref_list.revs)) - die(_("revision walk setup failed")); - - for (i = 0; i < ref_list.index; i++) { - struct ref_item *item = &ref_list.list[i]; - struct commit *commit = item->commit; - int is_merged = !!(commit->object.flags & UNINTERESTING); - item->ignore = is_merged != (merge_filter == SHOW_MERGED); - } - for (i = 0; i < ref_list.index; i++) { - struct ref_item *item = &ref_list.list[i]; - clear_commit_marks(item->commit, ALL_REV_FLAGS); - } - clear_commit_marks(filter, ALL_REV_FLAGS); - - if (verbose) - ref_list.maxwidth = calc_maxwidth(&ref_list); - } + for (i = 0; worktrees[i]; i++) { + struct worktree *wt = worktrees[i]; - qsort(ref_list.list, ref_list.index, sizeof(struct ref_item), ref_cmp); - - detached = (detached && (kinds & REF_LOCAL_BRANCH)); - if (detached && match_patterns(pattern, "HEAD")) - show_detached(&ref_list); - - for (i = 0; i < ref_list.index; i++) { - int current = !detached && - (ref_list.list[i].kind == REF_LOCAL_BRANCH) && - !strcmp(ref_list.list[i].name, head); - char *prefix = (kinds != REF_REMOTE_BRANCH && - ref_list.list[i].kind == REF_REMOTE_BRANCH) - ? "remotes/" : ""; - print_ref_item(&ref_list.list[i], ref_list.maxwidth, verbose, - abbrev, current, prefix); - } + if (!wt->is_detached) + continue; - free_ref_list(&ref_list); + if (is_worktree_being_rebased(wt, target)) + die(_("Branch %s is being rebased at %s"), + target, wt->path); - if (cb.ret) - error(_("some refs could not be read")); + if (is_worktree_being_bisected(wt, target)) + die(_("Branch %s is being bisected at %s"), + target, wt->path); + } - return cb.ret; + free_worktrees(worktrees); } static void rename_branch(const char *oldname, const char *newname, int force) @@ -721,6 +581,8 @@ static void rename_branch(const char *oldname, const char *newname, int force) validate_new_branchname(newname, &newref, force, clobber_head_ok); + reject_rebase_or_bisect_branch(oldref.buf); + strbuf_addf(&logmsg, "Branch: renamed %s to %s", oldref.buf, newref.buf); @@ -731,8 +593,7 @@ static void rename_branch(const char *oldname, const char *newname, int force) if (recovery) warning(_("Renamed a misnamed branch '%s' away"), oldref.buf + 11); - /* no need to pass logmsg here as HEAD didn't really move */ - if (!strcmp(oldname, head) && create_symref("HEAD", newref.buf, NULL)) + if (replace_each_worktree_head_symref(oldref.buf, newref.buf)) die(_("Branch renamed to %s, but HEAD is not updated!"), newname); strbuf_addf(&oldsection, "branch.%s", oldref.buf + 11); @@ -745,25 +606,10 @@ static void rename_branch(const char *oldname, const char *newname, int force) strbuf_release(&newsection); } -static int opt_parse_merge_filter(const struct option *opt, const char *arg, int unset) -{ - merge_filter = ((opt->long_name[0] == 'n') - ? SHOW_NOT_MERGED - : SHOW_MERGED); - if (unset) - merge_filter = SHOW_NOT_MERGED; /* b/c for --no-merged */ - if (!arg) - arg = "HEAD"; - if (get_sha1(arg, merge_filter_ref)) - die(_("malformed object name %s"), arg); - return 0; -} - static const char edit_description[] = "BRANCH_DESCRIPTION"; static int edit_branch_description(const char *branch_name) { - int status; struct strbuf buf = STRBUF_INIT; struct strbuf name = STRBUF_INIT; @@ -771,72 +617,57 @@ static int edit_branch_description(const char *branch_name) if (!buf.len || buf.buf[buf.len-1] != '\n') strbuf_addch(&buf, '\n'); strbuf_commented_addf(&buf, - "Please edit the description for the branch\n" - " %s\n" - "Lines starting with '%c' will be stripped.\n", + _("Please edit the description for the branch\n" + " %s\n" + "Lines starting with '%c' will be stripped.\n"), branch_name, comment_line_char); - if (write_file(git_path(edit_description), 0, "%s", buf.buf)) { - strbuf_release(&buf); - return error(_("could not write branch description template: %s"), - strerror(errno)); - } + write_file_buf(git_path(edit_description), buf.buf, buf.len); strbuf_reset(&buf); if (launch_editor(git_path(edit_description), &buf, NULL)) { strbuf_release(&buf); return -1; } - stripspace(&buf, 1); + strbuf_stripspace(&buf, 1); strbuf_addf(&name, "branch.%s.description", branch_name); - status = git_config_set(name.buf, buf.len ? buf.buf : NULL); + git_config_set(name.buf, buf.len ? buf.buf : NULL); strbuf_release(&name); strbuf_release(&buf); - return status; + return 0; } int cmd_branch(int argc, const char **argv, const char *prefix) { int delete = 0, rename = 0, force = 0, list = 0; - int verbose = 0, abbrev = -1, detached = 0; int reflog = 0, edit_description = 0; int quiet = 0, unset_upstream = 0; const char *new_upstream = NULL; enum branch_track track; - int kinds = REF_LOCAL_BRANCH; - struct commit_list *with_commit = NULL; + struct ref_filter filter; + static struct ref_sorting *sorting = NULL, **sorting_tail = &sorting; struct option options[] = { OPT_GROUP(N_("Generic options")), - OPT__VERBOSE(&verbose, + OPT__VERBOSE(&filter.verbose, N_("show hash and subject, give twice for upstream branch")), OPT__QUIET(&quiet, N_("suppress informational messages")), OPT_SET_INT('t', "track", &track, N_("set up tracking mode (see git-pull(1))"), BRANCH_TRACK_EXPLICIT), OPT_SET_INT( 0, "set-upstream", &track, N_("change upstream info"), BRANCH_TRACK_OVERRIDE), - OPT_STRING('u', "set-upstream-to", &new_upstream, "upstream", "change the upstream info"), - OPT_BOOL(0, "unset-upstream", &unset_upstream, "Unset the upstream info"), + OPT_STRING('u', "set-upstream-to", &new_upstream, N_("upstream"), N_("change the upstream info")), + OPT_BOOL(0, "unset-upstream", &unset_upstream, N_("Unset the upstream info")), OPT__COLOR(&branch_use_color, N_("use colored output")), - OPT_SET_INT('r', "remotes", &kinds, N_("act on remote-tracking branches"), - REF_REMOTE_BRANCH), - { - OPTION_CALLBACK, 0, "contains", &with_commit, N_("commit"), - N_("print only branches that contain the commit"), - PARSE_OPT_LASTARG_DEFAULT, - parse_opt_with_commit, (intptr_t)"HEAD", - }, - { - OPTION_CALLBACK, 0, "with", &with_commit, N_("commit"), - N_("print only branches that contain the commit"), - PARSE_OPT_HIDDEN | PARSE_OPT_LASTARG_DEFAULT, - parse_opt_with_commit, (intptr_t) "HEAD", - }, - OPT__ABBREV(&abbrev), + OPT_SET_INT('r', "remotes", &filter.kind, N_("act on remote-tracking branches"), + FILTER_REFS_REMOTES), + OPT_CONTAINS(&filter.with_commit, N_("print only branches that contain the commit")), + OPT_WITH(&filter.with_commit, N_("print only branches that contain the commit")), + OPT__ABBREV(&filter.abbrev), OPT_GROUP(N_("Specific git-branch actions:")), - OPT_SET_INT('a', "all", &kinds, N_("list both remote-tracking and local branches"), - REF_REMOTE_BRANCH | REF_LOCAL_BRANCH), + OPT_SET_INT('a', "all", &filter.kind, N_("list both remote-tracking and local branches"), + FILTER_REFS_REMOTES | FILTER_REFS_BRANCHES), OPT_BIT('d', "delete", &delete, N_("delete fully merged branch"), 1), OPT_BIT('D', NULL, &delete, N_("delete branch (even if not merged)"), 2), OPT_BIT('m', "move", &rename, N_("move/rename a branch and its reflog"), 1), @@ -846,22 +677,22 @@ int cmd_branch(int argc, const char **argv, const char *prefix) OPT_BOOL(0, "edit-description", &edit_description, N_("edit the description for the branch")), OPT__FORCE(&force, N_("force creation, move/rename, deletion")), + OPT_MERGED(&filter, N_("print only branches that are merged")), + OPT_NO_MERGED(&filter, N_("print only branches that are not merged")), + OPT_COLUMN(0, "column", &colopts, N_("list branches in columns")), + OPT_CALLBACK(0 , "sort", sorting_tail, N_("key"), + N_("field name to sort on"), &parse_opt_ref_sorting), { - OPTION_CALLBACK, 0, "no-merged", &merge_filter_ref, - N_("commit"), N_("print only not merged branches"), - PARSE_OPT_LASTARG_DEFAULT | PARSE_OPT_NONEG, - opt_parse_merge_filter, (intptr_t) "HEAD", - }, - { - OPTION_CALLBACK, 0, "merged", &merge_filter_ref, - N_("commit"), N_("print only merged branches"), - PARSE_OPT_LASTARG_DEFAULT | PARSE_OPT_NONEG, - opt_parse_merge_filter, (intptr_t) "HEAD", + OPTION_CALLBACK, 0, "points-at", &filter.points_at, N_("object"), + N_("print only branches of the object"), 0, parse_opt_object_name }, - OPT_COLUMN(0, "column", &colopts, N_("list branches in columns")), OPT_END(), }; + memset(&filter, 0, sizeof(filter)); + filter.kind = FILTER_REFS_BRANCHES; + filter.abbrev = -1; + if (argc == 2 && !strcmp(argv[1], "-h")) usage_with_options(builtin_branch_usage, options); @@ -873,11 +704,9 @@ int cmd_branch(int argc, const char **argv, const char *prefix) if (!head) die(_("Failed to resolve HEAD as a valid ref.")); if (!strcmp(head, "HEAD")) - detached = 1; + filter.detached = 1; else if (!skip_prefix(head, "refs/heads/", &head)) die(_("HEAD not found below refs/heads!")); - hashcpy(merge_filter_ref, head_sha1); - argc = parse_options(argc, argv, prefix, options, builtin_branch_usage, 0); @@ -885,17 +714,17 @@ int cmd_branch(int argc, const char **argv, const char *prefix) if (!delete && !rename && !edit_description && !new_upstream && !unset_upstream && argc == 0) list = 1; - if (with_commit || merge_filter != NO_FILTER) + if (filter.with_commit || filter.merge != REF_FILTER_MERGED_NONE || filter.points_at.nr) list = 1; if (!!delete + !!rename + !!new_upstream + list + unset_upstream > 1) usage_with_options(builtin_branch_usage, options); - if (abbrev == -1) - abbrev = DEFAULT_ABBREV; + if (filter.abbrev == -1) + filter.abbrev = DEFAULT_ABBREV; finalize_colopts(&colopts, -1); - if (verbose) { + if (filter.verbose) { if (explicitly_enable_column(colopts)) die(_("--column and --verbose are incompatible")); colopts = 0; @@ -909,20 +738,23 @@ int cmd_branch(int argc, const char **argv, const char *prefix) if (delete) { if (!argc) die(_("branch name required")); - return delete_branches(argc, argv, delete > 1, kinds, quiet); + return delete_branches(argc, argv, delete > 1, filter.kind, quiet); } else if (list) { - int ret = print_ref_list(kinds, detached, verbose, abbrev, - with_commit, argv); + /* git branch --local also shows HEAD when it is detached */ + if ((filter.kind & FILTER_REFS_BRANCHES) && filter.detached) + filter.kind |= FILTER_REFS_DETACHED_HEAD; + filter.name_patterns = argv; + print_ref_list(&filter, sorting); print_columns(&output, colopts, NULL); string_list_clear(&output, 0); - return ret; + return 0; } else if (edit_description) { const char *branch_name; struct strbuf branch_ref = STRBUF_INIT; if (!argc) { - if (detached) + if (filter.detached) die(_("Cannot give description to detached HEAD")); branch_name = head; } else if (argc == 1) @@ -975,7 +807,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix) * create_branch takes care of setting up the tracking * info and making sure new_upstream is correct */ - create_branch(head, branch->name, new_upstream, 0, 0, 0, quiet, BRANCH_TRACK_OVERRIDE); + create_branch(branch->name, new_upstream, 0, 0, 0, quiet, BRANCH_TRACK_OVERRIDE); } else if (unset_upstream) { struct branch *branch = branch_get(argv[0]); struct strbuf buf = STRBUF_INIT; @@ -1010,7 +842,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix) if (!branch) die(_("no such branch '%s'"), argv[0]); - if (kinds != REF_LOCAL_BRANCH) + if (filter.kind != FILTER_REFS_BRANCHES) die(_("-a and -r options to 'git branch' do not make sense with a branch name")); if (track == BRANCH_TRACK_OVERRIDE) @@ -1021,7 +853,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix) strbuf_release(&buf); branch_existed = ref_exists(branch->refname); - create_branch(head, argv[0], (argc == 2) ? argv[1] : head, + create_branch(argv[0], (argc == 2) ? argv[1] : head, force, reflog, 0, quiet, track); /* @@ -1032,8 +864,8 @@ int cmd_branch(int argc, const char **argv, const char *prefix) if (argc == 1 && track == BRANCH_TRACK_OVERRIDE && !branch_existed && remote_tracking) { fprintf(stderr, _("\nIf you wanted to make '%s' track '%s', do this:\n\n"), head, branch->name); - fprintf(stderr, _(" git branch -d %s\n"), branch->name); - fprintf(stderr, _(" git branch --set-upstream-to %s\n"), branch->name); + fprintf(stderr, " git branch -d %s\n", branch->name); + fprintf(stderr, " git branch --set-upstream-to %s\n", branch->name); } } else diff --git a/builtin/cat-file.c b/builtin/cat-file.c index 049a95f1f1..30383e9eb4 100644 --- a/builtin/cat-file.c +++ b/builtin/cat-file.c @@ -9,30 +9,71 @@ #include "userdiff.h" #include "streaming.h" #include "tree-walk.h" +#include "sha1-array.h" + +struct batch_options { + int enabled; + int follow_symlinks; + int print_contents; + int buffer_output; + int all_objects; + int cmdmode; /* may be 'w' or 'c' for --filters or --textconv */ + const char *format; +}; + +static const char *force_path; + +static int filter_object(const char *path, unsigned mode, + const struct object_id *oid, + char **buf, unsigned long *size) +{ + enum object_type type; + + *buf = read_sha1_file(oid->hash, &type, size); + if (!*buf) + return error(_("cannot read object %s '%s'"), + oid_to_hex(oid), path); + if ((type == OBJ_BLOB) && S_ISREG(mode)) { + struct strbuf strbuf = STRBUF_INIT; + if (convert_to_working_tree(path, *buf, *size, &strbuf)) { + free(*buf); + *size = strbuf.len; + *buf = strbuf_detach(&strbuf, NULL); + } + } + + return 0; +} static int cat_one_file(int opt, const char *exp_type, const char *obj_name, int unknown_type) { - unsigned char sha1[20]; + struct object_id oid; enum object_type type; char *buf; unsigned long size; struct object_context obj_context; - struct object_info oi = {NULL}; + struct object_info oi = OBJECT_INFO_INIT; struct strbuf sb = STRBUF_INIT; unsigned flags = LOOKUP_REPLACE_OBJECT; + const char *path = force_path; if (unknown_type) flags |= LOOKUP_UNKNOWN_OBJECT; - if (get_sha1_with_context(obj_name, 0, sha1, &obj_context)) + if (get_sha1_with_context(obj_name, 0, oid.hash, &obj_context)) die("Not a valid object name %s", obj_name); + if (!path) + path = obj_context.path; + if (obj_context.mode == S_IFINVALID) + obj_context.mode = 0100644; + buf = NULL; switch (opt) { case 't': oi.typename = &sb; - if (sha1_object_info_extended(sha1, &oi, flags) < 0) + if (sha1_object_info_extended(oid.hash, &oi, flags) < 0) die("git cat-file: could not get object info"); if (sb.len) { printf("%s\n", sb.buf); @@ -43,24 +84,34 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name, case 's': oi.sizep = &size; - if (sha1_object_info_extended(sha1, &oi, flags) < 0) + if (sha1_object_info_extended(oid.hash, &oi, flags) < 0) die("git cat-file: could not get object info"); printf("%lu\n", size); return 0; case 'e': - return !has_sha1_file(sha1); + return !has_object_file(&oid); + + case 'w': + if (!path[0]) + die("git cat-file --filters %s: <object> must be " + "<sha1:path>", obj_name); + + if (filter_object(path, obj_context.mode, + &oid, &buf, &size)) + return -1; + break; case 'c': - if (!obj_context.path[0]) + if (!path[0]) die("git cat-file --textconv %s: <object> must be <sha1:path>", obj_name); - if (textconv_object(obj_context.path, obj_context.mode, sha1, 1, &buf, &size)) + if (textconv_object(path, obj_context.mode, &oid, 1, &buf, &size)) break; case 'p': - type = sha1_object_info(sha1, NULL); + type = sha1_object_info(oid.hash, NULL); if (type < 0) die("Not a valid object name %s", obj_name); @@ -73,8 +124,8 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name, } if (type == OBJ_BLOB) - return stream_blob_to_fd(1, sha1, NULL, 0); - buf = read_sha1_file(sha1, &type, &size); + return stream_blob_to_fd(1, &oid, NULL, 0); + buf = read_sha1_file(oid.hash, &type, &size); if (!buf) die("Cannot read object %s", obj_name); @@ -83,19 +134,19 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name, case 0: if (type_from_string(exp_type) == OBJ_BLOB) { - unsigned char blob_sha1[20]; - if (sha1_object_info(sha1, NULL) == OBJ_TAG) { - char *buffer = read_sha1_file(sha1, &type, &size); + struct object_id blob_oid; + if (sha1_object_info(oid.hash, NULL) == OBJ_TAG) { + char *buffer = read_sha1_file(oid.hash, &type, &size); const char *target; if (!skip_prefix(buffer, "object ", &target) || - get_sha1_hex(target, blob_sha1)) - die("%s not a valid tag", sha1_to_hex(sha1)); + get_oid_hex(target, &blob_oid)) + die("%s not a valid tag", oid_to_hex(&oid)); free(buffer); } else - hashcpy(blob_sha1, sha1); + oidcpy(&blob_oid, &oid); - if (sha1_object_info(blob_sha1, NULL) == OBJ_BLOB) - return stream_blob_to_fd(1, blob_sha1, NULL, 0); + if (sha1_object_info(blob_oid.hash, NULL) == OBJ_BLOB) + return stream_blob_to_fd(1, &blob_oid, NULL, 0); /* * we attempted to dereference a tag to a blob * and failed; there may be new dereference @@ -103,7 +154,7 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name, * fall-back to the usual case. */ } - buf = read_object_with_reference(sha1, exp_type, &size, NULL); + buf = read_object_with_reference(oid.hash, exp_type, &size, NULL); break; default: @@ -118,12 +169,12 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name, } struct expand_data { - unsigned char sha1[20]; + struct object_id oid; enum object_type type; unsigned long size; - unsigned long disk_size; + off_t disk_size; const char *rest; - unsigned char delta_base_sha1[20]; + struct object_id delta_base_oid; /* * If mark_query is true, we do not expand anything, but rather @@ -144,6 +195,13 @@ struct expand_data { * elements above, so you can retrieve the response from there. */ struct object_info info; + + /* + * This flag will be true if the requested batch format and options + * don't require us to call sha1_object_info, which can then be + * optimized out. + */ + unsigned skip_object_info : 1; }; static int is_atom(const char *atom, const char *s, int slen) @@ -159,7 +217,7 @@ static void expand_atom(struct strbuf *sb, const char *atom, int len, if (is_atom("objectname", atom, len)) { if (!data->mark_query) - strbuf_addstr(sb, sha1_to_hex(data->sha1)); + strbuf_addstr(sb, oid_to_hex(&data->oid)); } else if (is_atom("objecttype", atom, len)) { if (data->mark_query) data->info.typep = &data->type; @@ -174,7 +232,7 @@ static void expand_atom(struct strbuf *sb, const char *atom, int len, if (data->mark_query) data->info.disk_sizep = &data->disk_size; else - strbuf_addf(sb, "%lu", data->disk_size); + strbuf_addf(sb, "%"PRIuMAX, (uintmax_t)data->disk_size); } else if (is_atom("rest", atom, len)) { if (data->mark_query) data->split_on_whitespace = 1; @@ -182,9 +240,10 @@ static void expand_atom(struct strbuf *sb, const char *atom, int len, strbuf_addstr(sb, data->rest); } else if (is_atom("deltabase", atom, len)) { if (data->mark_query) - data->info.delta_base_sha1 = data->delta_base_sha1; + data->info.delta_base_sha1 = data->delta_base_oid.hash; else - strbuf_addstr(sb, sha1_to_hex(data->delta_base_sha1)); + strbuf_addstr(sb, + oid_to_hex(&data->delta_base_oid)); } else die("unknown format element: %.*s", len, atom); } @@ -204,53 +263,102 @@ static size_t expand_format(struct strbuf *sb, const char *start, void *data) return end - start + 1; } -static void print_object_or_die(int fd, struct expand_data *data) +static void batch_write(struct batch_options *opt, const void *data, int len) +{ + if (opt->buffer_output) { + if (fwrite(data, 1, len, stdout) != len) + die_errno("unable to write to stdout"); + } else + write_or_die(1, data, len); +} + +static void print_object_or_die(struct batch_options *opt, struct expand_data *data) { - const unsigned char *sha1 = data->sha1; + const struct object_id *oid = &data->oid; assert(data->info.typep); if (data->type == OBJ_BLOB) { - if (stream_blob_to_fd(fd, sha1, NULL, 0) < 0) - die("unable to stream %s to stdout", sha1_to_hex(sha1)); + if (opt->buffer_output) + fflush(stdout); + if (opt->cmdmode) { + char *contents; + unsigned long size; + + if (!data->rest) + die("missing path for '%s'", oid_to_hex(oid)); + + if (opt->cmdmode == 'w') { + if (filter_object(data->rest, 0100644, oid, + &contents, &size)) + die("could not convert '%s' %s", + oid_to_hex(oid), data->rest); + } else if (opt->cmdmode == 'c') { + enum object_type type; + if (!textconv_object(data->rest, 0100644, oid, + 1, &contents, &size)) + contents = read_sha1_file(oid->hash, &type, + &size); + if (!contents) + die("could not convert '%s' %s", + oid_to_hex(oid), data->rest); + } else + die("BUG: invalid cmdmode: %c", opt->cmdmode); + batch_write(opt, contents, size); + free(contents); + } else if (stream_blob_to_fd(1, oid, NULL, 0) < 0) + die("unable to stream %s to stdout", oid_to_hex(oid)); } else { enum object_type type; unsigned long size; void *contents; - contents = read_sha1_file(sha1, &type, &size); + contents = read_sha1_file(oid->hash, &type, &size); if (!contents) - die("object %s disappeared", sha1_to_hex(sha1)); + die("object %s disappeared", oid_to_hex(oid)); if (type != data->type) - die("object %s changed type!?", sha1_to_hex(sha1)); + die("object %s changed type!?", oid_to_hex(oid)); if (data->info.sizep && size != data->size) - die("object %s changed size!?", sha1_to_hex(sha1)); + die("object %s changed size!?", oid_to_hex(oid)); - write_or_die(fd, contents, size); + batch_write(opt, contents, size); free(contents); } } -struct batch_options { - int enabled; - int follow_symlinks; - int print_contents; - const char *format; -}; - -static int batch_one_object(const char *obj_name, struct batch_options *opt, - struct expand_data *data) +static void batch_object_write(const char *obj_name, struct batch_options *opt, + struct expand_data *data) { struct strbuf buf = STRBUF_INIT; + + if (!data->skip_object_info && + sha1_object_info_extended(data->oid.hash, &data->info, LOOKUP_REPLACE_OBJECT) < 0) { + printf("%s missing\n", + obj_name ? obj_name : oid_to_hex(&data->oid)); + fflush(stdout); + return; + } + + strbuf_expand(&buf, opt->format, expand_format, data); + strbuf_addch(&buf, '\n'); + batch_write(opt, buf.buf, buf.len); + strbuf_release(&buf); + + if (opt->print_contents) { + print_object_or_die(opt, data); + batch_write(opt, "\n", 1); + } +} + +static void batch_one_object(const char *obj_name, struct batch_options *opt, + struct expand_data *data) +{ struct object_context ctx; int flags = opt->follow_symlinks ? GET_SHA1_FOLLOW_SYMLINKS : 0; enum follow_symlinks_result result; - if (!obj_name) - return 1; - - result = get_sha1_with_context(obj_name, flags, data->sha1, &ctx); + result = get_sha1_with_context(obj_name, flags, data->oid.hash, &ctx); if (result != FOUND) { switch (result) { case MISSING_OBJECT: @@ -274,7 +382,7 @@ static int batch_one_object(const char *obj_name, struct batch_options *opt, break; } fflush(stdout); - return 0; + return; } if (ctx.mode == 0) { @@ -282,24 +390,39 @@ static int batch_one_object(const char *obj_name, struct batch_options *opt, (uintmax_t)ctx.symlink_path.len, ctx.symlink_path.buf); fflush(stdout); - return 0; + return; } - if (sha1_object_info_extended(data->sha1, &data->info, LOOKUP_REPLACE_OBJECT) < 0) { - printf("%s missing\n", obj_name); - fflush(stdout); - return 0; - } + batch_object_write(obj_name, opt, data); +} - strbuf_expand(&buf, opt->format, expand_format, data); - strbuf_addch(&buf, '\n'); - write_or_die(1, buf.buf, buf.len); - strbuf_release(&buf); +struct object_cb_data { + struct batch_options *opt; + struct expand_data *expand; +}; - if (opt->print_contents) { - print_object_or_die(1, data); - write_or_die(1, "\n", 1); - } +static int batch_object_cb(const unsigned char sha1[20], void *vdata) +{ + struct object_cb_data *data = vdata; + hashcpy(data->expand->oid.hash, sha1); + batch_object_write(NULL, data->opt, data->expand); + return 0; +} + +static int batch_loose_object(const unsigned char *sha1, + const char *path, + void *data) +{ + sha1_array_append(data, sha1); + return 0; +} + +static int batch_packed_object(const unsigned char *sha1, + struct packed_git *pack, + uint32_t pos, + void *data) +{ + sha1_array_append(data, sha1); return 0; } @@ -322,6 +445,14 @@ static int batch_objects(struct batch_options *opt) data.mark_query = 1; strbuf_expand(&buf, opt->format, expand_format, &data); data.mark_query = 0; + if (opt->cmdmode) + data.split_on_whitespace = 1; + + if (opt->all_objects) { + struct object_info empty = OBJECT_INFO_INIT; + if (!memcmp(&data.info, &empty, sizeof(empty))) + data.skip_object_info = 1; + } /* * If we are printing out the object, then always fill in the type, @@ -330,6 +461,21 @@ static int batch_objects(struct batch_options *opt) if (opt->print_contents) data.info.typep = &data.type; + if (opt->all_objects) { + struct sha1_array sa = SHA1_ARRAY_INIT; + struct object_cb_data cb; + + for_each_loose_object(batch_loose_object, &sa, 0); + for_each_packed_object(batch_packed_object, &sa, 0); + + cb.opt = opt; + cb.expand = &data; + sha1_array_for_each_unique(&sa, batch_object_cb, &cb); + + sha1_array_clear(&sa); + return 0; + } + /* * We are going to call get_sha1 on a potentially very large number of * objects. In most large cases, these will be actual object sha1s. The @@ -340,7 +486,7 @@ static int batch_objects(struct batch_options *opt) save_warning = warn_on_object_refname_ambiguity; warn_on_object_refname_ambiguity = 0; - while (strbuf_getline(&buf, stdin, '\n') != EOF) { + while (strbuf_getline(&buf, stdin) != EOF) { if (data.split_on_whitespace) { /* * Split at first whitespace, tying off the beginning @@ -355,9 +501,7 @@ static int batch_objects(struct batch_options *opt) data.rest = p; } - retval = batch_one_object(buf.buf, opt, &data); - if (retval) - break; + batch_one_object(buf.buf, opt, &data); } strbuf_release(&buf); @@ -366,8 +510,8 @@ static int batch_objects(struct batch_options *opt) } static const char * const cat_file_usage[] = { - N_("git cat-file (-t [--allow-unknown-type]|-s [--allow-unknown-type]|-e|-p|<type>|--textconv) <object>"), - N_("git cat-file (--batch | --batch-check) [--follow-symlinks] < <list-of-objects>"), + N_("git cat-file (-t [--allow-unknown-type] | -s [--allow-unknown-type] | -e | -p | <type> | --textconv | --filters) [--path=<path>] <object>"), + N_("git cat-file (--batch | --batch-check) [--follow-symlinks] [--textconv | --filters]"), NULL }; @@ -412,8 +556,13 @@ int cmd_cat_file(int argc, const char **argv, const char *prefix) OPT_CMDMODE('p', NULL, &opt, N_("pretty-print object's content"), 'p'), OPT_CMDMODE(0, "textconv", &opt, N_("for blob objects, run textconv on object's content"), 'c'), - OPT_BOOL( 0, "allow-unknown-type", &unknown_type, + OPT_CMDMODE(0, "filters", &opt, + N_("for blob objects, run filters on object's content"), 'w'), + OPT_STRING(0, "path", &force_path, N_("blob"), + N_("use a specific path for --textconv/--filters")), + OPT_BOOL(0, "allow-unknown-type", &unknown_type, N_("allow -s and -t to work with broken/corrupt objects")), + OPT_BOOL(0, "buffer", &batch.buffer_output, N_("buffer --batch output")), { OPTION_CALLBACK, 0, "batch", &batch, "format", N_("show info and content of objects fed from the standard input"), PARSE_OPT_OPTARG, batch_option_callback }, @@ -422,15 +571,20 @@ int cmd_cat_file(int argc, const char **argv, const char *prefix) PARSE_OPT_OPTARG, batch_option_callback }, OPT_BOOL(0, "follow-symlinks", &batch.follow_symlinks, N_("follow in-tree symlinks (used with --batch or --batch-check)")), + OPT_BOOL(0, "batch-all-objects", &batch.all_objects, + N_("show all objects with --batch or --batch-check")), OPT_END() }; git_config(git_cat_file_config, NULL); + batch.buffer_output = -1; argc = parse_options(argc, argv, prefix, options, cat_file_usage, 0); if (opt) { - if (argc == 1) + if (batch.enabled && (opt == 'c' || opt == 'w')) + batch.cmdmode = opt; + else if (argc == 1) obj_name = argv[0]; else usage_with_options(cat_file_usage, options); @@ -442,14 +596,31 @@ int cmd_cat_file(int argc, const char **argv, const char *prefix) } else usage_with_options(cat_file_usage, options); } - if (batch.enabled && (opt || argc)) { + if (batch.enabled) { + if (batch.cmdmode != opt || argc) + usage_with_options(cat_file_usage, options); + if (batch.cmdmode && batch.all_objects) + die("--batch-all-objects cannot be combined with " + "--textconv nor with --filters"); + } + + if ((batch.follow_symlinks || batch.all_objects) && !batch.enabled) { usage_with_options(cat_file_usage, options); } - if (batch.follow_symlinks && !batch.enabled) { + if (force_path && opt != 'c' && opt != 'w') { + error("--path=<path> needs --textconv or --filters"); usage_with_options(cat_file_usage, options); } + if (force_path && batch.enabled) { + error("--path=<path> incompatible with --batch"); + usage_with_options(cat_file_usage, options); + } + + if (batch.buffer_output < 0) + batch.buffer_output = batch.all_objects; + if (batch.enabled) return batch_objects(&batch); diff --git a/builtin/check-attr.c b/builtin/check-attr.c index 21d2bedcc9..53a5a18c16 100644 --- a/builtin/check-attr.c +++ b/builtin/check-attr.c @@ -9,7 +9,7 @@ static int cached_attrs; static int stdin_paths; static const char * const check_attr_usage[] = { N_("git check-attr [-a | --all | <attr>...] [--] <pathname>..."), -N_("git check-attr --stdin [-z] [-a | --all | <attr>...] < <list-of-paths>"), +N_("git check-attr --stdin [-z] [-a | --all | <attr>...]"), NULL }; @@ -72,23 +72,23 @@ static void check_attr(const char *prefix, int cnt, static void check_attr_stdin_paths(const char *prefix, int cnt, struct git_attr_check *check) { - struct strbuf buf, nbuf; - int line_termination = nul_term_line ? 0 : '\n'; - - strbuf_init(&buf, 0); - strbuf_init(&nbuf, 0); - while (strbuf_getline(&buf, stdin, line_termination) != EOF) { - if (line_termination && buf.buf[0] == '"') { - strbuf_reset(&nbuf); - if (unquote_c_style(&nbuf, buf.buf, NULL)) + struct strbuf buf = STRBUF_INIT; + struct strbuf unquoted = STRBUF_INIT; + strbuf_getline_fn getline_fn; + + getline_fn = nul_term_line ? strbuf_getline_nul : strbuf_getline_lf; + while (getline_fn(&buf, stdin) != EOF) { + if (!nul_term_line && buf.buf[0] == '"') { + strbuf_reset(&unquoted); + if (unquote_c_style(&unquoted, buf.buf, NULL)) die("line is badly quoted"); - strbuf_swap(&buf, &nbuf); + strbuf_swap(&buf, &unquoted); } check_attr(prefix, cnt, check, buf.buf); maybe_flush_or_die(stdout, "attribute to stdout"); } strbuf_release(&buf); - strbuf_release(&nbuf); + strbuf_release(&unquoted); } static NORETURN void error_with_usage(const char *msg) diff --git a/builtin/check-ignore.c b/builtin/check-ignore.c index dc8d97c56c..1d73d3ca3d 100644 --- a/builtin/check-ignore.c +++ b/builtin/check-ignore.c @@ -8,7 +8,7 @@ static int quiet, verbose, stdin_paths, show_non_matching, no_index; static const char * const check_ignore_usage[] = { "git check-ignore [<options>] <pathname>...", -"git check-ignore [<options>] --stdin < <list-of-paths>", +"git check-ignore [<options>] --stdin", NULL }; @@ -115,19 +115,19 @@ static int check_ignore(struct dir_struct *dir, static int check_ignore_stdin_paths(struct dir_struct *dir, const char *prefix) { - struct strbuf buf, nbuf; + struct strbuf buf = STRBUF_INIT; + struct strbuf unquoted = STRBUF_INIT; char *pathspec[2] = { NULL, NULL }; - int line_termination = nul_term_line ? 0 : '\n'; + strbuf_getline_fn getline_fn; int num_ignored = 0; - strbuf_init(&buf, 0); - strbuf_init(&nbuf, 0); - while (strbuf_getline(&buf, stdin, line_termination) != EOF) { - if (line_termination && buf.buf[0] == '"') { - strbuf_reset(&nbuf); - if (unquote_c_style(&nbuf, buf.buf, NULL)) + getline_fn = nul_term_line ? strbuf_getline_nul : strbuf_getline_lf; + while (getline_fn(&buf, stdin) != EOF) { + if (!nul_term_line && buf.buf[0] == '"') { + strbuf_reset(&unquoted); + if (unquote_c_style(&unquoted, buf.buf, NULL)) die("line is badly quoted"); - strbuf_swap(&buf, &nbuf); + strbuf_swap(&buf, &unquoted); } pathspec[0] = buf.buf; num_ignored += check_ignore(dir, prefix, @@ -135,7 +135,7 @@ static int check_ignore_stdin_paths(struct dir_struct *dir, const char *prefix) maybe_flush_or_die(stdout, "check-ignore to stdout"); } strbuf_release(&buf); - strbuf_release(&nbuf); + strbuf_release(&unquoted); return num_ignored; } diff --git a/builtin/check-mailmap.c b/builtin/check-mailmap.c index eaaea546d3..cf0f54f6b9 100644 --- a/builtin/check-mailmap.c +++ b/builtin/check-mailmap.c @@ -54,7 +54,7 @@ int cmd_check_mailmap(int argc, const char **argv, const char *prefix) if (use_stdin) { struct strbuf buf = STRBUF_INIT; - while (strbuf_getline(&buf, stdin, '\n') != EOF) { + while (strbuf_getline_lf(&buf, stdin) != EOF) { check_mailmap(&mailmap, buf.buf); maybe_flush_or_die(stdout, "stdout"); } diff --git a/builtin/check-ref-format.c b/builtin/check-ref-format.c index fd915d5984..eac499450f 100644 --- a/builtin/check-ref-format.c +++ b/builtin/check-ref-format.c @@ -20,7 +20,7 @@ static const char builtin_check_ref_format_usage[] = */ static char *collapse_slashes(const char *refname) { - char *ret = xmalloc(strlen(refname) + 1); + char *ret = xmallocz(strlen(refname)); char ch; char prev = '/'; char *cp = ret; diff --git a/builtin/checkout-index.c b/builtin/checkout-index.c index 8028c3768f..30a49d9f42 100644 --- a/builtin/checkout-index.c +++ b/builtin/checkout-index.c @@ -11,12 +11,12 @@ #include "parse-options.h" #define CHECKOUT_ALL 4 -static int line_termination = '\n'; +static int nul_term_line; static int checkout_stage; /* default to checkout stage0 */ static int to_tempfile; static char topath[4][TEMPORARY_FILENAME_LENGTH + 1]; -static struct checkout state; +static struct checkout state = CHECKOUT_INIT; static void write_tempfile_record(const char *name, const char *prefix) { @@ -35,7 +35,8 @@ static void write_tempfile_record(const char *name, const char *prefix) fputs(topath[checkout_stage], stdout); putchar('\t'); - write_name_quoted_relative(name, prefix, stdout, line_termination); + write_name_quoted_relative(name, prefix, stdout, + nul_term_line ? '\0' : '\n'); for (i = 0; i < 4; i++) { topath[i][0] = 0; @@ -129,36 +130,6 @@ static const char * const builtin_checkout_index_usage[] = { static struct lock_file lock_file; -static int option_parse_u(const struct option *opt, - const char *arg, int unset) -{ - int *newfd = opt->value; - - state.refresh_cache = 1; - state.istate = &the_index; - if (*newfd < 0) - *newfd = hold_locked_index(&lock_file, 1); - return 0; -} - -static int option_parse_z(const struct option *opt, - const char *arg, int unset) -{ - if (unset) - line_termination = '\n'; - else - line_termination = 0; - return 0; -} - -static int option_parse_prefix(const struct option *opt, - const char *arg, int unset) -{ - state.base_dir = arg; - state.base_dir_len = strlen(arg); - return 0; -} - static int option_parse_stage(const struct option *opt, const char *arg, int unset) { @@ -170,7 +141,7 @@ static int option_parse_stage(const struct option *opt, if ('1' <= ch && ch <= '3') checkout_stage = arg[0] - '0'; else - die("stage should be between 1 and 3 or all"); + die(_("stage should be between 1 and 3 or all")); } return 0; } @@ -183,6 +154,7 @@ int cmd_checkout_index(int argc, const char **argv, const char *prefix) int read_from_stdin = 0; int prefix_length; int force = 0, quiet = 0, not_new = 0; + int index_opt = 0; struct option builtin_checkout_index_options[] = { OPT_BOOL('a', "all", &all, N_("check out all files in the index")), @@ -191,22 +163,19 @@ int cmd_checkout_index(int argc, const char **argv, const char *prefix) N_("no warning for existing files and files not in index")), OPT_BOOL('n', "no-create", ¬_new, N_("don't checkout new files")), - { OPTION_CALLBACK, 'u', "index", &newfd, NULL, - N_("update stat information in the index file"), - PARSE_OPT_NOARG, option_parse_u }, - { OPTION_CALLBACK, 'z', NULL, NULL, NULL, - N_("paths are separated with NUL character"), - PARSE_OPT_NOARG, option_parse_z }, + OPT_BOOL('u', "index", &index_opt, + N_("update stat information in the index file")), + OPT_BOOL('z', NULL, &nul_term_line, + N_("paths are separated with NUL character")), OPT_BOOL(0, "stdin", &read_from_stdin, N_("read list of paths from the standard input")), OPT_BOOL(0, "temp", &to_tempfile, N_("write the content to temporary files")), - OPT_CALLBACK(0, "prefix", NULL, N_("string"), - N_("when creating files, prepend <string>"), - option_parse_prefix), - OPT_CALLBACK(0, "stage", NULL, NULL, + OPT_STRING(0, "prefix", &state.base_dir, N_("string"), + N_("when creating files, prepend <string>")), + { OPTION_CALLBACK, 0, "stage", NULL, "1-3|all", N_("copy out the files from named stage"), - option_parse_stage), + PARSE_OPT_NONEG, option_parse_stage }, OPT_END() }; @@ -214,7 +183,6 @@ int cmd_checkout_index(int argc, const char **argv, const char *prefix) 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; if (read_cache() < 0) { @@ -227,15 +195,17 @@ int cmd_checkout_index(int argc, const char **argv, const char *prefix) state.quiet = quiet; state.not_new = not_new; - if (state.base_dir_len || to_tempfile) { - /* when --prefix is specified we do not - * want to update cache. - */ - if (state.refresh_cache) { - rollback_lock_file(&lock_file); - newfd = -1; - } - state.refresh_cache = 0; + if (!state.base_dir) + state.base_dir = ""; + state.base_dir_len = strlen(state.base_dir); + + /* + * when --prefix is specified we do not want to update cache. + */ + if (index_opt && !state.base_dir_len && !to_tempfile) { + state.refresh_cache = 1; + state.istate = &the_index; + newfd = hold_locked_index(&lock_file, 1); } /* Check out named files first */ @@ -253,24 +223,27 @@ int cmd_checkout_index(int argc, const char **argv, const char *prefix) } if (read_from_stdin) { - struct strbuf buf = STRBUF_INIT, nbuf = STRBUF_INIT; + struct strbuf buf = STRBUF_INIT; + struct strbuf unquoted = STRBUF_INIT; + strbuf_getline_fn getline_fn; if (all) die("git checkout-index: don't mix '--all' and '--stdin'"); - while (strbuf_getline(&buf, stdin, line_termination) != EOF) { + getline_fn = nul_term_line ? strbuf_getline_nul : strbuf_getline_lf; + while (getline_fn(&buf, stdin) != EOF) { char *p; - if (line_termination && buf.buf[0] == '"') { - strbuf_reset(&nbuf); - if (unquote_c_style(&nbuf, buf.buf, NULL)) + if (!nul_term_line && buf.buf[0] == '"') { + strbuf_reset(&unquoted); + if (unquote_c_style(&unquoted, buf.buf, NULL)) die("line is badly quoted"); - strbuf_swap(&buf, &nbuf); + strbuf_swap(&buf, &unquoted); } p = prefix_path(prefix, prefix_length, buf.buf); checkout_file(p, prefix); free(p); } - strbuf_release(&nbuf); + strbuf_release(&unquoted); strbuf_release(&buf); } diff --git a/builtin/checkout.c b/builtin/checkout.c index 9b49f0e413..512492aad9 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -18,9 +18,8 @@ #include "xdiff-interface.h" #include "ll-merge.h" #include "resolve-undo.h" +#include "submodule-config.h" #include "submodule.h" -#include "argv-array.h" -#include "sigchain.h" static const char * const checkout_usage[] = { N_("git checkout [<options>] <branch>"), @@ -38,6 +37,7 @@ struct checkout_opts { int overwrite_ignore; int ignore_skipworktree; int ignore_other_worktrees; + int show_progress; const char *new_branch; const char *new_branch_force; @@ -50,18 +50,14 @@ struct checkout_opts { const char *prefix; struct pathspec pathspec; struct tree *source_tree; - - const char *new_worktree; - const char **saved_argv; - int new_worktree_mode; }; static int post_checkout_hook(struct commit *old, struct commit *new, int changed) { return run_hook_le(NULL, "post-checkout", - sha1_to_hex(old ? old->object.sha1 : null_sha1), - sha1_to_hex(new ? new->object.sha1 : null_sha1), + sha1_to_hex(old ? old->object.oid.hash : null_sha1), + sha1_to_hex(new ? new->object.oid.hash : null_sha1), changed ? "1" : "0", NULL); /* "new" can be NULL when checking out from the index before a commit exists. */ @@ -80,7 +76,7 @@ static int update_some(const unsigned char *sha1, struct strbuf *base, len = base->len + strlen(pathname); ce = xcalloc(1, cache_entry_size(len)); - hashcpy(ce->sha1, sha1); + hashcpy(ce->oid.hash, sha1); memcpy(ce->name, base->buf, base->len); memcpy(ce->name + base->len, pathname, len - base->len); ce->ce_flags = create_ce_flags(0) | CE_UPDATE; @@ -96,7 +92,7 @@ static int update_some(const unsigned char *sha1, struct strbuf *base, if (pos >= 0) { struct cache_entry *old = active_cache[pos]; if (ce->ce_mode == old->ce_mode && - !hashcmp(ce->sha1, old->sha1)) { + !oidcmp(&ce->oid, &old->oid)) { old->ce_flags |= CE_UPDATE; free(ce); return 0; @@ -158,8 +154,8 @@ static int check_stages(unsigned stages, const struct cache_entry *ce, int pos) return 0; } -static int checkout_stage(int stage, struct cache_entry *ce, int pos, - struct checkout *state) +static int checkout_stage(int stage, const struct cache_entry *ce, int pos, + const struct checkout *state) { while (pos < active_nr && !strcmp(active_cache[pos]->name, ce->name)) { @@ -173,15 +169,15 @@ static int checkout_stage(int stage, struct cache_entry *ce, int pos, return error(_("path '%s' does not have their version"), ce->name); } -static int checkout_merged(int pos, struct checkout *state) +static int checkout_merged(int pos, const struct checkout *state) { struct cache_entry *ce = active_cache[pos]; const char *path = ce->name; mmfile_t ancestor, ours, theirs; int status; - unsigned char sha1[20]; + struct object_id oid; mmbuffer_t result_buf; - unsigned char threeway[3][20]; + struct object_id threeway[3]; unsigned mode = 0; memset(threeway, 0, sizeof(threeway)); @@ -190,18 +186,18 @@ static int checkout_merged(int pos, struct checkout *state) stage = ce_stage(ce); if (!stage || strcmp(path, ce->name)) break; - hashcpy(threeway[stage - 1], ce->sha1); + oidcpy(&threeway[stage - 1], &ce->oid); if (stage == 2) mode = create_ce_mode(ce->ce_mode); pos++; ce = active_cache[pos]; } - if (is_null_sha1(threeway[1]) || is_null_sha1(threeway[2])) + if (is_null_oid(&threeway[1]) || is_null_oid(&threeway[2])) return error(_("path '%s' does not have necessary versions"), path); - read_mmblob(&ancestor, threeway[0]); - read_mmblob(&ours, threeway[1]); - read_mmblob(&theirs, threeway[2]); + read_mmblob(&ancestor, &threeway[0]); + read_mmblob(&ours, &threeway[1]); + read_mmblob(&theirs, &threeway[2]); /* * NEEDSWORK: re-create conflicts from merges with @@ -230,9 +226,9 @@ static int checkout_merged(int pos, struct checkout *state) * object database even when it may contain conflicts). */ if (write_sha1_file(result_buf.ptr, result_buf.size, - blob_type, sha1)) + blob_type, oid.hash)) die(_("Unable to add merge result for '%s'"), path); - ce = make_cache_entry(mode, sha1, path, 2, 0); + ce = make_cache_entry(mode, oid.hash, path, 2, 0); if (!ce) die(_("make_cache_entry failed for path '%s'"), path); status = checkout_entry(ce, state, NULL); @@ -243,10 +239,9 @@ static int checkout_paths(const struct checkout_opts *opts, const char *revision) { int pos; - struct checkout state; + struct checkout state = CHECKOUT_INIT; static char *ps_matched; - unsigned char rev[20]; - int flag; + struct object_id rev; struct commit *head; int errs = 0; struct lock_file *lock_file; @@ -273,9 +268,6 @@ static int checkout_paths(const struct checkout_opts *opts, die(_("Cannot update paths and switch to branch '%s' at the same time."), opts->new_branch); - if (opts->new_worktree) - die(_("'%s' cannot be used with updating paths"), "--to"); - if (opts->patch_mode) return run_add_interactive(revision, "--patch=checkout", &opts->pathspec); @@ -284,12 +276,12 @@ static int checkout_paths(const struct checkout_opts *opts, hold_locked_index(lock_file, 1); if (read_cache_preload(&opts->pathspec) < 0) - return error(_("corrupt index file")); + return error(_("index file corrupt")); if (opts->source_tree) read_tree_some(opts->source_tree, &opts->pathspec); - ps_matched = xcalloc(1, opts->pathspec.nr); + ps_matched = xcalloc(opts->pathspec.nr, 1); /* * Make sure all pathspecs participated in locating the paths @@ -360,7 +352,6 @@ static int checkout_paths(const struct checkout_opts *opts, return 1; /* Now we are committed to check them out */ - memset(&state, 0, sizeof(state)); state.force = 1; state.refresh_cache = 1; state.istate = &the_index; @@ -382,8 +373,8 @@ static int checkout_paths(const struct checkout_opts *opts, if (write_locked_index(&the_index, lock_file, COMMIT_LOCK)) die(_("unable to write new index file")); - read_ref_full("HEAD", 0, rev, &flag); - head = lookup_commit_reference_gently(rev, 1); + read_ref_full("HEAD", 0, rev.hash, NULL); + head = lookup_commit_reference_gently(rev.hash, 1); errs |= post_checkout_hook(head, head, 0); return errs; @@ -408,7 +399,7 @@ static void describe_detached_head(const char *msg, struct commit *commit) if (!parse_commit(commit)) 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); + find_unique_abbrev(commit->object.oid.hash, DEFAULT_ABBREV), sb.buf); strbuf_release(&sb); } @@ -425,7 +416,7 @@ static int reset_tree(struct tree *tree, const struct checkout_opts *o, opts.reset = 1; opts.merge = 1; opts.fn = oneway_merge; - opts.verbose_update = !o->quiet && isatty(2); + opts.verbose_update = o->show_progress; opts.src_index = &the_index; opts.dst_index = &the_index; parse_tree(tree); @@ -478,7 +469,7 @@ static int merge_working_tree(const struct checkout_opts *opts, hold_locked_index(lock_file, 1); if (read_cache_preload(NULL) < 0) - return error(_("corrupt index file")); + return error(_("index file corrupt")); resolve_undo_clear(); if (opts->force) { @@ -509,18 +500,18 @@ static int merge_working_tree(const struct checkout_opts *opts, topts.update = 1; topts.merge = 1; topts.gently = opts->merge && old->commit; - topts.verbose_update = !opts->quiet && isatty(2); + topts.verbose_update = opts->show_progress; topts.fn = twoway_merge; if (opts->overwrite_ignore) { topts.dir = xcalloc(1, sizeof(*topts.dir)); topts.dir->flags |= DIR_SHOW_IGNORED; setup_standard_excludes(topts.dir); } - tree = parse_tree_indirect(old->commit && !opts->new_worktree_mode ? - old->commit->object.sha1 : + tree = parse_tree_indirect(old->commit ? + old->commit->object.oid.hash : EMPTY_TREE_SHA1_BIN); init_tree_desc(&trees[0], tree->buffer, tree->size); - tree = parse_tree_indirect(new->commit->object.sha1); + tree = parse_tree_indirect(new->commit->object.oid.hash); init_tree_desc(&trees[1], tree->buffer, tree->size); ret = unpack_trees(2, trees, &topts); @@ -575,10 +566,13 @@ static int merge_working_tree(const struct checkout_opts *opts, o.ancestor = old->name; o.branch1 = new->name; o.branch2 = "local"; - merge_trees(&o, new->commit->tree, work, + ret = merge_trees(&o, new->commit->tree, work, old->commit->tree, &result); + if (ret < 0) + exit(128); ret = reset_tree(new->commit->tree, opts, 0, writeout_error); + strbuf_release(&o.obuf); if (ret) return ret; } @@ -619,26 +613,24 @@ static void update_refs_for_switch(const struct checkout_opts *opts, if (opts->new_branch) { if (opts->new_orphan_branch) { if (opts->new_branch_log && !log_all_ref_updates) { - int temp; - struct strbuf log_file = STRBUF_INIT; int ret; - const char *ref_name; - - ref_name = mkpath("refs/heads/%s", opts->new_orphan_branch); - temp = log_all_ref_updates; - log_all_ref_updates = 1; - ret = log_ref_setup(ref_name, &log_file); - log_all_ref_updates = temp; - strbuf_release(&log_file); + char *refname; + struct strbuf err = STRBUF_INIT; + + refname = mkpathdup("refs/heads/%s", opts->new_orphan_branch); + ret = safe_create_reflog(refname, 1, &err); + free(refname); if (ret) { - fprintf(stderr, _("Can not do reflog for '%s'\n"), - opts->new_orphan_branch); + fprintf(stderr, _("Can not do reflog for '%s': %s\n"), + opts->new_orphan_branch, err.buf); + strbuf_release(&err); return; } + strbuf_release(&err); } } else - create_branch(old->name, opts->new_branch, new->name, + create_branch(opts->new_branch, new->name, opts->new_branch_force ? 1 : 0, opts->new_branch_log, opts->new_branch_force ? 1 : 0, @@ -650,7 +642,7 @@ static void update_refs_for_switch(const struct checkout_opts *opts, old_desc = old->name; if (!old_desc && old->commit) - old_desc = sha1_to_hex(old->commit->object.sha1); + old_desc = oid_to_hex(&old->commit->object.oid); reflog_msg = getenv("GIT_REFLOG_ACTION"); if (!reflog_msg) @@ -662,15 +654,17 @@ static void update_refs_for_switch(const struct checkout_opts *opts, 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, + update_ref(msg.buf, "HEAD", new->commit->object.oid.hash, NULL, REF_NODEREF, UPDATE_REFS_DIE_ON_ERR); if (!opts->quiet) { - if (old->path && advice_detached_head) + if (old->path && + advice_detached_head && !opts->force_detach) detach_advice(new->name); describe_detached_head(_("HEAD is now at"), new->commit); } } else if (new->path) { /* Switch branches. */ - create_symref("HEAD", new->path, msg.buf); + if (create_symref("HEAD", new->path, msg.buf) < 0) + die(_("unable to update HEAD")); if (!opts->quiet) { if (old->path && !strcmp(new->path, old->path)) { if (opts->new_branch_force) @@ -712,8 +706,7 @@ static int add_pending_uninteresting_ref(const char *refname, static void describe_one_orphan(struct strbuf *sb, struct commit *commit) { strbuf_addstr(sb, " "); - strbuf_addstr(sb, - find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV)); + strbuf_add_unique_abbrev(sb, commit->object.oid.hash, DEFAULT_ABBREV); strbuf_addch(sb, ' '); if (!parse_commit(commit)) pp_commit_easy(CMIT_FMT_ONELINE, commit, sb); @@ -771,7 +764,7 @@ static void suggest_reattach(struct commit *commit, struct rev_info *revs) " git branch <new-branch-name> %s\n\n", /* Give ngettext() the count */ lost), - find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV)); + find_unique_abbrev(commit->object.oid.hash, DEFAULT_ABBREV)); } /* @@ -789,10 +782,10 @@ static void orphaned_commit_warning(struct commit *old, struct commit *new) setup_revisions(0, NULL, &revs, NULL); object->flags &= ~UNINTERESTING; - add_pending_object(&revs, object, sha1_to_hex(object->sha1)); + add_pending_object(&revs, object, oid_to_hex(&object->oid)); for_each_ref(add_pending_uninteresting_ref, &revs); - add_pending_sha1(&revs, "HEAD", new->object.sha1, UNINTERESTING); + add_pending_sha1(&revs, "HEAD", new->object.oid.hash, UNINTERESTING); refs = revs.pending; revs.leak_pending = 1; @@ -814,11 +807,11 @@ static int switch_branches(const struct checkout_opts *opts, int ret = 0; struct branch_info old; void *path_to_free; - unsigned char rev[20]; + struct object_id rev; int flag, writeout_error = 0; memset(&old, 0, sizeof(old)); - old.path = path_to_free = resolve_refdup("HEAD", 0, rev, &flag); - old.commit = lookup_commit_reference_gently(rev, 1); + old.path = path_to_free = resolve_refdup("HEAD", 0, rev.hash, &flag); + old.commit = lookup_commit_reference_gently(rev.hash, 1); if (!(flag & REF_ISSYMREF)) old.path = NULL; @@ -839,8 +832,7 @@ static int switch_branches(const struct checkout_opts *opts, return ret; } - if (!opts->quiet && !old.path && old.commit && - new->commit != old.commit && !opts->new_worktree_mode) + if (!opts->quiet && !old.path && old.commit && new->commit != old.commit) orphaned_commit_warning(old.commit, new->commit); update_refs_for_switch(opts, &old, new); @@ -850,138 +842,6 @@ static int switch_branches(const struct checkout_opts *opts, return ret || writeout_error; } -static char *junk_work_tree; -static char *junk_git_dir; -static int is_junk; -static pid_t junk_pid; - -static void remove_junk(void) -{ - struct strbuf sb = STRBUF_INIT; - if (!is_junk || getpid() != junk_pid) - return; - if (junk_git_dir) { - strbuf_addstr(&sb, junk_git_dir); - remove_dir_recursively(&sb, 0); - strbuf_reset(&sb); - } - if (junk_work_tree) { - strbuf_addstr(&sb, junk_work_tree); - remove_dir_recursively(&sb, 0); - } - strbuf_release(&sb); -} - -static void remove_junk_on_signal(int signo) -{ - remove_junk(); - sigchain_pop(signo); - raise(signo); -} - -static int prepare_linked_checkout(const struct checkout_opts *opts, - struct branch_info *new) -{ - struct strbuf sb_git = STRBUF_INIT, sb_repo = STRBUF_INIT; - struct strbuf sb = STRBUF_INIT; - const char *path = opts->new_worktree, *name; - struct stat st; - struct child_process cp; - int counter = 0, len, ret; - - if (!new->commit) - die(_("no branch specified")); - if (file_exists(path) && !is_empty_dir(path)) - die(_("'%s' already exists"), path); - - len = strlen(path); - while (len && is_dir_sep(path[len - 1])) - len--; - - for (name = path + len - 1; name > path; name--) - if (is_dir_sep(*name)) { - name++; - break; - } - strbuf_addstr(&sb_repo, - git_path("worktrees/%.*s", (int)(path + len - name), name)); - len = sb_repo.len; - if (safe_create_leading_directories_const(sb_repo.buf)) - die_errno(_("could not create leading directories of '%s'"), - sb_repo.buf); - while (!stat(sb_repo.buf, &st)) { - counter++; - strbuf_setlen(&sb_repo, len); - strbuf_addf(&sb_repo, "%d", counter); - } - name = strrchr(sb_repo.buf, '/') + 1; - - junk_pid = getpid(); - atexit(remove_junk); - sigchain_push_common(remove_junk_on_signal); - - if (mkdir(sb_repo.buf, 0777)) - die_errno(_("could not create directory of '%s'"), sb_repo.buf); - junk_git_dir = xstrdup(sb_repo.buf); - is_junk = 1; - - /* - * lock the incomplete repo so prune won't delete it, unlock - * after the preparation is over. - */ - strbuf_addf(&sb, "%s/locked", sb_repo.buf); - write_file(sb.buf, 1, "initializing\n"); - - strbuf_addf(&sb_git, "%s/.git", path); - if (safe_create_leading_directories_const(sb_git.buf)) - die_errno(_("could not create leading directories of '%s'"), - sb_git.buf); - junk_work_tree = xstrdup(path); - - strbuf_reset(&sb); - strbuf_addf(&sb, "%s/gitdir", sb_repo.buf); - write_file(sb.buf, 1, "%s\n", real_path(sb_git.buf)); - write_file(sb_git.buf, 1, "gitdir: %s/worktrees/%s\n", - real_path(get_git_common_dir()), name); - /* - * This is to keep resolve_ref() happy. We need a valid HEAD - * or is_git_directory() will reject the directory. Any valid - * value would do because this value will be ignored and - * replaced at the next (real) checkout. - */ - strbuf_reset(&sb); - strbuf_addf(&sb, "%s/HEAD", sb_repo.buf); - write_file(sb.buf, 1, "%s\n", sha1_to_hex(new->commit->object.sha1)); - strbuf_reset(&sb); - strbuf_addf(&sb, "%s/commondir", sb_repo.buf); - write_file(sb.buf, 1, "../..\n"); - - if (!opts->quiet) - fprintf_ln(stderr, _("Enter %s (identifier %s)"), path, name); - - setenv("GIT_CHECKOUT_NEW_WORKTREE", "1", 1); - setenv(GIT_DIR_ENVIRONMENT, sb_git.buf, 1); - setenv(GIT_WORK_TREE_ENVIRONMENT, path, 1); - memset(&cp, 0, sizeof(cp)); - cp.git_cmd = 1; - cp.argv = opts->saved_argv; - ret = run_command(&cp); - if (!ret) { - is_junk = 0; - free(junk_work_tree); - free(junk_git_dir); - junk_work_tree = NULL; - junk_git_dir = NULL; - } - strbuf_reset(&sb); - strbuf_addf(&sb, "%s/locked", sb_repo.buf); - unlink_or_warn(sb.buf); - strbuf_release(&sb); - strbuf_release(&sb_repo); - strbuf_release(&sb_git); - return ret; -} - static int git_checkout_config(const char *var, const char *value, void *cb) { if (!strcmp(var, "diff.ignoresubmodules")) { @@ -999,7 +859,7 @@ static int git_checkout_config(const char *var, const char *value, void *cb) struct tracking_name_data { /* const */ char *src_ref; char *dst_ref; - unsigned char *dst_sha1; + struct object_id *dst_oid; int unique; }; @@ -1010,7 +870,7 @@ static int check_tracking_name(struct remote *remote, void *cb_data) memset(&query, 0, sizeof(struct refspec)); query.src = cb->src_ref; if (remote_find_tracking(remote, &query) || - get_sha1(query.dst, cb->dst_sha1)) { + get_oid(query.dst, cb->dst_oid)) { free(query.dst); return 0; } @@ -1023,13 +883,13 @@ static int check_tracking_name(struct remote *remote, void *cb_data) return 0; } -static const char *unique_tracking_name(const char *name, unsigned char *sha1) +static const char *unique_tracking_name(const char *name, struct object_id *oid) { struct tracking_name_data cb_data = { NULL, NULL, NULL, 1 }; char src_ref[PATH_MAX]; snprintf(src_ref, PATH_MAX, "refs/heads/%s", name); cb_data.src_ref = src_ref; - cb_data.dst_sha1 = sha1; + cb_data.dst_oid = oid; for_each_remote(check_tracking_name, &cb_data); if (cb_data.unique) return cb_data.dst_ref; @@ -1037,82 +897,16 @@ static const char *unique_tracking_name(const char *name, unsigned char *sha1) return NULL; } -static void check_linked_checkout(struct branch_info *new, const char *id) -{ - struct strbuf sb = STRBUF_INIT; - struct strbuf path = STRBUF_INIT; - struct strbuf gitdir = STRBUF_INIT; - const char *start, *end; - - if (id) - strbuf_addf(&path, "%s/worktrees/%s/HEAD", get_git_common_dir(), id); - else - strbuf_addf(&path, "%s/HEAD", get_git_common_dir()); - - if (strbuf_read_file(&sb, path.buf, 0) < 0 || - !skip_prefix(sb.buf, "ref:", &start)) - goto done; - while (isspace(*start)) - start++; - end = start; - while (*end && !isspace(*end)) - end++; - if (strncmp(start, new->path, end - start) || new->path[end - start] != '\0') - goto done; - if (id) { - strbuf_reset(&path); - strbuf_addf(&path, "%s/worktrees/%s/gitdir", get_git_common_dir(), id); - if (strbuf_read_file(&gitdir, path.buf, 0) <= 0) - goto done; - strbuf_rtrim(&gitdir); - } else - strbuf_addstr(&gitdir, get_git_common_dir()); - die(_("'%s' is already checked out at '%s'"), new->name, gitdir.buf); -done: - strbuf_release(&path); - strbuf_release(&sb); - strbuf_release(&gitdir); -} - -static void check_linked_checkouts(struct branch_info *new) -{ - struct strbuf path = STRBUF_INIT; - DIR *dir; - struct dirent *d; - - strbuf_addf(&path, "%s/worktrees", get_git_common_dir()); - if ((dir = opendir(path.buf)) == NULL) { - strbuf_release(&path); - return; - } - - /* - * $GIT_COMMON_DIR/HEAD is practically outside - * $GIT_DIR so resolve_ref_unsafe() won't work (it - * uses git_path). Parse the ref ourselves. - */ - check_linked_checkout(new, NULL); - - while ((d = readdir(dir)) != NULL) { - if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, "..")) - continue; - check_linked_checkout(new, d->d_name); - } - strbuf_release(&path); - closedir(dir); -} - static int parse_branchname_arg(int argc, const char **argv, int dwim_new_local_branch_ok, struct branch_info *new, struct checkout_opts *opts, - unsigned char rev[20]) + struct object_id *rev) { struct tree **source_tree = &opts->source_tree; const char **new_branch = &opts->new_branch; - int force_detach = opts->force_detach; int argcount = 0; - unsigned char branch_rev[20]; + struct object_id branch_rev; const char *arg; int dash_dash_pos; int has_dash_dash = 0; @@ -1178,7 +972,7 @@ static int parse_branchname_arg(int argc, const char **argv, if (!strcmp(arg, "-")) arg = "@{-1}"; - if (get_sha1_mb(arg, rev)) { + if (get_oid_mb(arg, rev)) { /* * Either case (3) or (4), with <something> not being * a commit, or an attempt to use case (1) with an @@ -1189,7 +983,8 @@ static int parse_branchname_arg(int argc, const char **argv, */ int recover_with_dwim = dwim_new_local_branch_ok; - if (check_filename(NULL, arg) && !has_dash_dash) + if (!has_dash_dash && + (check_filename(opts->prefix, arg) || !no_wildcard(arg))) recover_with_dwim = 0; /* * Accept "git checkout foo" and "git checkout foo --" @@ -1226,26 +1021,15 @@ static int parse_branchname_arg(int argc, const char **argv, setup_branch_path(new); if (!check_refname_format(new->path, 0) && - !read_ref(new->path, branch_rev)) - hashcpy(rev, branch_rev); + !read_ref(new->path, branch_rev.hash)) + oidcpy(rev, &branch_rev); else new->path = NULL; /* not an existing branch */ - if (new->path && !force_detach && !*new_branch) { - unsigned char sha1[20]; - int flag; - char *head_ref = resolve_refdup("HEAD", 0, sha1, &flag); - if (head_ref && - (!(flag & REF_ISSYMREF) || strcmp(head_ref, new->path)) && - !opts->ignore_other_worktrees) - check_linked_checkouts(new); - free(head_ref); - } - - new->commit = lookup_commit_reference_gently(rev, 1); + new->commit = lookup_commit_reference_gently(rev->hash, 1); if (!new->commit) { /* not a commit */ - *source_tree = parse_tree_indirect(rev); + *source_tree = parse_tree_indirect(rev->hash); } else { parse_commit_or_die(new->commit); *source_tree = new->commit->tree; @@ -1253,7 +1037,7 @@ static int parse_branchname_arg(int argc, const char **argv, if (!*source_tree) /* case (1): want a tree */ die(_("reference is not a tree: %s"), arg); - if (!has_dash_dash) {/* case (3).(d) -> (1) */ + if (!has_dash_dash) { /* case (3).(d) -> (1) */ /* * Do not complain the most common case * git checkout branch @@ -1261,7 +1045,7 @@ static int parse_branchname_arg(int argc, const char **argv, * it would be extremely annoying. */ if (argc) - verify_non_filename(NULL, arg); + verify_non_filename(opts->prefix, arg); } else { argcount++; argv++; @@ -1321,15 +1105,23 @@ static int checkout_branch(struct checkout_opts *opts, die(_("Cannot switch branch to a non-commit '%s'"), new->name); - if (opts->new_worktree) - return prepare_linked_checkout(opts, new); + if (new->path && !opts->force_detach && !opts->new_branch && + !opts->ignore_other_worktrees) { + struct object_id oid; + int flag; + char *head_ref = resolve_refdup("HEAD", 0, oid.hash, &flag); + if (head_ref && + (!(flag & REF_ISSYMREF) || strcmp(head_ref, new->path))) + die_if_checked_out(new->path, 1); + free(head_ref); + } if (!new->commit && opts->new_branch) { - unsigned char rev[20]; + struct object_id rev; int flag; - if (!read_ref_full("HEAD", 0, rev, &flag) && - (flag & REF_ISSYMREF) && is_null_sha1(rev)) + if (!read_ref_full("HEAD", 0, rev.hash, &flag) && + (flag & REF_ISSYMREF) && is_null_oid(&rev)) return switch_unborn_to_new_branch(opts); } return switch_branches(opts, new); @@ -1348,7 +1140,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) OPT_STRING('B', NULL, &opts.new_branch_force, N_("branch"), N_("create/reset and checkout a branch")), OPT_BOOL('l', NULL, &opts.new_branch_log, N_("create reflog for new branch")), - OPT_BOOL(0, "detach", &opts.force_detach, N_("detach the HEAD at named commit")), + OPT_BOOL(0, "detach", &opts.force_detach, N_("detach HEAD at named commit")), OPT_SET_INT('t', "track", &opts.track, N_("set upstream info for new branch"), BRANCH_TRACK_EXPLICIT), OPT_STRING(0, "orphan", &opts.new_orphan_branch, N_("new-branch"), N_("new unparented branch")), @@ -1366,10 +1158,9 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) N_("do not limit pathspecs to sparse entries only")), OPT_HIDDEN_BOOL(0, "guess", &dwim_new_local_branch, N_("second guess 'git checkout <no-such-branch>'")), - OPT_FILENAME(0, "to", &opts.new_worktree, - N_("check a branch out in a separate working directory")), OPT_BOOL(0, "ignore-other-worktrees", &opts.ignore_other_worktrees, N_("do not check if another worktree is holding the given ref")), + OPT_BOOL(0, "progress", &opts.show_progress, N_("force progress reporting")), OPT_END(), }; @@ -1377,9 +1168,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) memset(&new, 0, sizeof(new)); opts.overwrite_ignore = 1; opts.prefix = prefix; - - opts.saved_argv = xmalloc(sizeof(const char *) * (argc + 2)); - memcpy(opts.saved_argv, argv, sizeof(const char *) * (argc + 1)); + opts.show_progress = -1; gitmodules_config(); git_config(git_checkout_config, &opts); @@ -1389,13 +1178,12 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) argc = parse_options(argc, argv, prefix, options, checkout_usage, PARSE_OPT_KEEP_DASHDASH); - /* recursive execution from checkout_new_worktree() */ - opts.new_worktree_mode = getenv("GIT_CHECKOUT_NEW_WORKTREE") != NULL; - if (opts.new_worktree_mode) - opts.new_worktree = NULL; - - if (!opts.new_worktree) - setup_work_tree(); + if (opts.show_progress < 0) { + if (opts.quiet) + opts.show_progress = 0; + else + opts.show_progress = isatty(2); + } if (conflict_style) { opts.merge = 1; /* implied */ @@ -1443,14 +1231,14 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) * remote branches, erroring out for invalid or ambiguous cases. */ if (argc) { - unsigned char rev[20]; + struct object_id rev; int dwim_ok = !opts.patch_mode && dwim_new_local_branch && opts.track == BRANCH_TRACK_UNSPECIFIED && !opts.new_branch; int n = parse_branchname_arg(argc, argv, dwim_ok, - &new, &opts, rev); + &new, &opts, &rev); argv += n; argc -= n; } diff --git a/builtin/clean.c b/builtin/clean.c index 6dcb72e644..0371010afb 100644 --- a/builtin/clean.c +++ b/builtin/clean.c @@ -10,7 +10,6 @@ #include "cache.h" #include "dir.h" #include "parse-options.h" -#include "refs.h" #include "string-list.h" #include "quote.h" #include "column.h" @@ -155,13 +154,11 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag, struct strbuf quoted = STRBUF_INIT; struct dirent *e; int res = 0, ret = 0, gone = 1, original_len = path->len, len; - unsigned char submodule_head[20]; struct string_list dels = STRING_LIST_INIT_DUP; *dir_gone = 1; - if ((force_flag & REMOVE_DIR_KEEP_NESTED_GIT) && - !resolve_gitlink_ref(path->buf, "HEAD", submodule_head)) { + if ((force_flag & REMOVE_DIR_KEEP_NESTED_GIT) && is_nonbare_repository_dir(path)) { if (!quiet) { quote_path_relative(path->buf, prefix, "ed); printf(dry_run ? _(msg_would_skip_git_dir) : _(msg_skip_git_dir), @@ -184,8 +181,7 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag, return res; } - if (path->buf[original_len - 1] != '/') - strbuf_addch(path, '/'); + strbuf_complete(path, '/'); len = path->len; while ((e = readdir(dir)) != NULL) { @@ -547,7 +543,7 @@ static int *list_and_choose(struct menu_opts *opts, struct menu_stuff *stuff) int eof = 0; int i; - chosen = xmalloc(sizeof(int) * stuff->nr); + ALLOC_ARRAY(chosen, stuff->nr); /* set chosen as uninitialized */ for (i = 0; i < stuff->nr; i++) chosen[i] = -1; @@ -574,7 +570,7 @@ static int *list_and_choose(struct menu_opts *opts, struct menu_stuff *stuff) clean_get_color(CLEAN_COLOR_RESET)); } - if (strbuf_getline(&choice, stdin, '\n') != EOF) { + if (strbuf_getline_lf(&choice, stdin) != EOF) { strbuf_trim(&choice); } else { eof = 1; @@ -619,7 +615,7 @@ static int *list_and_choose(struct menu_opts *opts, struct menu_stuff *stuff) nr += chosen[i]; } - result = xcalloc(nr + 1, sizeof(int)); + result = xcalloc(st_add(nr, 1), sizeof(int)); for (i = 0; i < stuff->nr && j < nr; i++) { if (chosen[i]) result[j++] = i; @@ -656,7 +652,7 @@ static int filter_by_patterns_cmd(void) clean_print_color(CLEAN_COLOR_PROMPT); printf(_("Input ignore patterns>> ")); clean_print_color(CLEAN_COLOR_RESET); - if (strbuf_getline(&confirm, stdin, '\n') != EOF) + if (strbuf_getline_lf(&confirm, stdin) != EOF) strbuf_trim(&confirm); else putchar('\n'); @@ -754,7 +750,7 @@ static int ask_each_cmd(void) qname = quote_path_relative(item->string, NULL, &buf); /* TRANSLATORS: Make sure to keep [y/N] as is */ printf(_("Remove %s [y/N]? "), qname); - if (strbuf_getline(&confirm, stdin, '\n') != EOF) { + if (strbuf_getline_lf(&confirm, stdin) != EOF) { strbuf_trim(&confirm); } else { putchar('\n'); diff --git a/builtin/clone.c b/builtin/clone.c index 00535d0178..6c76a6ed66 100644 --- a/builtin/clone.c +++ b/builtin/clone.c @@ -40,16 +40,22 @@ static const char * const builtin_clone_usage[] = { static int option_no_checkout, option_bare, option_mirror, option_single_branch = -1; static int option_local = -1, option_no_hardlinks, option_shared, option_recursive; -static char *option_template, *option_depth; +static int option_shallow_submodules; +static int deepen; +static char *option_template, *option_depth, *option_since; static char *option_origin = NULL; static char *option_branch = NULL; +static struct string_list option_not = STRING_LIST_INIT_NODUP; static const char *real_git_dir; static char *option_upload_pack = "git-upload-pack"; static int option_verbosity; static int option_progress = -1; -static struct string_list option_config; -static struct string_list option_reference; +static enum transport_family family; +static struct string_list option_config = STRING_LIST_INIT_NODUP; +static struct string_list option_required_reference = STRING_LIST_INIT_NODUP; +static struct string_list option_optional_reference = STRING_LIST_INIT_NODUP; static int option_dissociate; +static int max_jobs = -1; static struct option builtin_clone_options[] = { OPT__VERBOSITY(&option_verbosity), @@ -72,10 +78,14 @@ static struct option builtin_clone_options[] = { N_("initialize submodules in the clone")), OPT_BOOL(0, "recurse-submodules", &option_recursive, N_("initialize submodules in the clone")), + OPT_INTEGER('j', "jobs", &max_jobs, + N_("number of submodules cloned in parallel")), OPT_STRING(0, "template", &option_template, N_("template-directory"), N_("directory from which templates will be used")), - OPT_STRING_LIST(0, "reference", &option_reference, N_("repo"), + OPT_STRING_LIST(0, "reference", &option_required_reference, N_("repo"), N_("reference repository")), + OPT_STRING_LIST(0, "reference-if-able", &option_optional_reference, + N_("repo"), N_("reference repository")), OPT_BOOL(0, "dissociate", &option_dissociate, N_("use --reference only while cloning")), OPT_STRING('o', "origin", &option_origin, N_("name"), @@ -86,107 +96,164 @@ static struct option builtin_clone_options[] = { N_("path to git-upload-pack on the remote")), OPT_STRING(0, "depth", &option_depth, N_("depth"), N_("create a shallow clone of that depth")), + OPT_STRING(0, "shallow-since", &option_since, N_("time"), + N_("create a shallow clone since a specific time")), + OPT_STRING_LIST(0, "shallow-exclude", &option_not, N_("revision"), + N_("deepen history of shallow clone by excluding rev")), OPT_BOOL(0, "single-branch", &option_single_branch, N_("clone only one branch, HEAD or --branch")), + OPT_BOOL(0, "shallow-submodules", &option_shallow_submodules, + N_("any cloned submodules will be shallow")), OPT_STRING(0, "separate-git-dir", &real_git_dir, N_("gitdir"), N_("separate git dir from working tree")), OPT_STRING_LIST('c', "config", &option_config, N_("key=value"), N_("set config inside the new repository")), + OPT_SET_INT('4', "ipv4", &family, N_("use IPv4 addresses only"), + TRANSPORT_FAMILY_IPV4), + OPT_SET_INT('6', "ipv6", &family, N_("use IPv6 addresses only"), + TRANSPORT_FAMILY_IPV6), OPT_END() }; -static const char *argv_submodule[] = { - "submodule", "update", "--init", "--recursive", NULL -}; - -static char *get_repo_path(const char *repo, int *is_bundle) +static const char *get_repo_path_1(struct strbuf *path, int *is_bundle) { static char *suffix[] = { "/.git", "", ".git/.git", ".git" }; static char *bundle_suffix[] = { ".bundle", "" }; + size_t baselen = path->len; struct stat st; int i; for (i = 0; i < ARRAY_SIZE(suffix); i++) { - const char *path; - path = mkpath("%s%s", repo, suffix[i]); - if (stat(path, &st)) + strbuf_setlen(path, baselen); + strbuf_addstr(path, suffix[i]); + if (stat(path->buf, &st)) continue; - if (S_ISDIR(st.st_mode) && is_git_directory(path)) { + if (S_ISDIR(st.st_mode) && is_git_directory(path->buf)) { *is_bundle = 0; - return xstrdup(absolute_path(path)); + return path->buf; } 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); + const char *dst; + int len, fd = open(path->buf, 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) { + dst = read_gitfile(path->buf); + if (dst) { *is_bundle = 0; - return xstrdup(absolute_path(path)); + return dst; } } } for (i = 0; i < ARRAY_SIZE(bundle_suffix); i++) { - const char *path; - path = mkpath("%s%s", repo, bundle_suffix[i]); - if (!stat(path, &st) && S_ISREG(st.st_mode)) { + strbuf_setlen(path, baselen); + strbuf_addstr(path, bundle_suffix[i]); + if (!stat(path->buf, &st) && S_ISREG(st.st_mode)) { *is_bundle = 1; - return xstrdup(absolute_path(path)); + return path->buf; } } return NULL; } +static char *get_repo_path(const char *repo, int *is_bundle) +{ + struct strbuf path = STRBUF_INIT; + const char *raw; + char *canon; + + strbuf_addstr(&path, repo); + raw = get_repo_path_1(&path, is_bundle); + canon = raw ? xstrdup(absolute_path(raw)) : NULL; + strbuf_release(&path); + return canon; +} + static char *guess_dir_name(const char *repo, int is_bundle, int is_bare) { - const char *end = repo + strlen(repo), *start; + const char *end = repo + strlen(repo), *start, *ptr; + size_t len; char *dir; /* + * Skip scheme. + */ + start = strstr(repo, "://"); + if (start == NULL) + start = repo; + else + start += 3; + + /* + * Skip authentication data. The stripping does happen + * greedily, such that we strip up to the last '@' inside + * the host part. + */ + for (ptr = start; ptr < end && !is_dir_sep(*ptr); ptr++) { + if (*ptr == '@') + start = ptr + 1; + } + + /* * Strip trailing spaces, slashes and /.git */ - while (repo < end && (is_dir_sep(end[-1]) || isspace(end[-1]))) + while (start < end && (is_dir_sep(end[-1]) || isspace(end[-1]))) end--; - if (end - repo > 5 && is_dir_sep(end[-5]) && + if (end - start > 5 && is_dir_sep(end[-5]) && !strncmp(end - 4, ".git", 4)) { end -= 5; - while (repo < end && is_dir_sep(end[-1])) + while (start < end && is_dir_sep(end[-1])) end--; } /* - * Find last component, but be prepared that repo could have - * the form "remote.example.com:foo.git", i.e. no slash - * in the directory part. + * Strip trailing port number if we've got only a + * hostname (that is, there is no dir separator but a + * colon). This check is required such that we do not + * strip URI's like '/foo/bar:2222.git', which should + * result in a dir '2222' being guessed due to backwards + * compatibility. */ - start = end; - while (repo < start && !is_dir_sep(start[-1]) && start[-1] != ':') - start--; + if (memchr(start, '/', end - start) == NULL + && memchr(start, ':', end - start) != NULL) { + ptr = end; + while (start < ptr && isdigit(ptr[-1]) && ptr[-1] != ':') + ptr--; + if (start < ptr && ptr[-1] == ':') + end = ptr - 1; + } + + /* + * Find last component. To remain backwards compatible we + * also regard colons as path separators, such that + * cloning a repository 'foo:bar.git' would result in a + * directory 'bar' being guessed. + */ + ptr = end; + while (start < ptr && !is_dir_sep(ptr[-1]) && ptr[-1] != ':') + ptr--; + start = ptr; /* * Strip .{bundle,git}. */ - if (is_bundle) { - if (end - start > 7 && !strncmp(end - 7, ".bundle", 7)) - end -= 7; - } else { - if (end - start > 4 && !strncmp(end - 4, ".git", 4)) - end -= 4; - } + len = end - start; + strip_suffix_mem(start, &len, is_bundle ? ".bundle" : ".git"); - if (is_bare) { - struct strbuf result = STRBUF_INIT; - strbuf_addf(&result, "%.*s.git", (int)(end - start), start); - dir = strbuf_detach(&result, NULL); - } else - dir = xstrndup(start, end - start); + if (!len || (len == 1 && *start == '/')) + die(_("No directory name could be guessed.\n" + "Please specify a directory on the command line")); + + if (is_bare) + dir = xstrfmt("%.*s.git", (int)len, start); + else + dir = xstrndup(start, len); /* * Replace sequences of 'control' characters and whitespace * with one ascii space, remove leading and trailing spaces. @@ -224,45 +291,37 @@ static void strip_trailing_slashes(char *dir) static int add_one_reference(struct string_list_item *item, void *cb_data) { - char *ref_git; - const char *repo; - struct strbuf alternate = STRBUF_INIT; - - /* Beware: read_gitfile(), real_path() and mkpath() return static buffer */ - ref_git = xstrdup(real_path(item->string)); - - repo = read_gitfile(ref_git); - if (!repo) - repo = read_gitfile(mkpath("%s/.git", ref_git)); - if (repo) { - free(ref_git); - ref_git = xstrdup(repo); - } - - if (!repo && is_directory(mkpath("%s/.git/objects", ref_git))) { - char *ref_git_git = mkpathdup("%s/.git", ref_git); - free(ref_git); - ref_git = ref_git_git; - } else if (!is_directory(mkpath("%s/objects", ref_git))) - die(_("reference repository '%s' is not a local repository."), - item->string); - - if (!access(mkpath("%s/shallow", ref_git), F_OK)) - die(_("reference repository '%s' is shallow"), item->string); - - if (!access(mkpath("%s/info/grafts", ref_git), F_OK)) - die(_("reference repository '%s' is grafted"), item->string); - - strbuf_addf(&alternate, "%s/objects", ref_git); - add_to_alternates_file(alternate.buf); - strbuf_release(&alternate); + struct strbuf err = STRBUF_INIT; + int *required = cb_data; + char *ref_git = compute_alternate_path(item->string, &err); + + if (!ref_git) { + if (*required) + die("%s", err.buf); + else + fprintf(stderr, + _("info: Could not add alternate for '%s': %s\n"), + item->string, err.buf); + } else { + struct strbuf sb = STRBUF_INIT; + strbuf_addf(&sb, "%s/objects", ref_git); + add_to_alternates_file(sb.buf); + strbuf_release(&sb); + } + + strbuf_release(&err); free(ref_git); return 0; } static void setup_reference(void) { - for_each_string_list(&option_reference, add_one_reference, NULL); + int required = 1; + for_each_string_list(&option_required_reference, + add_one_reference, &required); + required = 0; + for_each_string_list(&option_optional_reference, + add_one_reference, &required); } static void copy_alternates(struct strbuf *src, struct strbuf *dst, @@ -283,7 +342,7 @@ static void copy_alternates(struct strbuf *src, struct strbuf *dst, FILE *in = fopen(src->buf, "r"); struct strbuf line = STRBUF_INIT; - while (strbuf_getline(&line, in, '\n') != EOF) { + while (strbuf_getline(&line, in) != EOF) { char *abs_path; if (!line.len || line.buf[0] == '#') continue; @@ -292,8 +351,11 @@ static void copy_alternates(struct strbuf *src, struct strbuf *dst, continue; } abs_path = mkpathdup("%s/objects/%s", src_repo, line.buf); - normalize_path_copy(abs_path, abs_path); - add_to_alternates_file(abs_path); + if (!normalize_path_copy(abs_path, abs_path)) + add_to_alternates_file(abs_path); + else + warning("skipping invalid relative alternate: %s/%s", + src_repo, line.buf); free(abs_path); } strbuf_release(&line); @@ -373,8 +435,10 @@ static void clone_local(const char *src_repo, const char *dest_repo) } 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); + get_common_dir(&src, src_repo); + get_common_dir(&dest, dest_repo); + strbuf_addstr(&src, "/objects"); + strbuf_addstr(&dest, "/objects"); copy_or_link_directory(&src, &dest, src_repo, src.len); strbuf_release(&src); strbuf_release(&dest); @@ -491,16 +555,26 @@ static void write_remote_refs(const struct ref *local_refs) { const struct ref *r; - lock_packed_refs(LOCK_DIE_ON_ERROR); + struct ref_transaction *t; + struct strbuf err = STRBUF_INIT; + + t = ref_transaction_begin(&err); + if (!t) + die("%s", err.buf); for (r = local_refs; r; r = r->next) { if (!r->peer_ref) continue; - add_packed_ref(r->peer_ref->name, r->old_sha1); + if (ref_transaction_create(t, r->peer_ref->name, r->old_oid.hash, + 0, NULL, &err)) + die("%s", err.buf); } - if (commit_packed_refs()) - die_errno("unable to overwrite old ref-pack file"); + if (initial_ref_transaction_commit(t, &err)) + die("%s", err.buf); + + strbuf_release(&err); + ref_transaction_free(t); } static void write_followtags(const struct ref *refs, const char *msg) @@ -511,9 +585,9 @@ static void write_followtags(const struct ref *refs, const char *msg) continue; if (ends_with(ref->name, "^{}")) continue; - if (!has_sha1_file(ref->old_sha1)) + if (!has_object_file(&ref->old_oid)) continue; - update_ref(msg, ref->name, ref->old_sha1, + update_ref(msg, ref->name, ref->old_oid.hash, NULL, 0, UPDATE_REFS_DIE_ON_ERR); } } @@ -533,7 +607,7 @@ static int iterate_ref_map(void *cb_data, unsigned char sha1[20]) if (!ref) return -1; - hashcpy(sha1, ref->old_sha1); + hashcpy(sha1, ref->old_oid.hash); *rm = ref->next; return 0; } @@ -549,13 +623,13 @@ static void update_remote_refs(const struct ref *refs, const struct ref *rm = mapped_refs; if (check_connectivity) { - if (transport->progress) - fprintf(stderr, _("Checking connectivity... ")); - if (check_everything_connected_with_transport(iterate_ref_map, - 0, &rm, transport)) + struct check_connected_options opt = CHECK_CONNECTED_INIT; + + opt.transport = transport; + opt.progress = transport->progress; + + if (check_connected(iterate_ref_map, &rm, &opt)) die(_("remote did not send all necessary objects")); - if (transport->progress) - fprintf(stderr, _("done.\n")); } if (refs) { @@ -568,9 +642,11 @@ static void update_remote_refs(const struct ref *refs, struct strbuf head_ref = STRBUF_INIT; strbuf_addstr(&head_ref, branch_top); strbuf_addstr(&head_ref, "HEAD"); - create_symref(head_ref.buf, - remote_head_points_at->peer_ref->name, - msg); + if (create_symref(head_ref.buf, + remote_head_points_at->peer_ref->name, + msg) < 0) + die(_("unable to update %s"), head_ref.buf); + strbuf_release(&head_ref); } } @@ -580,16 +656,17 @@ static void update_head(const struct ref *our, const struct ref *remote, const char *head; if (our && skip_prefix(our->name, "refs/heads/", &head)) { /* Local default branch link */ - create_symref("HEAD", our->name, NULL); + if (create_symref("HEAD", our->name, NULL) < 0) + die(_("unable to update HEAD")); if (!option_bare) { - update_ref(msg, "HEAD", our->old_sha1, NULL, 0, + update_ref(msg, "HEAD", our->old_oid.hash, NULL, 0, UPDATE_REFS_DIE_ON_ERR); install_branch_config(0, head, option_origin, our->name); } } else if (our) { - struct commit *c = lookup_commit_reference(our->old_sha1); + struct commit *c = lookup_commit_reference(our->old_oid.hash); /* --branch specifies a non-branch (i.e. tags), detach HEAD */ - update_ref(msg, "HEAD", c->object.sha1, + update_ref(msg, "HEAD", c->object.oid.hash, NULL, REF_NODEREF, UPDATE_REFS_DIE_ON_ERR); } else if (remote) { /* @@ -597,12 +674,12 @@ static void update_head(const struct ref *our, const struct ref *remote, * HEAD points to a branch but we don't know which one. * Detach HEAD in all these cases. */ - update_ref(msg, "HEAD", remote->old_sha1, + update_ref(msg, "HEAD", remote->old_oid.hash, NULL, REF_NODEREF, UPDATE_REFS_DIE_ON_ERR); } } -static int checkout(void) +static int checkout(int submodule_progress) { unsigned char sha1[20]; char *head; @@ -656,15 +733,29 @@ static int checkout(void) err |= run_hook_le(NULL, "post-checkout", sha1_to_hex(null_sha1), sha1_to_hex(sha1), "1", NULL); - if (!err && option_recursive) - err = run_command_v_opt(argv_submodule, RUN_GIT_CMD); + if (!err && option_recursive) { + struct argv_array args = ARGV_ARRAY_INIT; + argv_array_pushl(&args, "submodule", "update", "--init", "--recursive", NULL); + + if (option_shallow_submodules == 1) + argv_array_push(&args, "--depth=1"); + + if (max_jobs != -1) + argv_array_pushf(&args, "--jobs=%d", max_jobs); + + if (submodule_progress) + argv_array_push(&args, "--progress"); + + err = run_command_v_opt(args.argv, RUN_GIT_CMD); + argv_array_clear(&args); + } return err; } static int write_one_config(const char *key, const char *value, void *data) { - return git_config_set_multivar(key, value ? value : "true", "^$", 0); + return git_config_set_multivar_gently(key, value ? value : "true", "^$", 0); } static void write_config(struct string_list *config) @@ -674,7 +765,7 @@ static void write_config(struct string_list *config) 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"); + die(_("unable to write parameters to config file")); } } @@ -733,11 +824,15 @@ static void write_refspec_config(const char *src_ref_prefix, static void dissociate_from_references(void) { static const char* argv[] = { "repack", "-a", "-d", NULL }; + char *alternates = git_pathdup("objects/info/alternates"); - if (run_command_v_opt(argv, RUN_GIT_CMD|RUN_COMMAND_NO_STDIN)) - die(_("cannot repack to clean up")); - if (unlink(git_path("objects/info/alternates")) && errno != ENOENT) - die_errno(_("cannot unlink temporary alternates file")); + if (!access(alternates, F_OK)) { + if (run_command_v_opt(argv, RUN_GIT_CMD|RUN_COMMAND_NO_STDIN)) + die(_("cannot repack to clean up")); + if (unlink(alternates) && errno != ENOENT) + die_errno(_("cannot unlink temporary alternates file")); + } + free(alternates); } int cmd_clone(int argc, const char **argv, const char *prefix) @@ -758,6 +853,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix) const char *src_ref_prefix = "refs/heads/"; struct remote *remote; int err = 0, complete_refs_before_fetch = 1; + int submodule_progress; struct refspec *refspec; const char *fetch_pattern; @@ -774,8 +870,10 @@ int cmd_clone(int argc, const char **argv, const char *prefix) usage_msg_opt(_("You must specify a repository to clone."), builtin_clone_usage, builtin_clone_options); + if (option_depth || option_since || option_not.nr) + deepen = 1; if (option_single_branch == -1) - option_single_branch = option_depth ? 1 : 0; + option_single_branch = deepen ? 1 : 0; if (option_mirror) option_bare = 1; @@ -848,23 +946,40 @@ int cmd_clone(int argc, const char **argv, const char *prefix) set_git_work_tree(work_tree); } - junk_git_dir = git_dir; + junk_git_dir = real_git_dir ? real_git_dir : git_dir; if (safe_create_leading_directories_const(git_dir) < 0) die(_("could not create leading directories of '%s'"), git_dir); - set_git_dir_init(git_dir, real_git_dir, 0); - if (real_git_dir) { - git_dir = real_git_dir; - junk_git_dir = real_git_dir; - } - if (0 <= option_verbosity) { if (option_bare) fprintf(stderr, _("Cloning into bare repository '%s'...\n"), dir); else fprintf(stderr, _("Cloning into '%s'...\n"), dir); } - init_db(option_template, INIT_DB_QUIET); + + if (option_recursive) { + if (option_required_reference.nr && + option_optional_reference.nr) + die(_("clone --recursive is not compatible with " + "both --reference and --reference-if-able")); + else if (option_required_reference.nr) { + string_list_append(&option_config, + "submodule.alternateLocation=superproject"); + string_list_append(&option_config, + "submodule.alternateErrorStrategy=die"); + } else if (option_optional_reference.nr) { + string_list_append(&option_config, + "submodule.alternateLocation=superproject"); + string_list_append(&option_config, + "submodule.alternateErrorStrategy=info"); + } + } + + init_db(git_dir, real_git_dir, option_template, INIT_DB_QUIET); + + if (real_git_dir) + git_dir = real_git_dir; + write_config(&option_config); git_config(git_default_config, NULL); @@ -884,12 +999,8 @@ int cmd_clone(int argc, const char **argv, const char *prefix) git_config_set(key.buf, repo); strbuf_reset(&key); - if (option_reference.nr) + if (option_required_reference.nr || option_optional_reference.nr) setup_reference(); - else if (option_dissociate) { - warning(_("--dissociate given, but there is no --reference")); - option_dissociate = 0; - } fetch_pattern = value.buf; refspec = parse_fetch_refspec(1, &fetch_pattern); @@ -899,12 +1010,17 @@ int cmd_clone(int argc, const char **argv, const char *prefix) remote = remote_get(option_origin); transport = transport_get(remote, remote->url[0]); transport_set_verbosity(transport, option_verbosity, option_progress); + transport->family = family; path = get_repo_path(remote->url[0], &is_bundle); is_local = option_local != 0 && path && !is_bundle; if (is_local) { if (option_depth) warning(_("--depth is ignored in local clones; use file:// instead.")); + if (option_since) + warning(_("--shallow-since is ignored in local clones; use file:// instead.")); + if (option_not.nr) + warning(_("--shallow-exclude is ignored in local clones; use file:// instead.")); if (!access(mkpath("%s/shallow", path), F_OK)) { if (option_local > 0) warning(_("source repository is shallow, ignoring --local")); @@ -923,6 +1039,12 @@ int cmd_clone(int argc, const char **argv, const char *prefix) if (option_depth) transport_set_option(transport, TRANS_OPT_DEPTH, option_depth); + if (option_since) + transport_set_option(transport, TRANS_OPT_DEEPEN_SINCE, + option_since); + if (option_not.nr) + transport_set_option(transport, TRANS_OPT_DEEPEN_NOT, + (const char *)&option_not); if (option_single_branch) transport_set_option(transport, TRANS_OPT_FOLLOWTAGS, "1"); @@ -930,7 +1052,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix) transport_set_option(transport, TRANS_OPT_UPLOADPACK, option_upload_pack); - if (transport->smart_options && !option_depth) + if (transport->smart_options && !deepen) transport->smart_options->check_self_contained_and_connected = 1; refs = transport_get_remote_refs(transport); @@ -948,7 +1070,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix) * remote HEAD check. */ for (ref = refs; ref; ref = ref->next) - if (is_null_sha1(ref->old_sha1)) { + if (is_null_oid(&ref->old_oid)) { complete_refs_before_fetch = 0; break; } @@ -1000,14 +1122,24 @@ int cmd_clone(int argc, const char **argv, const char *prefix) update_head(our_head_points_at, remote_head, reflog_msg.buf); + /* + * We want to show progress for recursive submodule clones iff + * we did so for the main clone. But only the transport knows + * the final decision for this flag, so we need to rescue the value + * before we free the transport. + */ + submodule_progress = transport->progress; + transport_unlock_pack(transport); transport_disconnect(transport); - if (option_dissociate) + if (option_dissociate) { + close_all_packs(); dissociate_from_references(); + } junk_mode = JUNK_LEAVE_REPO; - err = checkout(); + err = checkout(submodule_progress); strbuf_release(&reflog_msg); strbuf_release(&branch_top); diff --git a/builtin/column.c b/builtin/column.c index 449413c8a8..33314b4d71 100644 --- a/builtin/column.c +++ b/builtin/column.c @@ -51,7 +51,7 @@ int cmd_column(int argc, const char **argv, const char *prefix) die(_("--command must be the first argument")); } finalize_colopts(&colopts, -1); - while (!strbuf_getline(&sb, stdin, '\n')) + while (!strbuf_getline(&sb, stdin)) string_list_append(&list, sb.buf); print_columns(&list, colopts, &copts); diff --git a/builtin/commit-tree.c b/builtin/commit-tree.c index 25aa2cdef3..605017261c 100644 --- a/builtin/commit-tree.c +++ b/builtin/commit-tree.c @@ -10,17 +10,17 @@ #include "utf8.h" #include "gpg-interface.h" -static const char commit_tree_usage[] = "git commit-tree [(-p <sha1>)...] [-S[<keyid>]] [-m <message>] [-F <file>] <sha1> <changelog"; +static const char commit_tree_usage[] = "git commit-tree [(-p <sha1>)...] [-S[<keyid>]] [-m <message>] [-F <file>] <sha1>"; static const char *sign_commit; static void new_parent(struct commit *parent, struct commit_list **parents_p) { - unsigned char *sha1 = parent->object.sha1; + struct object_id *oid = &parent->object.oid; struct commit_list *parents; for (parents = *parents_p; parents; parents = parents->next) { if (parents->item == parent) { - error("duplicate parent %s ignored", sha1_to_hex(sha1)); + error("duplicate parent %s ignored", oid_to_hex(oid)); return; } parents_p = &parents->next; @@ -33,10 +33,6 @@ static int commit_tree_config(const char *var, const char *value, void *cb) int status = git_gpg_config(var, value, NULL); if (status) return status; - if (!strcmp(var, "commit.gpgsign")) { - sign_commit = git_config_bool(var, value) ? "" : NULL; - return 0; - } return git_default_config(var, value, cb); } @@ -44,8 +40,8 @@ int cmd_commit_tree(int argc, const char **argv, const char *prefix) { int i, got_tree = 0; struct commit_list *parents = NULL; - unsigned char tree_sha1[20]; - unsigned char commit_sha1[20]; + struct object_id tree_oid; + struct object_id commit_oid; struct strbuf buffer = STRBUF_INIT; git_config(commit_tree_config, NULL); @@ -56,13 +52,13 @@ int cmd_commit_tree(int argc, const char **argv, const char *prefix) for (i = 1; i < argc; i++) { const char *arg = argv[i]; if (!strcmp(arg, "-p")) { - unsigned char sha1[20]; + struct object_id oid; if (argc <= ++i) usage(commit_tree_usage); - if (get_sha1_commit(argv[i], sha1)) + if (get_sha1_commit(argv[i], oid.hash)) die("Not a valid object name %s", argv[i]); - assert_sha1_type(sha1, OBJ_COMMIT); - new_parent(lookup_commit(sha1), &parents); + assert_sha1_type(oid.hash, OBJ_COMMIT); + new_parent(lookup_commit(oid.hash), &parents); continue; } @@ -109,7 +105,7 @@ int cmd_commit_tree(int argc, const char **argv, const char *prefix) continue; } - if (get_sha1_tree(arg, tree_sha1)) + if (get_sha1_tree(arg, tree_oid.hash)) die("Not a valid object name %s", arg); if (got_tree) die("Cannot give more than one trees"); @@ -121,13 +117,13 @@ int cmd_commit_tree(int argc, const char **argv, const char *prefix) die_errno("git commit-tree: failed to read"); } - if (commit_tree(buffer.buf, buffer.len, tree_sha1, parents, - commit_sha1, NULL, sign_commit)) { + if (commit_tree(buffer.buf, buffer.len, tree_oid.hash, parents, + commit_oid.hash, NULL, sign_commit)) { strbuf_release(&buffer); return 1; } - printf("%s\n", sha1_to_hex(commit_sha1)); + printf("%s\n", oid_to_hex(&commit_oid)); strbuf_release(&buffer); return 0; } diff --git a/builtin/commit.c b/builtin/commit.c index 254477fd1d..276c74034e 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -32,6 +32,7 @@ #include "sequencer.h" #include "notes-utils.h" #include "mailmap.h" +#include "sigchain.h" static const char * const builtin_commit_usage[] = { N_("git commit [<options>] [--] <pathspec>..."), @@ -91,8 +92,9 @@ N_("If you wish to skip this commit, use:\n" "Then \"git cherry-pick --continue\" will resume cherry-picking\n" "the remaining commits.\n"); +static GIT_PATH_FUNC(git_path_commit_editmsg, "COMMIT_EDITMSG") + 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 */ static enum { @@ -113,6 +115,7 @@ static char *fixup_message, *squash_message; static int all, also, interactive, patch_interactive, only, amend, signoff; static int edit_flag = -1; /* unspecified */ static int quiet, verbose, no_verify, allow_empty, dry_run, renew_authorship; +static int config_commit_verbose = -1; /* unspecified */ static int no_post_rewrite, allow_empty_message; static char *untracked_files_arg, *force_date, *ignore_submodule_arg; static char *sign_commit; @@ -139,14 +142,24 @@ static int show_ignored_in_status, have_option_m; static const char *only_include_assumed; static struct strbuf message = STRBUF_INIT; -static enum status_format { - STATUS_FORMAT_NONE = 0, - STATUS_FORMAT_LONG, - STATUS_FORMAT_SHORT, - STATUS_FORMAT_PORCELAIN, +static enum wt_status_format status_format = STATUS_FORMAT_UNSPECIFIED; - STATUS_FORMAT_UNSPECIFIED -} status_format = STATUS_FORMAT_UNSPECIFIED; +static int opt_parse_porcelain(const struct option *opt, const char *arg, int unset) +{ + enum wt_status_format *value = (enum wt_status_format *)opt->value; + if (unset) + *value = STATUS_FORMAT_NONE; + else if (!arg) + *value = STATUS_FORMAT_PORCELAIN; + else if (!strcmp(arg, "v1") || !strcmp(arg, "1")) + *value = STATUS_FORMAT_PORCELAIN; + else if (!strcmp(arg, "v2") || !strcmp(arg, "2")) + *value = STATUS_FORMAT_PORCELAIN_V2; + else + die("unsupported porcelain version '%s'", arg); + + return 0; +} static int opt_parse_m(const struct option *opt, const char *arg, int unset) { @@ -166,11 +179,11 @@ static int opt_parse_m(const struct option *opt, const char *arg, int unset) static void determine_whence(struct wt_status *s) { - if (file_exists(git_path("MERGE_HEAD"))) + if (file_exists(git_path_merge_head())) whence = FROM_MERGE; - else if (file_exists(git_path("CHERRY_PICK_HEAD"))) { + else if (file_exists(git_path_cherry_pick_head())) { whence = FROM_CHERRY_PICK; - if (file_exists(git_path(SEQ_DIR))) + if (file_exists(git_path_seq_dir())) sequencer_in_use = 1; } else @@ -185,6 +198,7 @@ static void status_init_config(struct wt_status *s, config_fn_t fn) gitmodules_config(); git_config(fn, s); determine_whence(s); + init_diff_ui_defaults(); s->hints = advice_status_hints; /* must come after git_config() */ } @@ -299,7 +313,7 @@ static void create_base_index(const struct commit *current_head) opts.dst_index = &the_index; opts.fn = oneway_merge; - tree = parse_tree_indirect(current_head->object.sha1); + tree = parse_tree_indirect(current_head->object.oid.hash); if (!tree) die(_("failed to unpack HEAD tree object")); parse_tree(tree); @@ -324,6 +338,7 @@ static const char *prepare_index(int argc, const char **argv, const char *prefix struct string_list partial; struct pathspec pathspec; int refresh_flags = REFRESH_QUIET; + const char *ret; if (is_status) refresh_flags |= REFRESH_UNMERGED; @@ -344,7 +359,7 @@ static const char *prepare_index(int argc, const char **argv, const char *prefix die(_("unable to create temporary index")); old_index_env = getenv(INDEX_ENVIRONMENT); - setenv(INDEX_ENVIRONMENT, index_lock.filename.buf, 1); + setenv(INDEX_ENVIRONMENT, get_lock_file_path(&index_lock), 1); if (interactive_add(argc, argv, prefix, patch_interactive) != 0) die(_("interactive add failed")); @@ -355,7 +370,7 @@ static const char *prepare_index(int argc, const char **argv, const char *prefix unsetenv(INDEX_ENVIRONMENT); discard_cache(); - read_cache_from(index_lock.filename.buf); + read_cache_from(get_lock_file_path(&index_lock)); if (update_main_cache_tree(WRITE_TREE_SILENT) == 0) { if (reopen_lock_file(&index_lock) < 0) die(_("unable to write index file")); @@ -365,7 +380,7 @@ static const char *prepare_index(int argc, const char **argv, const char *prefix warning(_("Failed to update main cache tree")); commit_style = COMMIT_NORMAL; - return index_lock.filename.buf; + return get_lock_file_path(&index_lock); } /* @@ -388,7 +403,7 @@ static const char *prepare_index(int argc, const char **argv, const char *prefix if (write_locked_index(&the_index, &index_lock, CLOSE_LOCK)) die(_("unable to write new_index file")); commit_style = COMMIT_NORMAL; - return index_lock.filename.buf; + return get_lock_file_path(&index_lock); } /* @@ -404,10 +419,8 @@ static const char *prepare_index(int argc, const char **argv, const char *prefix hold_locked_index(&index_lock, 1); refresh_cache_or_die(refresh_flags); if (active_cache_changed - || !cache_tree_fully_valid(active_cache_tree)) { + || !cache_tree_fully_valid(active_cache_tree)) update_main_cache_tree(WRITE_TREE_SILENT); - active_cache_changed = 1; - } if (active_cache_changed) { if (write_locked_index(&the_index, &index_lock, COMMIT_LOCK)) @@ -475,9 +488,9 @@ static const char *prepare_index(int argc, const char **argv, const char *prefix die(_("unable to write temporary index file")); discard_cache(); - read_cache_from(false_lock.filename.buf); - - return false_lock.filename.buf; + ret = get_lock_file_path(&false_lock); + read_cache_from(ret); + return ret; } static int run_status(FILE *fp, const char *index_file, const char *prefix, int nowarn, @@ -497,24 +510,13 @@ static int run_status(FILE *fp, const char *index_file, const char *prefix, int s->fp = fp; s->nowarn = nowarn; s->is_initial = get_sha1(s->reference, sha1) ? 1 : 0; + if (!s->is_initial) + hashcpy(s->sha1_commit, sha1); + s->status_format = status_format; + s->ignore_submodule_arg = ignore_submodule_arg; wt_status_collect(s); - - switch (status_format) { - case STATUS_FORMAT_SHORT: - wt_shortstatus_print(s); - break; - case STATUS_FORMAT_PORCELAIN: - wt_porcelain_print(s); - break; - case STATUS_FORMAT_UNSPECIFIED: - die("BUG: finalize_deferred_config() should have been called"); - break; - case STATUS_FORMAT_NONE: - case STATUS_FORMAT_LONG: - wt_status_print(s); - break; - } + wt_status_print(s); return s->commitable; } @@ -694,7 +696,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix, } } - if (message.len) { + if (have_option_m) { strbuf_addbuf(&sb, &message); hook_arg1 = "message"; } else if (logfile && !strcmp(logfile, "-")) { @@ -712,7 +714,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix, char *buffer; buffer = strstr(use_message_buffer, "\n\n"); if (buffer) - strbuf_addstr(&sb, buffer + 2); + strbuf_addstr(&sb, skip_blank_lines(buffer + 2)); hook_arg1 = "commit"; hook_arg2 = use_message; } else if (fixup_message) { @@ -725,12 +727,21 @@ static int prepare_to_commit(const char *index_file, const char *prefix, 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) + } else if (!stat(git_path_merge_msg(), &statbuf)) { + /* + * prepend SQUASH_MSG here if it exists and a + * "merge --squash" was originally performed + */ + if (!stat(git_path_squash_msg(), &statbuf)) { + if (strbuf_read_file(&sb, git_path_squash_msg(), 0) < 0) + die_errno(_("could not read SQUASH_MSG")); + hook_arg1 = "squash"; + } else + hook_arg1 = "merge"; + if (strbuf_read_file(&sb, git_path_merge_msg(), 0) < 0) die_errno(_("could not read MERGE_MSG")); - hook_arg1 = "merge"; - } else if (!stat(git_path("SQUASH_MSG"), &statbuf)) { - if (strbuf_read_file(&sb, git_path("SQUASH_MSG"), 0) < 0) + } else if (!stat(git_path_squash_msg(), &statbuf)) { + if (strbuf_read_file(&sb, git_path_squash_msg(), 0) < 0) die_errno(_("could not read SQUASH_MSG")); hook_arg1 = "squash"; } else if (template_file) { @@ -761,9 +772,9 @@ static int prepare_to_commit(const char *index_file, const char *prefix, hook_arg2 = ""; } - s->fp = fopen(git_path(commit_editmsg), "w"); + s->fp = fopen_for_writing(git_path_commit_editmsg()); if (s->fp == NULL) - die_errno(_("could not open '%s'"), git_path(commit_editmsg)); + die_errno(_("could not open '%s'"), git_path_commit_editmsg()); /* Ignore status.displayCommentPrefix: we do need comments in COMMIT_EDITMSG. */ old_display_comment_prefix = s->display_comment_prefix; @@ -776,7 +787,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix, s->hints = 0; if (clean_message_contents) - stripspace(&sb, 0); + strbuf_stripspace(&sb, 0); if (signoff) append_signoff(&sb, ignore_non_trailer(&sb), 0); @@ -856,7 +867,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix, _("%s" "Date: %s"), ident_shown++ ? "" : "\n", - show_ident_date(&ai, DATE_NORMAL)); + show_ident_date(&ai, DATE_MODE(NORMAL))); if (!committer_ident_sufficiently_given()) status_printf_ln(s, GIT_COLOR_NORMAL, @@ -883,9 +894,14 @@ static int prepare_to_commit(const char *index_file, const char *prefix, if (amend) parent = "HEAD^1"; - if (get_sha1(parent, sha1)) - commitable = !!active_nr; - else { + if (get_sha1(parent, sha1)) { + int i, ita_nr = 0; + + for (i = 0; i < active_nr; i++) + if (ce_intent_to_add(active_cache[i])) + ita_nr++; + commitable = active_nr - ita_nr > 0; + } else { /* * Unless the user did explicitly request a submodule * ignore mode by passing a command line option we do @@ -899,7 +915,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix, if (ignore_submodule_arg && !strcmp(ignore_submodule_arg, "all")) diff_flags |= DIFF_OPT_IGNORE_SUBMODULES; - commitable = index_differs_from(parent, diff_flags); + commitable = index_differs_from(parent, diff_flags, 1); } } strbuf_release(&committer_ident); @@ -940,7 +956,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix, } if (run_commit_hook(use_editor, index_file, "prepare-commit-msg", - git_path(commit_editmsg), hook_arg1, hook_arg2, NULL)) + git_path_commit_editmsg(), hook_arg1, hook_arg2, NULL)) return 0; if (use_editor) { @@ -948,7 +964,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix, const char *env[2] = { NULL }; env[0] = index; snprintf(index, sizeof(index), "GIT_INDEX_FILE=%s", index_file); - if (launch_editor(git_path(commit_editmsg), NULL, env)) { + if (launch_editor(git_path_commit_editmsg(), NULL, env)) { fprintf(stderr, _("Please supply the message using either -m or -F option.\n")); exit(1); @@ -956,7 +972,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix, } if (!no_verify && - run_commit_hook(use_editor, index_file, "commit-msg", git_path(commit_editmsg), NULL)) { + run_commit_hook(use_editor, index_file, "commit-msg", git_path_commit_editmsg(), NULL)) { return 0; } @@ -1015,7 +1031,7 @@ static int template_untouched(struct strbuf *sb) if (!template_file || strbuf_read_file(&tmpl, template_file, 0) <= 0) return 0; - stripspace(&tmpl, cleanup_mode == CLEANUP_ALL); + strbuf_stripspace(&tmpl, cleanup_mode == CLEANUP_ALL); if (!skip_prefix(sb->buf, tmpl.buf, &start)) start = sb->buf; strbuf_release(&tmpl); @@ -1046,7 +1062,7 @@ static const char *find_author_by_nickname(const char *name) commit = get_revision(&revs); if (commit) { struct pretty_print_context ctx = {0}; - ctx.date_mode = DATE_NORMAL; + ctx.date_mode.type = DATE_NORMAL; strbuf_release(&buf); format_commit_message(commit, "%aN <%aE>", &buf, &ctx); clear_mailmap(&mailmap); @@ -1087,7 +1103,7 @@ static const char *read_commit_message(const char *name) * is not in effect here. */ static struct status_deferred_config { - enum status_format status_format; + enum wt_status_format status_format; int show_branch; } status_deferred_config = { STATUS_FORMAT_UNSPECIFIED, @@ -1097,6 +1113,7 @@ static struct status_deferred_config { static void finalize_deferred_config(struct wt_status *s) { int use_deferred_config = (status_format != STATUS_FORMAT_PORCELAIN && + status_format != STATUS_FORMAT_PORCELAIN_V2 && !s->null_termination); if (s->null_termination) { @@ -1162,9 +1179,9 @@ static int parse_and_validate_options(int argc, const char *argv[], f++; if (f > 1) die(_("Only one of -c/-C/-F/--fixup can be used.")); - if (message.len && f > 0) + if (have_option_m && f > 0) die((_("Option -m cannot be combined with -c/-C/-F/--fixup."))); - if (f || message.len) + if (f || have_option_m) template_file = NULL; if (edit_message) use_message = edit_message; @@ -1189,10 +1206,8 @@ static int parse_and_validate_options(int argc, const char *argv[], if (also + only + all + interactive > 1) die(_("Only one of --include/--only/--all/--interactive/--patch can be used.")); - if (argc == 0 && (also || (only && !amend))) + if (argc == 0 && (also || (only && !amend && !allow_empty))) die(_("No paths with --include/--only does not make sense.")); - if (argc == 0 && only && amend) - only_include_assumed = _("Clever... amending the last one with dirty index."); if (argc > 0 && !also && !only) only_include_assumed = _("Explicit paths specified without -i or -o; assuming --only paths..."); if (!cleanup_arg || !strcmp(cleanup_arg, "default")) @@ -1324,9 +1339,9 @@ int cmd_status(int argc, const char **argv, const char *prefix) N_("show status concisely"), STATUS_FORMAT_SHORT), OPT_BOOL('b', "branch", &s.show_branch, N_("show branch information")), - OPT_SET_INT(0, "porcelain", &status_format, - N_("machine-readable output"), - STATUS_FORMAT_PORCELAIN), + { OPTION_CALLBACK, 0, "porcelain", &status_format, + N_("version"), N_("machine-readable output"), + PARSE_OPT_OPTARG, opt_parse_porcelain }, OPT_SET_INT(0, "long", &status_format, N_("show status in long format (default)"), STATUS_FORMAT_LONG), @@ -1368,7 +1383,13 @@ int cmd_status(int argc, const char **argv, const char *prefix) fd = hold_locked_index(&index_lock, 0); s.is_initial = get_sha1(s.reference, sha1) ? 1 : 0; + if (!s.is_initial) + hashcpy(s.sha1_commit, sha1); + s.ignore_submodule_arg = ignore_submodule_arg; + s.status_format = status_format; + s.verbose = verbose; + wt_status_collect(&s); if (0 <= fd) @@ -1377,23 +1398,7 @@ int cmd_status(int argc, const char **argv, const char *prefix) if (s.relative_paths) s.prefix = prefix; - switch (status_format) { - case STATUS_FORMAT_SHORT: - wt_shortstatus_print(&s); - break; - case STATUS_FORMAT_PORCELAIN: - wt_porcelain_print(&s); - break; - case STATUS_FORMAT_UNSPECIFIED: - die("BUG: finalize_deferred_config() should have been called"); - break; - case STATUS_FORMAT_NONE: - case STATUS_FORMAT_LONG: - s.verbose = verbose; - s.ignore_submodule_arg = ignore_submodule_arg; - wt_status_print(&s); - break; - } + wt_status_print(&s); return 0; } @@ -1505,6 +1510,11 @@ static int git_commit_config(const char *k, const char *v, void *cb) sign_commit = git_config_bool(k, v) ? "" : NULL; return 0; } + if (!strcmp(k, "commit.verbose")) { + int is_bool; + config_commit_verbose = git_config_bool_or_int(k, v, &is_bool); + return 0; + } status = git_gpg_config(k, v, NULL); if (status) @@ -1538,8 +1548,10 @@ static int run_rewrite_hook(const unsigned char *oldsha1, return code; n = snprintf(buf, sizeof(buf), "%s %s\n", sha1_to_hex(oldsha1), sha1_to_hex(newsha1)); + sigchain_push(SIGPIPE, SIG_IGN); write_in_full(proc.in, buf, n); close(proc.in); + sigchain_pop(SIGPIPE); return finish_command(&proc); } @@ -1598,7 +1610,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix) OPT_BOOL(0, "interactive", &interactive, N_("interactively add files")), OPT_BOOL('p', "patch", &patch_interactive, N_("interactively add changes")), OPT_BOOL('o', "only", &only, N_("commit only specified files")), - OPT_BOOL('n', "no-verify", &no_verify, N_("bypass pre-commit hook")), + OPT_BOOL('n', "no-verify", &no_verify, N_("bypass pre-commit and commit-msg hooks")), OPT_BOOL(0, "dry-run", &dry_run, N_("show what would be committed")), OPT_SET_INT(0, "short", &status_format, N_("show status concisely"), STATUS_FORMAT_SHORT), @@ -1628,7 +1640,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix) const char *index_file, *reflog_msg; char *nl; unsigned char sha1[20]; - struct commit_list *parents = NULL, **pptr = &parents; + struct commit_list *parents = NULL; struct stat statbuf; struct commit *current_head = NULL; struct commit_extra_header *extra = NULL; @@ -1649,9 +1661,13 @@ int cmd_commit(int argc, const char **argv, const char *prefix) if (parse_commit(current_head)) die(_("could not parse HEAD commit")); } + verbose = -1; /* unspecified */ argc = parse_and_validate_options(argc, argv, builtin_commit_options, builtin_commit_usage, prefix, current_head, &s); + if (verbose == -1) + verbose = (config_commit_verbose < 0) ? 0 : config_commit_verbose; + if (dry_run) return dry_run_commit(argc, argv, prefix, current_head, &s); index_file = prepare_index(argc, argv, prefix, current_head, 0); @@ -1670,36 +1686,34 @@ int cmd_commit(int argc, const char **argv, const char *prefix) if (!reflog_msg) reflog_msg = "commit (initial)"; } else if (amend) { - struct commit_list *c; - if (!reflog_msg) reflog_msg = "commit (amend)"; - for (c = current_head->parents; c; c = c->next) - pptr = &commit_list_insert(c->item, pptr)->next; + parents = copy_commit_list(current_head->parents); } else if (whence == FROM_MERGE) { struct strbuf m = STRBUF_INIT; FILE *fp; int allow_fast_forward = 1; + struct commit_list **pptr = &parents; if (!reflog_msg) reflog_msg = "commit (merge)"; - pptr = &commit_list_insert(current_head, pptr)->next; - fp = fopen(git_path("MERGE_HEAD"), "r"); + pptr = commit_list_append(current_head, pptr); + fp = fopen(git_path_merge_head(), "r"); if (fp == NULL) die_errno(_("could not open '%s' for reading"), - git_path("MERGE_HEAD")); - while (strbuf_getline(&m, fp, '\n') != EOF) { + git_path_merge_head()); + while (strbuf_getline_lf(&m, fp) != EOF) { struct commit *parent; parent = get_merge_parent(m.buf); if (!parent) die(_("Corrupt MERGE_HEAD file (%s)"), m.buf); - pptr = &commit_list_insert(parent, pptr)->next; + pptr = commit_list_append(parent, pptr); } fclose(fp); strbuf_release(&m); - if (!stat(git_path("MERGE_MODE"), &statbuf)) { - if (strbuf_read_file(&sb, git_path("MERGE_MODE"), 0) < 0) + if (!stat(git_path_merge_mode(), &statbuf)) { + if (strbuf_read_file(&sb, git_path_merge_mode(), 0) < 0) die_errno(_("could not read MERGE_MODE")); if (!strcmp(sb.buf, "no-ff")) allow_fast_forward = 0; @@ -1711,12 +1725,12 @@ int cmd_commit(int argc, const char **argv, const char *prefix) reflog_msg = (whence == FROM_CHERRY_PICK) ? "commit (cherry-pick)" : "commit"; - pptr = &commit_list_insert(current_head, pptr)->next; + commit_list_insert(current_head, &parents); } /* Finally, get the commit message */ strbuf_reset(&sb); - if (strbuf_read_file(&sb, git_path(commit_editmsg), 0) < 0) { + 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)); @@ -1727,7 +1741,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix) wt_status_truncate_message_at_cut_line(&sb); if (cleanup_mode != CLEANUP_NONE) - stripspace(&sb, cleanup_mode == CLEANUP_ALL); + strbuf_stripspace(&sb, cleanup_mode == CLEANUP_ALL); if (template_untouched(&sb) && !allow_empty_message) { rollback_index_files(); fprintf(stderr, _("Aborting commit; you did not edit the message.\n")); @@ -1767,7 +1781,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix) if (!transaction || ref_transaction_update(transaction, "HEAD", sha1, current_head - ? current_head->object.sha1 : null_sha1, + ? current_head->object.oid.hash : null_sha1, 0, sb.buf, &err) || ref_transaction_commit(transaction, &err)) { rollback_index_files(); @@ -1775,12 +1789,12 @@ int cmd_commit(int argc, const char **argv, const char *prefix) } ref_transaction_free(transaction); - unlink(git_path("CHERRY_PICK_HEAD")); - unlink(git_path("REVERT_HEAD")); - unlink(git_path("MERGE_HEAD")); - unlink(git_path("MERGE_MSG")); - unlink(git_path("MERGE_MODE")); - unlink(git_path("SQUASH_MSG")); + unlink(git_path_cherry_pick_head()); + unlink(git_path_revert_head()); + unlink(git_path_merge_head()); + unlink(git_path_merge_msg()); + unlink(git_path_merge_mode()); + unlink(git_path_squash_msg()); if (commit_index_files()) die (_("Repository has been updated, but unable to write\n" @@ -1794,10 +1808,10 @@ int cmd_commit(int argc, const char **argv, const char *prefix) cfg = init_copy_notes_for_rewrite("amend"); if (cfg) { /* we are amending, so current_head is not NULL */ - copy_note_for_rewrite(cfg, current_head->object.sha1, sha1); + copy_note_for_rewrite(cfg, current_head->object.oid.hash, sha1); finish_copy_notes_for_rewrite(cfg, "Notes added by 'git commit --amend'"); } - run_rewrite_hook(current_head->object.sha1, sha1); + run_rewrite_hook(current_head->object.oid.hash, sha1); } if (!quiet) print_summary(prefix, sha1, !current_head); diff --git a/builtin/config.c b/builtin/config.c index 7188405f7e..05843a0f96 100644 --- a/builtin/config.c +++ b/builtin/config.c @@ -3,6 +3,7 @@ #include "color.h" #include "parse-options.h" #include "urlmatch.h" +#include "quote.h" static const char *const builtin_config_usage[] = { N_("git config [<options>]"), @@ -13,6 +14,7 @@ static char *key; static regex_t *key_regexp; static regex_t *regexp; static int show_keys; +static int omit_values; static int use_key_regexp; static int do_all; static int do_not_match; @@ -23,9 +25,9 @@ static char term = '\n'; static int use_global_config, use_system_config, use_local_config; static struct git_config_source given_config_source; static int actions, types; -static const char *get_color_slot, *get_colorbool_slot; static int end_null; static int respect_includes = -1; +static int show_origin; #define ACTION_GET (1<<0) #define ACTION_GET_ALL (1<<1) @@ -78,7 +80,9 @@ static struct option builtin_config_options[] = { OPT_BIT(0, "path", &types, N_("value is a path (file or directory name)"), TYPE_PATH), OPT_GROUP(N_("Other")), OPT_BOOL('z', "null", &end_null, N_("terminate values with NUL byte")), + OPT_BOOL(0, "name-only", &omit_values, N_("show variable names only")), OPT_BOOL(0, "includes", &respect_includes, N_("respect include directives on lookup")), + OPT_BOOL(0, "show-origin", &show_origin, N_("show origin of config (file, standard input, blob, command line)")), OPT_END(), }; @@ -89,9 +93,29 @@ static void check_argc(int argc, int min, int max) { usage_with_options(builtin_config_usage, builtin_config_options); } +static void show_config_origin(struct strbuf *buf) +{ + const char term = end_null ? '\0' : '\t'; + + strbuf_addstr(buf, current_config_origin_type()); + strbuf_addch(buf, ':'); + if (end_null) + strbuf_addstr(buf, current_config_name()); + else + quote_c_style(current_config_name(), buf, NULL, 0); + strbuf_addch(buf, term); +} + static int show_all_config(const char *key_, const char *value_, void *cb) { - if (value_) + if (show_origin) { + struct strbuf buf = STRBUF_INIT; + show_config_origin(&buf); + /* Use fwrite as "buf" can contain \0's if "end_null" is set. */ + fwrite(buf.buf, 1, buf.len, stdout); + strbuf_release(&buf); + } + if (!omit_values && value_) printf("%s%c%s%c", key_, delim, value_, term); else printf("%s%c", key_, term); @@ -106,48 +130,42 @@ struct strbuf_list { static int format_config(struct strbuf *buf, const char *key_, const char *value_) { - int must_free_vptr = 0; - int must_print_delim = 0; - char value[256]; - const char *vptr = value; - - strbuf_init(buf, 0); - - if (show_keys) { + if (show_origin) + show_config_origin(buf); + if (show_keys) strbuf_addstr(buf, key_); - must_print_delim = 1; - } - if (types == TYPE_INT) - sprintf(value, "%"PRId64, - git_config_int64(key_, value_ ? value_ : "")); - else if (types == TYPE_BOOL) - vptr = git_config_bool(key_, value_) ? "true" : "false"; - else if (types == TYPE_BOOL_OR_INT) { - int is_bool, v; - v = git_config_bool_or_int(key_, value_, &is_bool); - if (is_bool) - vptr = v ? "true" : "false"; - else - sprintf(value, "%d", v); - } else if (types == TYPE_PATH) { - if (git_config_pathname(&vptr, key_, value_) < 0) - return -1; - must_free_vptr = 1; - } else if (value_) { - vptr = value_; - } else { - /* Just show the key name */ - vptr = ""; - must_print_delim = 0; - } + if (!omit_values) { + if (show_keys) + strbuf_addch(buf, key_delim); - if (must_print_delim) - strbuf_addch(buf, key_delim); - strbuf_addstr(buf, vptr); + if (types == TYPE_INT) + strbuf_addf(buf, "%"PRId64, + git_config_int64(key_, value_ ? value_ : "")); + else if (types == TYPE_BOOL) + strbuf_addstr(buf, git_config_bool(key_, value_) ? + "true" : "false"); + else if (types == TYPE_BOOL_OR_INT) { + int is_bool, v; + v = git_config_bool_or_int(key_, value_, &is_bool); + if (is_bool) + strbuf_addstr(buf, v ? "true" : "false"); + else + strbuf_addf(buf, "%d", v); + } else if (types == TYPE_PATH) { + const char *v; + if (git_config_pathname(&v, key_, value_) < 0) + return -1; + strbuf_addstr(buf, v); + free((char *)v); + } else if (value_) { + strbuf_addstr(buf, value_); + } else { + /* Just show the key name; back out delimiter */ + if (show_keys) + strbuf_setlen(buf, buf->len - 1); + } + } strbuf_addch(buf, term); - - if (must_free_vptr) - free((char *)vptr); return 0; } @@ -164,6 +182,7 @@ static int collect_config(const char *key_, const char *value_, void *cb) return 0; ALLOC_GROW(values->items, values->nr + 1, values->alloc); + strbuf_init(&values->items[values->nr], 0); return format_config(&values->items[values->nr++], key_, value_); } @@ -251,8 +270,6 @@ free_strings: static char *normalize_value(const char *key, const char *value) { - char *normalized; - if (!value) return NULL; @@ -263,27 +280,21 @@ static char *normalize_value(const char *key, const char *value) * "~/foobar/" in the config file, and to expand the ~ * when retrieving the value. */ - normalized = xstrdup(value); - else { - normalized = xmalloc(64); - if (types == TYPE_INT) { - int64_t v = git_config_int64(key, value); - sprintf(normalized, "%"PRId64, v); - } - else if (types == TYPE_BOOL) - sprintf(normalized, "%s", - git_config_bool(key, value) ? "true" : "false"); - else if (types == TYPE_BOOL_OR_INT) { - int is_bool, v; - v = git_config_bool_or_int(key, value, &is_bool); - if (!is_bool) - sprintf(normalized, "%d", v); - else - sprintf(normalized, "%s", v ? "true" : "false"); - } + return xstrdup(value); + if (types == TYPE_INT) + return xstrfmt("%"PRId64, git_config_int64(key, value)); + if (types == TYPE_BOOL) + return xstrdup(git_config_bool(key, value) ? "true" : "false"); + if (types == TYPE_BOOL_OR_INT) { + int is_bool, v; + v = git_config_bool_or_int(key, value, &is_bool); + if (!is_bool) + return xstrfmt("%d", v); + else + return xstrdup(v ? "true" : "false"); } - return normalized; + die("BUG: cannot normalize type %d", types); } static int get_color_found; @@ -365,6 +376,9 @@ static int get_colorbool(const char *var, int print) static void check_write(void) { + if (!given_config_source.file && !startup_info->have_repository) + die("not in a git directory"); + if (given_config_source.use_stdin) die("writing to stdin is not supported"); @@ -402,6 +416,7 @@ static int urlmatch_collect_fn(const char *var, const char *value, void *cb) static int get_urlmatch(const char *var, const char *url) { + int ret; char *section_tail; struct string_list_item *item; struct urlmatch_config config = { STRING_LIST_INIT_DUP }; @@ -428,16 +443,15 @@ static int get_urlmatch(const char *var, const char *url) git_config_with_options(urlmatch_config_entry, &config, &given_config_source, respect_includes); + ret = !values.nr; + for_each_string_list_item(item, &values) { struct urlmatch_current_candidate_value *matched = item->util; - struct strbuf key = STRBUF_INIT; struct strbuf buf = STRBUF_INIT; - strbuf_addstr(&key, item->string); - format_config(&buf, key.buf, + format_config(&buf, item->string, matched->value_is_null ? NULL : matched->value.buf); fwrite(buf.buf, 1, buf.len, stdout); - strbuf_release(&key); strbuf_release(&buf); strbuf_release(&matched->value); @@ -447,7 +461,7 @@ static int get_urlmatch(const char *var, const char *url) free(config.url.url); free((void *)config.section); - return 0; + return ret; } static char *default_user_config(void) @@ -549,6 +563,18 @@ int cmd_config(int argc, const char **argv, const char *prefix) default: usage_with_options(builtin_config_usage, builtin_config_options); } + if (omit_values && + !(actions == ACTION_LIST || actions == ACTION_GET_REGEXP)) { + error("--name-only is only applicable to --list or --get-regexp"); + usage_with_options(builtin_config_usage, builtin_config_options); + } + + if (show_origin && !(actions & + (ACTION_GET|ACTION_GET_ALL|ACTION_GET_REGEXP|ACTION_LIST))) { + error("--show-origin is only applicable to --get, --get-all, " + "--get-regexp, and --list."); + usage_with_options(builtin_config_usage, builtin_config_options); + } if (actions == ACTION_LIST) { check_argc(argc, 0, 0); @@ -577,7 +603,7 @@ int cmd_config(int argc, const char **argv, const char *prefix) given_config_source.file : git_path("config")); if (use_global_config) { int fd = open(config_file, O_CREAT | O_EXCL | O_WRONLY, 0666); - if (fd) { + if (fd >= 0) { char *content = default_user_config(); write_str_in_full(fd, content); free(content); @@ -594,33 +620,33 @@ int cmd_config(int argc, const char **argv, const char *prefix) check_write(); check_argc(argc, 2, 2); value = normalize_value(argv[0], argv[1]); - ret = git_config_set_in_file(given_config_source.file, argv[0], value); + ret = git_config_set_in_file_gently(given_config_source.file, argv[0], value); if (ret == CONFIG_NOTHING_SET) - error("cannot overwrite multiple values with a single value\n" - " Use a regexp, --add or --replace-all to change %s.", argv[0]); + error(_("cannot overwrite multiple values with a single value\n" + " Use a regexp, --add or --replace-all to change %s."), argv[0]); return ret; } else if (actions == ACTION_SET_ALL) { check_write(); check_argc(argc, 2, 3); value = normalize_value(argv[0], argv[1]); - return git_config_set_multivar_in_file(given_config_source.file, - argv[0], value, argv[2], 0); + return git_config_set_multivar_in_file_gently(given_config_source.file, + argv[0], value, argv[2], 0); } else if (actions == ACTION_ADD) { check_write(); check_argc(argc, 2, 2); value = normalize_value(argv[0], argv[1]); - return git_config_set_multivar_in_file(given_config_source.file, - argv[0], value, - CONFIG_REGEX_NONE, 0); + return git_config_set_multivar_in_file_gently(given_config_source.file, + argv[0], value, + CONFIG_REGEX_NONE, 0); } else if (actions == ACTION_REPLACE_ALL) { check_write(); check_argc(argc, 2, 3); value = normalize_value(argv[0], argv[1]); - return git_config_set_multivar_in_file(given_config_source.file, - argv[0], value, argv[2], 1); + return git_config_set_multivar_in_file_gently(given_config_source.file, + argv[0], value, argv[2], 1); } else if (actions == ACTION_GET) { check_argc(argc, 1, 2); @@ -646,17 +672,17 @@ int cmd_config(int argc, const char **argv, const char *prefix) check_write(); check_argc(argc, 1, 2); if (argc == 2) - return git_config_set_multivar_in_file(given_config_source.file, - argv[0], NULL, argv[1], 0); + return git_config_set_multivar_in_file_gently(given_config_source.file, + argv[0], NULL, argv[1], 0); else - return git_config_set_in_file(given_config_source.file, - argv[0], NULL); + return git_config_set_in_file_gently(given_config_source.file, + argv[0], NULL); } else if (actions == ACTION_UNSET_ALL) { check_write(); check_argc(argc, 1, 2); - return git_config_set_multivar_in_file(given_config_source.file, - argv[0], NULL, argv[1], 1); + return git_config_set_multivar_in_file_gently(given_config_source.file, + argv[0], NULL, argv[1], 1); } else if (actions == ACTION_RENAME_SECTION) { int ret; diff --git a/builtin/count-objects.c b/builtin/count-objects.c index ad0c79954a..a04b4f2ef3 100644 --- a/builtin/count-objects.c +++ b/builtin/count-objects.c @@ -8,6 +8,7 @@ #include "dir.h" #include "builtin.h" #include "parse-options.h" +#include "quote.h" static unsigned long garbage; static off_t size_garbage; @@ -15,9 +16,31 @@ static int verbose; static unsigned long loose, packed, packed_loose; static off_t loose_size; -static void real_report_garbage(const char *desc, const char *path) +static const char *bits_to_msg(unsigned seen_bits) +{ + switch (seen_bits) { + case 0: + return "no corresponding .idx or .pack"; + case PACKDIR_FILE_GARBAGE: + return "garbage found"; + case PACKDIR_FILE_PACK: + return "no corresponding .idx"; + case PACKDIR_FILE_IDX: + return "no corresponding .pack"; + case PACKDIR_FILE_PACK|PACKDIR_FILE_IDX: + default: + return NULL; + } +} + +static void real_report_garbage(unsigned seen_bits, const char *path) { struct stat st; + const char *desc = bits_to_msg(seen_bits); + + if (!desc) + return; + if (!stat(path, &st)) size_garbage += st.st_size; warning("%s: %s", desc, path); @@ -27,7 +50,7 @@ static void real_report_garbage(const char *desc, const char *path) static void loose_garbage(const char *path) { if (verbose) - report_garbage("garbage found", path); + report_garbage(PACKDIR_FILE_GARBAGE, path); } static int count_loose(const unsigned char *sha1, const char *path, void *data) @@ -51,6 +74,14 @@ static int count_cruft(const char *basename, const char *path, void *data) return 0; } +static int print_alternate(struct alternate_object_database *alt, void *data) +{ + printf("alternate: "); + quote_c_style(alt->path, NULL, stdout, 0); + putchar('\n'); + return 0; +} + static char const * const count_objects_usage[] = { N_("git count-objects [-v] [-H | --human-readable]"), NULL @@ -66,6 +97,8 @@ int cmd_count_objects(int argc, const char **argv, const char *prefix) OPT_END(), }; + git_config(git_default_config, NULL); + argc = parse_options(argc, argv, prefix, opts, count_objects_usage, 0); /* we do not take arguments other than flags for now */ if (argc) @@ -118,6 +151,7 @@ int cmd_count_objects(int argc, const char **argv, const char *prefix) printf("prune-packable: %lu\n", packed_loose); printf("garbage: %lu\n", garbage); printf("size-garbage: %s\n", garbage_buf.buf); + foreach_alt_odb(print_alternate, NULL); strbuf_release(&loose_buf); strbuf_release(&pack_buf); strbuf_release(&garbage_buf); diff --git a/builtin/describe.c b/builtin/describe.c index a36c829e57..01490a157e 100644 --- a/builtin/describe.c +++ b/builtin/describe.c @@ -252,14 +252,14 @@ static void describe(const char *arg, int last_one) if (!cmit) die(_("%s is not a valid '%s' object"), arg, commit_type); - n = find_commit_name(cmit->object.sha1); + n = find_commit_name(cmit->object.oid.hash); if (n && (tags || all || n->prio == 2)) { /* * Exact match to an existing ref. */ display_name(n); if (longformat) - show_suffix(0, n->tag ? n->tag->tagged->sha1 : sha1); + show_suffix(0, n->tag ? n->tag->tagged->oid.hash : sha1); if (dirty) printf("%s", dirty); printf("\n"); @@ -267,7 +267,7 @@ 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'"), oid_to_hex(&cmit->object.oid)); if (debug) fprintf(stderr, _("searching to describe %s\n"), arg); @@ -317,7 +317,7 @@ static void describe(const char *arg, int last_one) if (annotated_cnt && !list) { if (debug) fprintf(stderr, _("finished search at %s\n"), - sha1_to_hex(c->object.sha1)); + oid_to_hex(&c->object.oid)); break; } while (parents) { @@ -334,9 +334,9 @@ static void describe(const char *arg, int last_one) } if (!match_cnt) { - const unsigned char *sha1 = cmit->object.sha1; + struct object_id *oid = &cmit->object.oid; if (always) { - printf("%s", find_unique_abbrev(sha1, abbrev)); + printf("%s", find_unique_abbrev(oid->hash, abbrev)); if (dirty) printf("%s", dirty); printf("\n"); @@ -345,14 +345,14 @@ static void describe(const char *arg, int last_one) if (unannotated_cnt) die(_("No annotated tags can describe '%s'.\n" "However, there were unannotated tags: try --tags."), - sha1_to_hex(sha1)); + oid_to_hex(oid)); else die(_("No tags can describe '%s'.\n" "Try --always, or create some tags."), - sha1_to_hex(sha1)); + oid_to_hex(oid)); } - qsort(all_matches, match_cnt, sizeof(all_matches[0]), compare_pt); + QSORT(all_matches, match_cnt, compare_pt); if (gave_up_on) { commit_list_insert_by_date(gave_up_on, &list); @@ -374,13 +374,13 @@ static void describe(const char *arg, int last_one) _("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)); + oid_to_hex(&gave_up_on->object.oid)); } } display_name(all_matches[0].name); if (abbrev) - show_suffix(all_matches[0].depth, cmit->object.sha1); + show_suffix(all_matches[0].depth, cmit->object.oid.hash); if (dirty) printf("%s", dirty); printf("\n"); @@ -443,10 +443,10 @@ int cmd_describe(int argc, const char **argv, const char *prefix) if (pattern) argv_array_pushf(&args, "--refs=refs/tags/%s", pattern); } - while (*argv) { - argv_array_push(&args, *argv); - argv++; - } + if (argc) + argv_array_pushv(&args, argv); + else + argv_array_push(&args, "HEAD"); return cmd_name_rev(args.argc, args.argv, prefix); } diff --git a/builtin/diff-files.c b/builtin/diff-files.c index 8ed2eb8813..15c61fd8d1 100644 --- a/builtin/diff-files.c +++ b/builtin/diff-files.c @@ -24,6 +24,7 @@ int cmd_diff_files(int argc, const char **argv, const char *prefix) gitmodules_config(); git_config(git_diff_basic_config, NULL); /* no "diff" UI options */ rev.abbrev = 0; + precompose_argv(argc, argv); argc = setup_revisions(argc, argv, &rev, NULL); while (1 < argc && argv[1][0] == '-') { diff --git a/builtin/diff-index.c b/builtin/diff-index.c index d979824f93..1af373d002 100644 --- a/builtin/diff-index.c +++ b/builtin/diff-index.c @@ -21,6 +21,7 @@ int cmd_diff_index(int argc, const char **argv, const char *prefix) gitmodules_config(); git_config(git_diff_basic_config, NULL); /* no "diff" UI options */ rev.abbrev = 0; + precompose_argv(argc, argv); argc = setup_revisions(argc, argv, &rev, NULL); for (i = 1; i < argc; i++) { diff --git a/builtin/diff-tree.c b/builtin/diff-tree.c index 12b683d021..806dd7a885 100644 --- a/builtin/diff-tree.c +++ b/builtin/diff-tree.c @@ -49,9 +49,9 @@ static int stdin_diff_trees(struct tree *tree1, char *line, int len) tree2 = lookup_tree(sha1); if (!tree2 || parse_tree(tree2)) return -1; - printf("%s %s\n", sha1_to_hex(tree1->object.sha1), - sha1_to_hex(tree2->object.sha1)); - diff_tree_sha1(tree1->object.sha1, tree2->object.sha1, + printf("%s %s\n", oid_to_hex(&tree1->object.oid), + oid_to_hex(&tree2->object.oid)); + diff_tree_sha1(tree1->object.oid.hash, tree2->object.oid.hash, "", &log_tree_opt.diffopt); log_tree_diff_flush(&log_tree_opt); return 0; @@ -114,6 +114,8 @@ int cmd_diff_tree(int argc, const char **argv, const char *prefix) opt->disable_stdin = 1; memset(&s_r_opt, 0, sizeof(s_r_opt)); s_r_opt.tweak = diff_tree_tweak_rev; + + precompose_argv(argc, argv); argc = setup_revisions(argc, argv, opt, &s_r_opt); while (--argc > 0) { @@ -139,7 +141,7 @@ int cmd_diff_tree(int argc, const char **argv, const char *prefix) break; case 1: tree1 = opt->pending.objects[0].item; - diff_tree_commit_sha1(tree1->sha1); + diff_tree_commit_sha1(tree1->oid.hash); break; case 2: tree1 = opt->pending.objects[0].item; @@ -149,8 +151,8 @@ int cmd_diff_tree(int argc, const char **argv, const char *prefix) tree2 = tree1; tree1 = tmp; } - diff_tree_sha1(tree1->sha1, - tree2->sha1, + diff_tree_sha1(tree1->oid.hash, + tree2->oid.hash, "", &opt->diffopt); log_tree_diff_flush(opt); break; diff --git a/builtin/diff.c b/builtin/diff.c index 4326fa56bf..7f91f6d226 100644 --- a/builtin/diff.c +++ b/builtin/diff.c @@ -175,8 +175,8 @@ static int builtin_diff_tree(struct rev_info *revs, */ if (ent1->item->flags & UNINTERESTING) swap = 1; - sha1[swap] = ent0->item->sha1; - sha1[1 - swap] = ent1->item->sha1; + sha1[swap] = ent0->item->oid.hash; + sha1[1 - swap] = ent1->item->oid.hash; diff_tree_sha1(sha1[0], sha1[1], "", &revs->diffopt); log_tree_diff_flush(revs); return 0; @@ -196,8 +196,8 @@ static int builtin_diff_combined(struct rev_info *revs, if (!revs->dense_combined_merges && !revs->combine_merges) revs->dense_combined_merges = revs->combine_merges = 1; for (i = 1; i < ents; i++) - sha1_array_append(&parents, ent[i].item->sha1); - diff_tree_combined(ent[0].item->sha1, &parents, + sha1_array_append(&parents, ent[i].item->oid.hash); + diff_tree_combined(ent[0].item->oid.hash, &parents, revs->dense_combined_merges, revs); sha1_array_clear(&parents); return 0; @@ -301,24 +301,27 @@ int cmd_diff(int argc, const char **argv, const char *prefix) break; } - if (!no_index) - prefix = setup_git_directory_gently(&nongit); + prefix = setup_git_directory_gently(&nongit); - /* - * Treat git diff with at least one path outside of the - * repo the same as if the command would have been executed - * outside of a git repository. In this case it behaves - * the same way as "git diff --no-index <a> <b>", which acts - * as a colourful "diff" replacement. - */ - if (nongit || ((argc == i + 2) && - (!path_inside_repo(prefix, argv[i]) || - !path_inside_repo(prefix, argv[i + 1])))) - no_index = DIFF_NO_INDEX_IMPLICIT; + if (!no_index) { + /* + * Treat git diff with at least one path outside of the + * repo the same as if the command would have been executed + * outside of a git repository. In this case it behaves + * the same way as "git diff --no-index <a> <b>", which acts + * as a colourful "diff" replacement. + */ + if (nongit || ((argc == i + 2) && + (!path_inside_repo(prefix, argv[i]) || + !path_inside_repo(prefix, argv[i + 1])))) + no_index = DIFF_NO_INDEX_IMPLICIT; + } if (!no_index) gitmodules_config(); + init_diff_ui_defaults(); git_config(git_diff_ui_config, NULL); + precompose_argv(argc, argv); init_revisions(&rev, prefix); @@ -341,7 +344,7 @@ int cmd_diff(int argc, const char **argv, const char *prefix) } if (no_index) /* If this is a no-index diff, just run it and exit there. */ - diff_no_index(&rev, argc, argv, prefix); + diff_no_index(&rev, argc, argv); /* Otherwise, we are doing the usual "git" diff */ rev.diffopt.skip_stat_unmatch = !!diff_auto_refresh_index; @@ -395,7 +398,7 @@ int cmd_diff(int argc, const char **argv, const char *prefix) const char *name = entry->name; int flags = (obj->flags & UNINTERESTING); if (!obj->parsed) - obj = parse_object(obj->sha1); + obj = parse_object(obj->oid.hash); obj = deref_tag(obj, NULL, 0); if (!obj) die(_("invalid object '%s' given."), name); @@ -408,7 +411,7 @@ int cmd_diff(int argc, const char **argv, const char *prefix) } else if (obj->type == OBJ_BLOB) { if (2 <= blobs) die(_("more than two blobs given: '%s'"), name); - hashcpy(blob[blobs].sha1, obj->sha1); + hashcpy(blob[blobs].sha1, obj->oid.hash); blob[blobs].name = name; blob[blobs].mode = entry->mode; blobs++; diff --git a/builtin/fast-export.c b/builtin/fast-export.c index b8182c241d..1e815b5577 100644 --- a/builtin/fast-export.c +++ b/builtin/fast-export.c @@ -5,6 +5,7 @@ */ #include "builtin.h" #include "cache.h" +#include "refs.h" #include "commit.h" #include "object.h" #include "tag.h" @@ -346,7 +347,7 @@ static void show_filemodify(struct diff_queue_struct *q, * 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); + QSORT(q->queue, q->nr, depth_first); for (i = 0; i < q->nr; i++) { struct diff_filespec *ospec = q->queue[i]->one; @@ -367,7 +368,7 @@ static void show_filemodify(struct diff_queue_struct *q, print_path(spec->path); putchar('\n'); - if (!hashcmp(ospec->sha1, spec->sha1) && + if (!oidcmp(&ospec->oid, &spec->oid) && ospec->mode == spec->mode) break; /* fallthrough */ @@ -382,10 +383,10 @@ static void show_filemodify(struct diff_queue_struct *q, if (no_data || S_ISGITLINK(spec->mode)) printf("M %06o %s ", spec->mode, sha1_to_hex(anonymize ? - anonymize_sha1(spec->sha1) : - spec->sha1)); + anonymize_sha1(spec->oid.hash) : + spec->oid.hash)); else { - struct object *object = lookup_object(spec->sha1); + struct object *object = lookup_object(spec->oid.hash); printf("M %06o :%d ", spec->mode, get_object_mark(object)); } @@ -543,13 +544,13 @@ static void handle_commit(struct commit *commit, struct rev_info *rev) author = strstr(commit_buffer, "\nauthor "); if (!author) die ("Could not find author in commit %s", - sha1_to_hex(commit->object.sha1)); + oid_to_hex(&commit->object.oid)); author++; author_end = strchrnul(author, '\n'); committer = strstr(author_end, "\ncommitter "); if (!committer) die ("Could not find committer in commit %s", - sha1_to_hex(commit->object.sha1)); + oid_to_hex(&commit->object.oid)); committer++; committer_end = strchrnul(committer, '\n'); message = strstr(committer_end, "\n\n"); @@ -561,17 +562,17 @@ static void handle_commit(struct commit *commit, struct rev_info *rev) get_object_mark(&commit->parents->item->object) != 0 && !full_tree) { parse_commit_or_die(commit->parents->item); - diff_tree_sha1(commit->parents->item->tree->object.sha1, - commit->tree->object.sha1, "", &rev->diffopt); + diff_tree_sha1(commit->parents->item->tree->object.oid.hash, + commit->tree->object.oid.hash, "", &rev->diffopt); } else - diff_root_tree_sha1(commit->tree->object.sha1, + diff_root_tree_sha1(commit->tree->object.oid.hash, "", &rev->diffopt); /* Export the referenced blobs, and remember the marks. */ for (i = 0; i < diff_queued_diff.nr; i++) if (!S_ISGITLINK(diff_queued_diff.queue[i]->two->mode)) - export_blob(diff_queued_diff.queue[i]->two->sha1); + export_blob(diff_queued_diff.queue[i]->two->oid.hash); refname = commit->util; if (anonymize) { @@ -660,13 +661,13 @@ static void handle_tag(const char *name, struct tag *tag) } if (tagged->type == OBJ_TREE) { warning("Omitting tag %s,\nsince tags of trees (or tags of tags of trees, etc.) are not supported.", - sha1_to_hex(tag->object.sha1)); + oid_to_hex(&tag->object.oid)); return; } - buf = read_sha1_file(tag->object.sha1, &type, &size); + buf = read_sha1_file(tag->object.oid.hash, &type, &size); if (!buf) - die ("Could not read tag %s", sha1_to_hex(tag->object.sha1)); + die ("Could not read tag %s", oid_to_hex(&tag->object.oid)); message = memmem(buf, size, "\n\n", 2); if (message) { message += 2; @@ -705,16 +706,16 @@ static void handle_tag(const char *name, struct tag *tag) case ABORT: die ("Encountered signed tag %s; use " "--signed-tags=<mode> to handle it.", - sha1_to_hex(tag->object.sha1)); + oid_to_hex(&tag->object.oid)); case WARN: warning ("Exporting signed tag %s", - sha1_to_hex(tag->object.sha1)); + oid_to_hex(&tag->object.oid)); /* fallthru */ case VERBATIM: break; case WARN_STRIP: warning ("Stripping signature from tag %s", - sha1_to_hex(tag->object.sha1)); + oid_to_hex(&tag->object.oid)); /* fallthru */ case STRIP: message_size = signature + 1 - message; @@ -730,14 +731,14 @@ static void handle_tag(const char *name, struct tag *tag) case ABORT: die ("Tag %s tags unexported object; use " "--tag-of-filtered-object=<mode> to handle it.", - sha1_to_hex(tag->object.sha1)); + oid_to_hex(&tag->object.oid)); case DROP: /* Ignore this tag altogether */ return; case REWRITE: if (tagged->type != OBJ_COMMIT) { die ("Tag %s tags unexported %s!", - sha1_to_hex(tag->object.sha1), + oid_to_hex(&tag->object.oid), typename(tagged->type)); } p = (struct commit *)tagged; @@ -750,7 +751,7 @@ static void handle_tag(const char *name, struct tag *tag) break; if (!p->parents) die ("Can't find replacement commit for tag %s\n", - sha1_to_hex(tag->object.sha1)); + oid_to_hex(&tag->object.oid)); p = p->parents->item; } tagged_mark = get_object_mark(&p->object); @@ -776,7 +777,7 @@ static struct commit *get_commit(struct rev_cmdline_entry *e, char *full_name) /* handle nested tags */ while (tag && tag->object.type == OBJ_TAG) { - parse_object(tag->object.sha1); + parse_object(tag->object.oid.hash); string_list_append(&extra_refs, full_name)->util = tag; tag = (struct tag *)tag->tagged; } @@ -827,7 +828,7 @@ static void get_tags_and_duplicates(struct rev_cmdline_info *info) case OBJ_COMMIT: break; case OBJ_BLOB: - export_blob(commit->object.sha1); + export_blob(commit->object.oid.hash); continue; default: /* OBJ_TAG (nested tags) is already handled */ warning("Tag points to object of unexpected type %s, skipping.", @@ -879,7 +880,7 @@ static void export_marks(char *file) FILE *f; int e = 0; - f = fopen(file, "w"); + f = fopen_for_writing(file); if (!f) die_errno("Unable to open marks file %s for writing.", file); @@ -887,7 +888,7 @@ static void export_marks(char *file) if (deco->base && deco->base->type == 1) { mark = ptr_to_mark(deco->decoration); if (fprintf(f, ":%"PRIu32" %s\n", mark, - sha1_to_hex(deco->base->sha1)) < 0) { + oid_to_hex(&deco->base->oid)) < 0) { e = 1; break; } @@ -1020,7 +1021,7 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix) const char **refspecs_str; int i; - refspecs_str = xmalloc(sizeof(*refspecs_str) * refspecs_list.nr); + ALLOC_ARRAY(refspecs_str, refspecs_list.nr); for (i = 0; i < refspecs_list.nr; i++) refspecs_str[i] = refspecs_list.items[i].string; diff --git a/builtin/fetch-pack.c b/builtin/fetch-pack.c index 4a6b340ab6..cfe9e447c2 100644 --- a/builtin/fetch-pack.c +++ b/builtin/fetch-pack.c @@ -10,31 +10,34 @@ static const char fetch_pack_usage[] = "[--include-tag] [--upload-pack=<git-upload-pack>] [--depth=<n>] " "[--no-progress] [--diag-url] [-v] [<host>:]<directory> [<refs>...]"; -static void add_sought_entry_mem(struct ref ***sought, int *nr, int *alloc, - const char *name, int namelen) +static void add_sought_entry(struct ref ***sought, int *nr, int *alloc, + const char *name) { - struct ref *ref = xcalloc(1, sizeof(*ref) + namelen + 1); - unsigned char sha1[20]; - - if (namelen > 41 && name[40] == ' ' && !get_sha1_hex(name, sha1)) { - hashcpy(ref->old_sha1, sha1); - name += 41; - namelen -= 41; + struct ref *ref; + struct object_id oid; + + if (!get_oid_hex(name, &oid)) { + if (name[GIT_SHA1_HEXSZ] == ' ') { + /* <sha1> <ref>, find refname */ + name += GIT_SHA1_HEXSZ + 1; + } else if (name[GIT_SHA1_HEXSZ] == '\0') { + ; /* <sha1>, leave sha1 as name */ + } else { + /* <ref>, clear cruft from oid */ + oidclr(&oid); + } + } else { + /* <ref>, clear cruft from get_oid_hex */ + oidclr(&oid); } - memcpy(ref->name, name, namelen); - ref->name[namelen] = '\0'; + ref = alloc_ref(name); + oidcpy(&ref->old_oid, &oid); (*nr)++; ALLOC_GROW(*sought, *nr, *alloc); (*sought)[*nr - 1] = ref; } -static void add_sought_entry(struct ref ***sought, int *nr, int *alloc, - const char *string) -{ - add_sought_entry_mem(sought, nr, alloc, string, strlen(string)); -} - int cmd_fetch_pack(int argc, const char **argv, const char *prefix) { int i, ret; @@ -48,6 +51,7 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix) struct child_process *conn; struct fetch_pack_args args; struct sha1_array shallow = SHA1_ARRAY_INIT; + struct string_list deepen_not = STRING_LIST_INIT_DUP; packet_trace_identity("fetch-pack"); @@ -57,12 +61,12 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix) for (i = 1; i < argc && *argv[i] == '-'; i++) { const char *arg = argv[i]; - if (starts_with(arg, "--upload-pack=")) { - args.uploadpack = arg + 14; + if (skip_prefix(arg, "--upload-pack=", &arg)) { + args.uploadpack = arg; continue; } - if (starts_with(arg, "--exec=")) { - args.uploadpack = arg + 7; + if (skip_prefix(arg, "--exec=", &arg)) { + args.uploadpack = arg; continue; } if (!strcmp("--quiet", arg) || !strcmp("-q", arg)) { @@ -98,8 +102,20 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix) args.verbose = 1; continue; } - if (starts_with(arg, "--depth=")) { - args.depth = strtol(arg + 8, NULL, 0); + if (skip_prefix(arg, "--depth=", &arg)) { + args.depth = strtol(arg, NULL, 0); + continue; + } + if (skip_prefix(arg, "--shallow-since=", &arg)) { + args.deepen_since = xstrdup(arg); + continue; + } + if (skip_prefix(arg, "--shallow-exclude=", &arg)) { + string_list_append(&deepen_not, arg); + continue; + } + if (!strcmp(arg, "--deepen-relative")) { + args.deepen_relative = 1; continue; } if (!strcmp("--no-progress", arg)) { @@ -129,6 +145,8 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix) } usage(fetch_pack_usage); } + if (deepen_not.nr) + args.deepen_not = &deepen_not; if (i < argc) dest = argv[i++]; @@ -156,7 +174,7 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix) else { /* read from stdin one ref per line, until EOF */ struct strbuf line = STRBUF_INIT; - while (strbuf_getline(&line, stdin, '\n') != EOF) + while (strbuf_getline_lf(&line, stdin) != EOF) add_sought_entry(&sought, &nr_sought, &alloc_sought, line.buf); strbuf_release(&line); } @@ -210,7 +228,7 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix) while (ref) { printf("%s %s\n", - sha1_to_hex(ref->old_sha1), ref->name); + oid_to_hex(&ref->old_oid), ref->name); ref = ref->next; } diff --git a/builtin/fetch.c b/builtin/fetch.c index 8d5b2dba2b..1d77e58a00 100644 --- a/builtin/fetch.c +++ b/builtin/fetch.c @@ -11,9 +11,11 @@ #include "run-command.h" #include "parse-options.h" #include "sigchain.h" +#include "submodule-config.h" #include "submodule.h" #include "connected.h" #include "argv-array.h" +#include "utf8.h" static const char * const builtin_fetch_usage[] = { N_("git fetch [<options>] [<repository> [<refspec>...]]"), @@ -33,11 +35,15 @@ static int fetch_prune_config = -1; /* unspecified */ static int prune = -1; /* unspecified */ #define PRUNE_BY_DEFAULT 0 /* do we prune by default? */ -static int all, append, dry_run, force, keep, multiple, update_head_ok, verbosity; +static int all, append, dry_run, force, keep, multiple, update_head_ok, verbosity, deepen_relative; static int progress = -1, recurse_submodules = RECURSE_SUBMODULES_DEFAULT; -static int tags = TAGS_DEFAULT, unshallow, update_shallow; +static int tags = TAGS_DEFAULT, unshallow, update_shallow, deepen; +static int max_children = -1; +static enum transport_family family; static const char *depth; +static const char *deepen_since; static const char *upload_pack; +static struct string_list deepen_not = STRING_LIST_INIT_NODUP; static struct strbuf default_rla = STRBUF_INIT; static struct transport *gtransport; static struct transport *gsecondary; @@ -98,6 +104,8 @@ static struct option builtin_fetch_options[] = { N_("fetch all tags and associated objects"), TAGS_SET), OPT_SET_INT('n', NULL, &tags, N_("do not fetch all tags (--no-tags)"), TAGS_UNSET), + OPT_INTEGER('j', "jobs", &max_children, + N_("number of submodules fetched in parallel")), OPT_BOOL('p', "prune", &prune, N_("prune remote-tracking branches no longer on remote")), { OPTION_CALLBACK, 0, "recurse-submodules", NULL, N_("on-demand"), @@ -111,6 +119,12 @@ static struct option builtin_fetch_options[] = { OPT_BOOL(0, "progress", &progress, N_("force progress reporting")), OPT_STRING(0, "depth", &depth, N_("depth"), N_("deepen history of shallow clone")), + OPT_STRING(0, "shallow-since", &deepen_since, N_("time"), + N_("deepen history of shallow repository based on time")), + OPT_STRING_LIST(0, "shallow-exclude", &deepen_not, N_("revision"), + N_("deepen history of shallow clone by excluding rev")), + OPT_INTEGER(0, "deepen", &deepen_relative, + N_("deepen history of shallow clone")), { OPTION_SET_INT, 0, "unshallow", &unshallow, NULL, N_("convert to a complete repository"), PARSE_OPT_NONEG | PARSE_OPT_NOARG, NULL, 1 }, @@ -123,6 +137,10 @@ static struct option builtin_fetch_options[] = { N_("accept refs that update .git/shallow")), { OPTION_CALLBACK, 0, "refmap", NULL, N_("refmap"), N_("specify fetch refmap"), PARSE_OPT_NONEG, parse_refmap_arg }, + OPT_SET_INT('4', "ipv4", &family, N_("use IPv4 addresses only"), + TRANSPORT_FAMILY_IPV4), + OPT_SET_INT('6', "ipv6", &family, N_("use IPv6 addresses only"), + TRANSPORT_FAMILY_IPV6), OPT_END() }; @@ -195,7 +213,7 @@ static int will_fetch(struct ref **head, const unsigned char *sha1) { struct ref *rm = *head; while (rm) { - if (!hashcmp(rm->old_sha1, sha1)) + if (!hashcmp(rm->old_oid.hash, sha1)) return 1; rm = rm->next; } @@ -223,9 +241,10 @@ static void find_non_local_tags(struct transport *transport, * as one to ignore by setting util to NULL. */ if (ends_with(ref->name, "^{}")) { - if (item && !has_sha1_file(ref->old_sha1) && - !will_fetch(head, ref->old_sha1) && - !has_sha1_file(item->util) && + if (item && + !has_object_file_with_flags(&ref->old_oid, HAS_SHA1_QUICK) && + !will_fetch(head, ref->old_oid.hash) && + !has_sha1_file_with_flags(item->util, HAS_SHA1_QUICK) && !will_fetch(head, item->util)) item->util = NULL; item = NULL; @@ -238,7 +257,8 @@ static void find_non_local_tags(struct transport *transport, * to check if it is a lightweight tag that we want to * fetch. */ - if (item && !has_sha1_file(item->util) && + if (item && + !has_sha1_file_with_flags(item->util, HAS_SHA1_QUICK) && !will_fetch(head, item->util)) item->util = NULL; @@ -250,7 +270,7 @@ static void find_non_local_tags(struct transport *transport, continue; item = string_list_insert(&remote_refs, ref->name); - item->util = (void *)ref->old_sha1; + item->util = (void *)&ref->old_oid; } string_list_clear(&existing_refs, 1); @@ -258,7 +278,8 @@ static void find_non_local_tags(struct transport *transport, * We may have a final lightweight tag that needs to be * checked to see if it needs fetching. */ - if (item && !has_sha1_file(item->util) && + if (item && + !has_sha1_file_with_flags(item->util, HAS_SHA1_QUICK) && !will_fetch(head, item->util)) item->util = NULL; @@ -272,7 +293,7 @@ static void find_non_local_tags(struct transport *transport, { struct ref *rm = alloc_ref(item->string); rm->peer_ref = alloc_ref(item->string); - hashcpy(rm->old_sha1, item->util); + oidcpy(&rm->old_oid, item->util); **tail = rm; *tail = &rm->next; } @@ -338,9 +359,6 @@ static struct ref *get_ref_map(struct transport *transport, for (i = 0; i < fetch_refspec_nr; i++) get_fetch_map(ref_map, &fetch_refspec[i], &oref_tail, 1); - - if (tags == TAGS_SET) - get_fetch_map(remote_refs, tag_refspec, &tail, 0); } else if (refmap_array) { die("--refmap option is only meaningful with command-line refspec(s)."); } else { @@ -418,8 +436,8 @@ static int s_update_ref(const char *action, transaction = ref_transaction_begin(&err); if (!transaction || ref_transaction_update(transaction, ref->name, - ref->new_sha1, - check_old ? ref->old_sha1 : NULL, + ref->new_oid.hash, + check_old ? ref->old_oid.hash : NULL, 0, msg, &err)) goto fail; @@ -440,59 +458,184 @@ fail: : STORE_REF_ERROR_OTHER; } -#define REFCOL_WIDTH 10 +static int refcol_width = 10; +static int compact_format; + +static void adjust_refcol_width(const struct ref *ref) +{ + int max, rlen, llen, len; + + /* uptodate lines are only shown on high verbosity level */ + if (!verbosity && !oidcmp(&ref->peer_ref->old_oid, &ref->old_oid)) + return; + + max = term_columns(); + rlen = utf8_strwidth(prettify_refname(ref->name)); + + llen = utf8_strwidth(prettify_refname(ref->peer_ref->name)); + + /* + * rough estimation to see if the output line is too long and + * should not be counted (we can't do precise calculation + * anyway because we don't know if the error explanation part + * will be printed in update_local_ref) + */ + if (compact_format) { + llen = 0; + max = max * 2 / 3; + } + len = 21 /* flag and summary */ + rlen + 4 /* -> */ + llen; + if (len >= max) + return; + + /* + * Not precise calculation for compact mode because '*' can + * appear on the left hand side of '->' and shrink the column + * back. + */ + if (refcol_width < rlen) + refcol_width = rlen; +} + +static void prepare_format_display(struct ref *ref_map) +{ + struct ref *rm; + const char *format = "full"; + + git_config_get_string_const("fetch.output", &format); + if (!strcasecmp(format, "full")) + compact_format = 0; + else if (!strcasecmp(format, "compact")) + compact_format = 1; + else + die(_("configuration fetch.output contains invalid value %s"), + format); + + for (rm = ref_map; rm; rm = rm->next) { + if (rm->status == REF_STATUS_REJECT_SHALLOW || + !rm->peer_ref || + !strcmp(rm->name, "HEAD")) + continue; + + adjust_refcol_width(rm); + } +} + +static void print_remote_to_local(struct strbuf *display, + const char *remote, const char *local) +{ + strbuf_addf(display, "%-*s -> %s", refcol_width, remote, local); +} + +static int find_and_replace(struct strbuf *haystack, + const char *needle, + const char *placeholder) +{ + const char *p = strstr(haystack->buf, needle); + int plen, nlen; + + if (!p) + return 0; + + if (p > haystack->buf && p[-1] != '/') + return 0; + + plen = strlen(p); + nlen = strlen(needle); + if (plen > nlen && p[nlen] != '/') + return 0; + + strbuf_splice(haystack, p - haystack->buf, nlen, + placeholder, strlen(placeholder)); + return 1; +} + +static void print_compact(struct strbuf *display, + const char *remote, const char *local) +{ + struct strbuf r = STRBUF_INIT; + struct strbuf l = STRBUF_INIT; + + if (!strcmp(remote, local)) { + strbuf_addf(display, "%-*s -> *", refcol_width, remote); + return; + } + + strbuf_addstr(&r, remote); + strbuf_addstr(&l, local); + + if (!find_and_replace(&r, local, "*")) + find_and_replace(&l, remote, "*"); + print_remote_to_local(display, r.buf, l.buf); + + strbuf_release(&r); + strbuf_release(&l); +} + +static void format_display(struct strbuf *display, char code, + const char *summary, const char *error, + const char *remote, const char *local, + int summary_width) +{ + int width = (summary_width + strlen(summary) - gettext_width(summary)); + + strbuf_addf(display, "%c %-*s ", code, width, summary); + if (!compact_format) + print_remote_to_local(display, remote, local); + else + print_compact(display, remote, local); + if (error) + strbuf_addf(display, " (%s)", error); +} static int update_local_ref(struct ref *ref, const char *remote, const struct ref *remote_ref, - struct strbuf *display) + struct strbuf *display, + int summary_width) { struct commit *current = NULL, *updated; enum object_type type; struct branch *current_branch = branch_get(NULL); const char *pretty_ref = prettify_refname(ref->name); - type = sha1_object_info(ref->new_sha1, NULL); + type = sha1_object_info(ref->new_oid.hash, NULL); if (type < 0) - die(_("object %s not found"), sha1_to_hex(ref->new_sha1)); + die(_("object %s not found"), oid_to_hex(&ref->new_oid)); - if (!hashcmp(ref->old_sha1, ref->new_sha1)) { + if (!oidcmp(&ref->old_oid, &ref->new_oid)) { if (verbosity > 0) - strbuf_addf(display, "= %-*s %-*s -> %s", - TRANSPORT_SUMMARY(_("[up to date]")), - REFCOL_WIDTH, remote, pretty_ref); + format_display(display, '=', _("[up to date]"), NULL, + remote, pretty_ref, summary_width); return 0; } if (current_branch && !strcmp(ref->name, current_branch->name) && !(update_head_ok || is_bare_repository()) && - !is_null_sha1(ref->old_sha1)) { + !is_null_oid(&ref->old_oid)) { /* * If this is the head, and it's not okay to update * the head, and the old value of the head isn't empty... */ - strbuf_addf(display, - _("! %-*s %-*s -> %s (can't fetch in current branch)"), - TRANSPORT_SUMMARY(_("[rejected]")), - REFCOL_WIDTH, remote, pretty_ref); + format_display(display, '!', _("[rejected]"), + _("can't fetch in current branch"), + remote, pretty_ref, summary_width); return 1; } - if (!is_null_sha1(ref->old_sha1) && + if (!is_null_oid(&ref->old_oid) && starts_with(ref->name, "refs/tags/")) { int r; r = s_update_ref("updating tag", ref, 0); - strbuf_addf(display, "%c %-*s %-*s -> %s%s", - r ? '!' : '-', - TRANSPORT_SUMMARY(_("[tag update]")), - REFCOL_WIDTH, remote, pretty_ref, - r ? _(" (unable to update local ref)") : ""); + format_display(display, r ? '!' : 't', _("[tag update]"), + r ? _("unable to update local ref") : NULL, + remote, pretty_ref, summary_width); return r; } - current = lookup_commit_reference_gently(ref->old_sha1, 1); - updated = lookup_commit_reference_gently(ref->new_sha1, 1); + current = lookup_commit_reference_gently(ref->old_oid.hash, 1); + updated = lookup_commit_reference_gently(ref->new_oid.hash, 1); if (!current || !updated) { const char *msg; const char *what; @@ -516,53 +659,47 @@ static int update_local_ref(struct ref *ref, if ((recurse_submodules != RECURSE_SUBMODULES_OFF) && (recurse_submodules != RECURSE_SUBMODULES_ON)) - check_for_new_submodule_commits(ref->new_sha1); + check_for_new_submodule_commits(ref->new_oid.hash); r = s_update_ref(msg, ref, 0); - strbuf_addf(display, "%c %-*s %-*s -> %s%s", - r ? '!' : '*', - TRANSPORT_SUMMARY(what), - REFCOL_WIDTH, remote, pretty_ref, - r ? _(" (unable to update local ref)") : ""); + format_display(display, r ? '!' : '*', what, + r ? _("unable to update local ref") : NULL, + remote, pretty_ref, summary_width); return r; } if (in_merge_bases(current, updated)) { - char quickref[83]; + struct strbuf quickref = STRBUF_INIT; int r; - strcpy(quickref, find_unique_abbrev(current->object.sha1, DEFAULT_ABBREV)); - strcat(quickref, ".."); - strcat(quickref, find_unique_abbrev(ref->new_sha1, DEFAULT_ABBREV)); + strbuf_add_unique_abbrev(&quickref, current->object.oid.hash, DEFAULT_ABBREV); + strbuf_addstr(&quickref, ".."); + strbuf_add_unique_abbrev(&quickref, ref->new_oid.hash, DEFAULT_ABBREV); if ((recurse_submodules != RECURSE_SUBMODULES_OFF) && (recurse_submodules != RECURSE_SUBMODULES_ON)) - check_for_new_submodule_commits(ref->new_sha1); + check_for_new_submodule_commits(ref->new_oid.hash); r = s_update_ref("fast-forward", ref, 1); - strbuf_addf(display, "%c %-*s %-*s -> %s%s", - r ? '!' : ' ', - TRANSPORT_SUMMARY_WIDTH, quickref, - REFCOL_WIDTH, remote, pretty_ref, - r ? _(" (unable to update local ref)") : ""); + format_display(display, r ? '!' : ' ', quickref.buf, + r ? _("unable to update local ref") : NULL, + remote, pretty_ref, summary_width); + strbuf_release(&quickref); return r; } else if (force || ref->force) { - char quickref[84]; + struct strbuf quickref = STRBUF_INIT; int r; - strcpy(quickref, find_unique_abbrev(current->object.sha1, DEFAULT_ABBREV)); - strcat(quickref, "..."); - strcat(quickref, find_unique_abbrev(ref->new_sha1, DEFAULT_ABBREV)); + strbuf_add_unique_abbrev(&quickref, current->object.oid.hash, DEFAULT_ABBREV); + strbuf_addstr(&quickref, "..."); + strbuf_add_unique_abbrev(&quickref, ref->new_oid.hash, DEFAULT_ABBREV); if ((recurse_submodules != RECURSE_SUBMODULES_OFF) && (recurse_submodules != RECURSE_SUBMODULES_ON)) - check_for_new_submodule_commits(ref->new_sha1); + check_for_new_submodule_commits(ref->new_oid.hash); r = s_update_ref("forced-update", ref, 1); - strbuf_addf(display, "%c %-*s %-*s -> %s (%s)", - r ? '!' : '+', - TRANSPORT_SUMMARY_WIDTH, quickref, - REFCOL_WIDTH, remote, pretty_ref, - r ? _("unable to update local ref") : _("forced update")); + format_display(display, r ? '!' : '+', quickref.buf, + r ? _("unable to update local ref") : _("forced update"), + remote, pretty_ref, summary_width); + strbuf_release(&quickref); return r; } else { - strbuf_addf(display, "! %-*s %-*s -> %s %s", - TRANSPORT_SUMMARY(_("[rejected]")), - REFCOL_WIDTH, remote, pretty_ref, - _("(non-fast-forward)")); + format_display(display, '!', _("[rejected]"), _("non-fast-forward"), + remote, pretty_ref, summary_width); return 1; } } @@ -577,7 +714,7 @@ static int iterate_ref_map(void *cb_data, unsigned char sha1[20]) if (!ref) return -1; /* end of the list */ *rm = ref->next; - hashcpy(sha1, ref->old_sha1); + hashcpy(sha1, ref->old_oid.hash); return 0; } @@ -591,12 +728,13 @@ static int store_updated_refs(const char *raw_url, const char *remote_name, const char *what, *kind; struct ref *rm; char *url; - const char *filename = dry_run ? "/dev/null" : git_path("FETCH_HEAD"); + const char *filename = dry_run ? "/dev/null" : git_path_fetch_head(); int want_status; + int summary_width = transport_summary_width(ref_map); fp = fopen(filename, "a"); if (!fp) - return error(_("cannot open %s: %s\n"), filename, strerror(errno)); + return error_errno(_("cannot open %s"), filename); if (raw_url) url = transport_anonymize_url(raw_url); @@ -604,11 +742,13 @@ static int store_updated_refs(const char *raw_url, const char *remote_name, url = xstrdup("foreign"); rm = ref_map; - if (check_everything_connected(iterate_ref_map, 0, &rm)) { + if (check_connected(iterate_ref_map, &rm, NULL)) { rc = error(_("%s did not send all necessary objects\n"), url); goto abort; } + prepare_format_display(ref_map); + /* * We do a pass for each fetch_head_status type in their enum order, so * merged entries are written before not-for-merge. That lets readers @@ -628,7 +768,7 @@ static int store_updated_refs(const char *raw_url, const char *remote_name, continue; } - commit = lookup_commit_reference_gently(rm->old_sha1, 1); + commit = lookup_commit_reference_gently(rm->old_oid.hash, 1); if (!commit) rm->fetch_head_status = FETCH_HEAD_NOT_FOR_MERGE; @@ -636,10 +776,9 @@ static int store_updated_refs(const char *raw_url, const char *remote_name, continue; if (rm->peer_ref) { - ref = xcalloc(1, sizeof(*ref) + strlen(rm->peer_ref->name) + 1); - strcpy(ref->name, rm->peer_ref->name); - hashcpy(ref->old_sha1, rm->peer_ref->old_sha1); - hashcpy(ref->new_sha1, rm->old_sha1); + ref = alloc_ref(rm->peer_ref->name); + oidcpy(&ref->old_oid, &rm->peer_ref->old_oid); + oidcpy(&ref->new_oid, &rm->old_oid); ref->force = rm->peer_ref->force; } @@ -684,7 +823,7 @@ static int store_updated_refs(const char *raw_url, const char *remote_name, /* fall-through */ case FETCH_HEAD_MERGE: fprintf(fp, "%s\t%s\t%s", - sha1_to_hex(rm->old_sha1), + oid_to_hex(&rm->old_oid), merge_status_marker, note.buf); for (i = 0; i < url_len; ++i) @@ -701,14 +840,14 @@ static int store_updated_refs(const char *raw_url, const char *remote_name, strbuf_reset(¬e); if (ref) { - rc |= update_local_ref(ref, what, rm, ¬e); + rc |= update_local_ref(ref, what, rm, ¬e, + summary_width); free(ref); } else - strbuf_addf(¬e, "* %-*s %-*s -> FETCH_HEAD", - TRANSPORT_SUMMARY_WIDTH, - *kind ? kind : "branch", - REFCOL_WIDTH, - *what ? what : "HEAD"); + format_display(¬e, '*', + *kind ? kind : "branch", NULL, + *what ? what : "HEAD", + "FETCH_HEAD", summary_width); if (note.len) { if (verbosity >= 0 && !shown_url) { fprintf(stderr, _("From %.*s\n"), @@ -741,6 +880,7 @@ static int store_updated_refs(const char *raw_url, const char *remote_name, static int quickfetch(struct ref *ref_map) { struct ref *rm = ref_map; + struct check_connected_options opt = CHECK_CONNECTED_INIT; /* * If we are deepening a shallow clone we already have these @@ -749,9 +889,10 @@ static int quickfetch(struct ref *ref_map) * really need to perform. Claiming failure now will ensure * we perform the network exchange to deepen our history. */ - if (depth) + if (deepen) return -1; - return check_everything_connected(iterate_ref_map, 1, &rm); + opt.quiet = 1; + return check_connected(iterate_ref_map, &rm, &opt); } static int fetch_refs(struct transport *transport, struct ref *ref_map) @@ -773,6 +914,7 @@ static int prune_refs(struct refspec *refs, int ref_count, struct ref *ref_map, int url_len, i, result = 0; struct ref *ref, *stale_refs = get_stale_heads(refs, ref_count, ref_map); char *url; + int summary_width = transport_summary_width(stale_refs); const char *dangling_msg = dry_run ? _(" (%s will become dangling)") : _(" (%s has become dangling)"); @@ -790,20 +932,32 @@ static int prune_refs(struct refspec *refs, int ref_count, struct ref *ref_map, if (4 < i && !strncmp(".git", url + i - 3, 4)) url_len = i - 3; - for (ref = stale_refs; ref; ref = ref->next) { - if (!dry_run) - result |= delete_ref(ref->name, NULL, 0); - if (verbosity >= 0 && !shown_url) { - fprintf(stderr, _("From %.*s\n"), url_len, url); - shown_url = 1; - } - if (verbosity >= 0) { - fprintf(stderr, " x %-*s %-*s -> %s\n", - TRANSPORT_SUMMARY(_("[deleted]")), - REFCOL_WIDTH, _("(none)"), prettify_refname(ref->name)); + if (!dry_run) { + struct string_list refnames = STRING_LIST_INIT_NODUP; + + for (ref = stale_refs; ref; ref = ref->next) + string_list_append(&refnames, ref->name); + + result = delete_refs(&refnames, 0); + string_list_clear(&refnames, 0); + } + + if (verbosity >= 0) { + for (ref = stale_refs; ref; ref = ref->next) { + struct strbuf sb = STRBUF_INIT; + if (!shown_url) { + fprintf(stderr, _("From %.*s\n"), url_len, url); + shown_url = 1; + } + format_display(&sb, '-', _("[deleted]"), NULL, + _("(none)"), prettify_refname(ref->name), + summary_width); + fprintf(stderr, " %s\n",sb.buf); + strbuf_release(&sb); warn_dangling_symref(stderr, dangling_msg, ref->name); } } + free(url); free_refs(stale_refs); return result; @@ -825,11 +979,11 @@ static void check_not_current_branch(struct ref *ref_map) static int truncate_fetch_head(void) { - const char *filename = git_path("FETCH_HEAD"); - FILE *fp = fopen(filename, "w"); + const char *filename = git_path_fetch_head(); + FILE *fp = fopen_for_writing(filename); if (!fp) - return error(_("cannot open %s: %s\n"), filename, strerror(errno)); + return error_errno(_("cannot open %s"), filename); fclose(fp); return 0; } @@ -845,17 +999,25 @@ static void set_option(struct transport *transport, const char *name, const char name, transport->url); } -static struct transport *prepare_transport(struct remote *remote) +static struct transport *prepare_transport(struct remote *remote, int deepen) { struct transport *transport; transport = transport_get(remote, NULL); transport_set_verbosity(transport, verbosity, progress); + transport->family = family; if (upload_pack) set_option(transport, TRANS_OPT_UPLOADPACK, upload_pack); if (keep) set_option(transport, TRANS_OPT_KEEP, "yes"); if (depth) set_option(transport, TRANS_OPT_DEPTH, depth); + if (deepen && deepen_since) + set_option(transport, TRANS_OPT_DEEPEN_SINCE, deepen_since); + if (deepen && deepen_not.nr) + set_option(transport, TRANS_OPT_DEEPEN_NOT, + (const char *)&deepen_not); + if (deepen_relative) + set_option(transport, TRANS_OPT_DEEPEN_RELATIVE, "yes"); if (update_shallow) set_option(transport, TRANS_OPT_UPDATE_SHALLOW, "yes"); return transport; @@ -863,13 +1025,25 @@ static struct transport *prepare_transport(struct remote *remote) static void backfill_tags(struct transport *transport, struct ref *ref_map) { - if (transport->cannot_reuse) { - gsecondary = prepare_transport(transport->remote); + int cannot_reuse; + + /* + * Once we have set TRANS_OPT_DEEPEN_SINCE, we can't unset it + * when remote helper is used (setting it to an empty string + * is not unsetting). We could extend the remote helper + * protocol for that, but for now, just force a new connection + * without deepen-since. Similar story for deepen-not. + */ + cannot_reuse = transport->cannot_reuse || + deepen_since || deepen_not.nr; + if (cannot_reuse) { + gsecondary = prepare_transport(transport->remote, 0); transport = gsecondary; } transport_set_option(transport, TRANS_OPT_FOLLOWTAGS, NULL); transport_set_option(transport, TRANS_OPT_DEPTH, "0"); + transport_set_option(transport, TRANS_OPT_DEEPEN_RELATIVE, NULL); fetch_refs(transport, ref_map); if (gsecondary) { @@ -917,7 +1091,7 @@ static int do_fetch(struct transport *transport, rm->peer_ref->name); if (peer_item) { struct object_id *old_oid = peer_item->util; - hashcpy(rm->peer_ref->old_sha1, old_oid->hash); + oidcpy(&rm->peer_ref->old_oid, old_oid); } } } @@ -979,17 +1153,15 @@ static int get_remote_group(const char *key, const char *value, void *priv) { struct remote_group_data *g = priv; - if (starts_with(key, "remotes.") && - !strcmp(key + 8, g->name)) { + if (skip_prefix(key, "remotes.", &key) && !strcmp(key, g->name)) { /* split list by white space */ - int space = strcspn(value, " \t\n"); while (*value) { - if (space > 1) { - string_list_append(g->list, - xstrndup(value, space)); - } - value += space + (value[space] != '\0'); - space = strcspn(value, " \t\n"); + size_t wordlen = strcspn(value, " \t\n"); + + if (wordlen >= 1) + string_list_append_nodup(g->list, + xstrndup(value, wordlen)); + value += wordlen + (value[wordlen] != '\0'); } } @@ -1004,10 +1176,9 @@ static int add_remote_or_group(const char *name, struct string_list *list) git_config(get_remote_group, &g); if (list->nr == prev_nr) { - struct remote *remote; - if (!remote_is_configured(name)) + struct remote *remote = remote_get(name); + if (!remote_is_configured(remote)) return 0; - remote = remote_get(name); string_list_append(list, remote->name); } return 1; @@ -1083,7 +1254,7 @@ static int fetch_one(struct remote *remote, int argc, const char **argv) die(_("No remote repository specified. Please, specify either a URL or a\n" "remote name from which new revisions should be fetched.")); - gtransport = prepare_transport(remote); + gtransport = prepare_transport(remote, 1); if (prune < 0) { /* no command line request */ @@ -1098,7 +1269,7 @@ static int fetch_one(struct remote *remote, int argc, const char **argv) if (argc > 0) { int j = 0; int i; - refs = xcalloc(argc + 1, sizeof(const char *)); + refs = xcalloc(st_add(argc, 1), sizeof(const char *)); for (i = 0; i < argc; i++) { if (!strcmp(argv[i], "tag")) { i++; @@ -1126,7 +1297,7 @@ 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 = STRING_LIST_INIT_NODUP; + struct string_list list = STRING_LIST_INIT_DUP; struct remote *remote; int result = 0; struct argv_array argv_gc_auto = ARGV_ARRAY_INIT; @@ -1143,21 +1314,27 @@ int cmd_fetch(int argc, const char **argv, const char *prefix) argc = parse_options(argc, argv, prefix, builtin_fetch_options, builtin_fetch_usage, 0); + if (deepen_relative) { + if (deepen_relative < 0) + die(_("Negative depth in --deepen is not supported")); + if (depth) + die(_("--deepen and --depth are mutually exclusive")); + depth = xstrfmt("%d", deepen_relative); + } if (unshallow) { if (depth) die(_("--depth and --unshallow cannot be used together")); else if (!is_repository_shallow()) die(_("--unshallow on a complete repository does not make sense")); - else { - static char inf_depth[12]; - sprintf(inf_depth, "%d", INFINITE_DEPTH); - depth = inf_depth; - } + else + depth = xstrfmt("%d", INFINITE_DEPTH); } /* no need to be strict, transport_set_option() will validate it again */ if (depth && atoi(depth) < 1) die(_("depth %s is not a positive number"), depth); + if (depth || deepen_since || deepen_not.nr) + deepen = 1; if (recurse_submodules != RECURSE_SUBMODULES_OFF) { if (recurse_submodules_default) { @@ -1207,14 +1384,15 @@ int cmd_fetch(int argc, const char **argv, const char *prefix) result = fetch_populated_submodules(&options, submodule_prefix, recurse_submodules, - verbosity < 0); + verbosity < 0, + max_children); argv_array_clear(&options); } - /* All names were strdup()ed or strndup()ed */ - list.strdup_strings = 1; string_list_clear(&list, 0); + close_all_packs(); + argv_array_pushl(&argv_gc_auto, "gc", "--auto", NULL); if (verbosity < 0) argv_array_push(&argv_gc_auto, "--quiet"); diff --git a/builtin/fmt-merge-msg.c b/builtin/fmt-merge-msg.c index 05f4c26311..efab62fd85 100644 --- a/builtin/fmt-merge-msg.c +++ b/builtin/fmt-merge-msg.c @@ -1,5 +1,6 @@ #include "builtin.h" #include "cache.h" +#include "refs.h" #include "commit.h" #include "diff.h" #include "revision.h" @@ -271,7 +272,7 @@ static int cmp_string_list_util_as_integral(const void *a_, const void *b_) static void add_people_count(struct strbuf *out, struct string_list *people) { if (people->nr == 1) - strbuf_addf(out, "%s", people->items[0].string); + strbuf_addstr(out, people->items[0].string); else if (people->nr == 2) strbuf_addf(out, "%s (%d) and %s (%d)", people->items[0].string, @@ -313,14 +314,10 @@ static void add_people_info(struct strbuf *out, struct string_list *authors, struct string_list *committers) { - if (authors->nr) - qsort(authors->items, - authors->nr, sizeof(authors->items[0]), - cmp_string_list_util_as_integral); - if (committers->nr) - qsort(committers->items, - committers->nr, sizeof(committers->items[0]), - cmp_string_list_util_as_integral); + QSORT(authors->items, authors->nr, + cmp_string_list_util_as_integral); + QSORT(committers->items, committers->nr, + cmp_string_list_util_as_integral); credit_people(out, authors, 'a'); credit_people(out, committers, 'c'); @@ -377,7 +374,7 @@ static void shortlog(const char *name, if (!sb.len) string_list_append(&subjects, - sha1_to_hex(commit->object.sha1)); + oid_to_hex(&commit->object.oid)); else string_list_append(&subjects, strbuf_detach(&sb, NULL)); } @@ -394,7 +391,7 @@ static void shortlog(const char *name, for (i = 0; i < subjects.nr; i++) if (i >= limit) - strbuf_addf(out, " ...\n"); + strbuf_addstr(out, " ...\n"); else strbuf_addf(out, " %s\n", subjects.items[i].string); @@ -536,7 +533,7 @@ static void fmt_merge_msg_sigs(struct strbuf *out) static void find_merge_parents(struct merge_parents *result, struct strbuf *in, unsigned char *head) { - struct commit_list *parents, *next; + struct commit_list *parents; struct commit *head_commit; int pos = 0, i, j; @@ -567,7 +564,7 @@ static void find_merge_parents(struct merge_parents *result, if (!parent) continue; commit_list_insert(parent, &parents); - add_merge_parent(result, obj->sha1, parent->object.sha1); + add_merge_parent(result, obj->oid.hash, parent->object.oid.hash); } head_commit = lookup_commit(head); if (head_commit) @@ -575,13 +572,10 @@ static void find_merge_parents(struct merge_parents *result, parents = reduce_heads(parents); while (parents) { + struct commit *cmit = pop_commit(&parents); for (i = 0; i < result->nr; i++) - if (!hashcmp(result->item[i].commit, - parents->item->object.sha1)) + if (!hashcmp(result->item[i].commit, cmit->object.oid.hash)) result->item[i].used = 1; - next = parents->next; - free(parents); - parents = next; } for (i = j = 0; i < result->nr; i++) { diff --git a/builtin/for-each-ref.c b/builtin/for-each-ref.c index f7e51a7fad..4e9f6c29bf 100644 --- a/builtin/for-each-ref.c +++ b/builtin/for-each-ref.c @@ -2,1091 +2,25 @@ #include "cache.h" #include "refs.h" #include "object.h" -#include "tag.h" -#include "commit.h" -#include "tree.h" -#include "blob.h" -#include "quote.h" #include "parse-options.h" -#include "remote.h" -#include "color.h" - -/* Quoting styles */ -#define QUOTE_NONE 0 -#define QUOTE_SHELL 1 -#define QUOTE_PERL 2 -#define QUOTE_PYTHON 4 -#define QUOTE_TCL 8 - -typedef enum { FIELD_STR, FIELD_ULONG, FIELD_TIME } cmp_type; - -struct atom_value { - const char *s; - unsigned long ul; /* used for sorting when not FIELD_STR */ -}; - -struct ref_sort { - struct ref_sort *next; - int atom; /* index into used_atom array */ - unsigned reverse : 1; -}; - -struct refinfo { - char *refname; - unsigned char objectname[20]; - int flag; - const char *symref; - struct atom_value *value; -}; - -static struct { - const char *name; - cmp_type cmp_type; -} valid_atom[] = { - { "refname" }, - { "objecttype" }, - { "objectsize", FIELD_ULONG }, - { "objectname" }, - { "tree" }, - { "parent" }, - { "numparent", FIELD_ULONG }, - { "object" }, - { "type" }, - { "tag" }, - { "author" }, - { "authorname" }, - { "authoremail" }, - { "authordate", FIELD_TIME }, - { "committer" }, - { "committername" }, - { "committeremail" }, - { "committerdate", FIELD_TIME }, - { "tagger" }, - { "taggername" }, - { "taggeremail" }, - { "taggerdate", FIELD_TIME }, - { "creator" }, - { "creatordate", FIELD_TIME }, - { "subject" }, - { "body" }, - { "contents" }, - { "contents:subject" }, - { "contents:body" }, - { "contents:signature" }, - { "upstream" }, - { "push" }, - { "symref" }, - { "flag" }, - { "HEAD" }, - { "color" }, -}; - -/* - * An atom is a valid field atom listed above, possibly prefixed with - * a "*" to denote deref_tag(). - * - * We parse given format string and sort specifiers, and make a list - * of properties that we need to extract out of objects. refinfo - * structure will hold an array of values extracted that can be - * indexed with the "atom number", which is an index into this - * array. - */ -static const char **used_atom; -static cmp_type *used_atom_type; -static int used_atom_cnt, need_tagged, need_symref; -static int need_color_reset_at_eol; - -/* - * Used to parse format string and sort specifiers - */ -static int parse_atom(const char *atom, const char *ep) -{ - const char *sp; - int i, at; - - sp = atom; - if (*sp == '*' && sp < ep) - sp++; /* deref */ - if (ep <= sp) - die("malformed field name: %.*s", (int)(ep-atom), atom); - - /* Do we have the atom already used elsewhere? */ - for (i = 0; i < used_atom_cnt; i++) { - int len = strlen(used_atom[i]); - if (len == ep - atom && !memcmp(used_atom[i], atom, len)) - return i; - } - - /* Is the atom a valid one? */ - for (i = 0; i < ARRAY_SIZE(valid_atom); i++) { - int len = strlen(valid_atom[i].name); - /* - * If the atom name has a colon, strip it and everything after - * it off - it specifies the format for this entry, and - * shouldn't be used for checking against the valid_atom - * table. - */ - const char *formatp = strchr(sp, ':'); - if (!formatp || ep < formatp) - formatp = ep; - if (len == formatp - sp && !memcmp(valid_atom[i].name, sp, len)) - break; - } - - if (ARRAY_SIZE(valid_atom) <= i) - die("unknown field name: %.*s", (int)(ep-atom), atom); - - /* Add it in, including the deref prefix */ - at = used_atom_cnt; - used_atom_cnt++; - REALLOC_ARRAY(used_atom, used_atom_cnt); - REALLOC_ARRAY(used_atom_type, used_atom_cnt); - used_atom[at] = xmemdupz(atom, ep - atom); - used_atom_type[at] = valid_atom[i].cmp_type; - if (*atom == '*') - need_tagged = 1; - if (!strcmp(used_atom[at], "symref")) - need_symref = 1; - return at; -} - -/* - * In a format string, find the next occurrence of %(atom). - */ -static const char *find_next(const char *cp) -{ - while (*cp) { - if (*cp == '%') { - /* - * %( is the start of an atom; - * %% is a quoted per-cent. - */ - if (cp[1] == '(') - return cp; - else if (cp[1] == '%') - cp++; /* skip over two % */ - /* otherwise this is a singleton, literal % */ - } - cp++; - } - return NULL; -} - -/* - * Make sure the format string is well formed, and parse out - * the used atoms. - */ -static int verify_format(const char *format) -{ - const char *cp, *sp; - - need_color_reset_at_eol = 0; - for (cp = format; *cp && (sp = find_next(cp)); ) { - const char *color, *ep = strchr(sp, ')'); - int at; - - if (!ep) - return error("malformed format string %s", sp); - /* sp points at "%(" and ep points at the closing ")" */ - at = parse_atom(sp + 2, ep); - cp = ep + 1; - - if (skip_prefix(used_atom[at], "color:", &color)) - need_color_reset_at_eol = !!strcmp(color, "reset"); - } - return 0; -} - -/* - * Given an object name, read the object data and size, and return a - * "struct object". If the object data we are returning is also borrowed - * by the "struct object" representation, set *eaten as well---it is a - * signal from parse_object_buffer to us not to free the buffer. - */ -static void *get_obj(const unsigned char *sha1, struct object **obj, unsigned long *sz, int *eaten) -{ - enum object_type type; - void *buf = read_sha1_file(sha1, &type, sz); - - if (buf) - *obj = parse_object_buffer(sha1, type, *sz, buf, eaten); - else - *obj = NULL; - return buf; -} - -static int grab_objectname(const char *name, const unsigned char *sha1, - struct atom_value *v) -{ - if (!strcmp(name, "objectname")) { - char *s = xmalloc(41); - strcpy(s, sha1_to_hex(sha1)); - v->s = s; - return 1; - } - if (!strcmp(name, "objectname:short")) { - v->s = xstrdup(find_unique_abbrev(sha1, DEFAULT_ABBREV)); - return 1; - } - return 0; -} - -/* See grab_values */ -static void grab_common_values(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz) -{ - int i; - - for (i = 0; i < used_atom_cnt; i++) { - const char *name = used_atom[i]; - struct atom_value *v = &val[i]; - if (!!deref != (*name == '*')) - continue; - if (deref) - name++; - if (!strcmp(name, "objecttype")) - v->s = typename(obj->type); - else if (!strcmp(name, "objectsize")) { - char *s = xmalloc(40); - sprintf(s, "%lu", sz); - v->ul = sz; - v->s = s; - } - else if (deref) - grab_objectname(name, obj->sha1, v); - } -} - -/* See grab_values */ -static void grab_tag_values(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz) -{ - int i; - struct tag *tag = (struct tag *) obj; - - for (i = 0; i < used_atom_cnt; i++) { - const char *name = used_atom[i]; - struct atom_value *v = &val[i]; - if (!!deref != (*name == '*')) - continue; - if (deref) - name++; - if (!strcmp(name, "tag")) - v->s = tag->tag; - else if (!strcmp(name, "type") && tag->tagged) - v->s = typename(tag->tagged->type); - else if (!strcmp(name, "object") && tag->tagged) { - char *s = xmalloc(41); - strcpy(s, sha1_to_hex(tag->tagged->sha1)); - v->s = s; - } - } -} - -/* See grab_values */ -static void grab_commit_values(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz) -{ - int i; - struct commit *commit = (struct commit *) obj; - - for (i = 0; i < used_atom_cnt; i++) { - const char *name = used_atom[i]; - struct atom_value *v = &val[i]; - if (!!deref != (*name == '*')) - continue; - if (deref) - name++; - if (!strcmp(name, "tree")) { - char *s = xmalloc(41); - strcpy(s, sha1_to_hex(commit->tree->object.sha1)); - v->s = s; - } - if (!strcmp(name, "numparent")) { - char *s = xmalloc(40); - v->ul = commit_list_count(commit->parents); - sprintf(s, "%lu", v->ul); - v->s = s; - } - else if (!strcmp(name, "parent")) { - int num = commit_list_count(commit->parents); - int i; - struct commit_list *parents; - char *s = xmalloc(41 * num + 1); - v->s = s; - for (i = 0, parents = commit->parents; - parents; - parents = parents->next, i = i + 41) { - struct commit *parent = parents->item; - strcpy(s+i, sha1_to_hex(parent->object.sha1)); - if (parents->next) - s[i+40] = ' '; - } - if (!i) - *s = '\0'; - } - } -} - -static const char *find_wholine(const char *who, int wholen, const char *buf, unsigned long sz) -{ - const char *eol; - while (*buf) { - if (!strncmp(buf, who, wholen) && - buf[wholen] == ' ') - return buf + wholen + 1; - eol = strchr(buf, '\n'); - if (!eol) - return ""; - eol++; - if (*eol == '\n') - return ""; /* end of header */ - buf = eol; - } - return ""; -} - -static const char *copy_line(const char *buf) -{ - const char *eol = strchrnul(buf, '\n'); - return xmemdupz(buf, eol - buf); -} - -static const char *copy_name(const char *buf) -{ - const char *cp; - for (cp = buf; *cp && *cp != '\n'; cp++) { - if (!strncmp(cp, " <", 2)) - return xmemdupz(buf, cp - buf); - } - return ""; -} - -static const char *copy_email(const char *buf) -{ - const char *email = strchr(buf, '<'); - const char *eoemail; - if (!email) - return ""; - eoemail = strchr(email, '>'); - if (!eoemail) - return ""; - return xmemdupz(email, eoemail + 1 - email); -} - -static char *copy_subject(const char *buf, unsigned long len) -{ - char *r = xmemdupz(buf, len); - int i; - - for (i = 0; i < len; i++) - if (r[i] == '\n') - r[i] = ' '; - - return r; -} - -static void grab_date(const char *buf, struct atom_value *v, const char *atomname) -{ - const char *eoemail = strstr(buf, "> "); - char *zone; - unsigned long timestamp; - long tz; - enum date_mode date_mode = DATE_NORMAL; - const char *formatp; - - /* - * We got here because atomname ends in "date" or "date<something>"; - * it's not possible that <something> is not ":<format>" because - * parse_atom() wouldn't have allowed it, so we can assume that no - * ":" means no format is specified, and use the default. - */ - formatp = strchr(atomname, ':'); - if (formatp != NULL) { - formatp++; - date_mode = parse_date_format(formatp); - } - - if (!eoemail) - goto bad; - timestamp = strtoul(eoemail + 2, &zone, 10); - if (timestamp == ULONG_MAX) - goto bad; - tz = strtol(zone, NULL, 10); - if ((tz == LONG_MIN || tz == LONG_MAX) && errno == ERANGE) - goto bad; - v->s = xstrdup(show_date(timestamp, tz, date_mode)); - v->ul = timestamp; - return; - bad: - v->s = ""; - v->ul = 0; -} - -/* See grab_values */ -static void grab_person(const char *who, struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz) -{ - int i; - int wholen = strlen(who); - const char *wholine = NULL; - - for (i = 0; i < used_atom_cnt; i++) { - const char *name = used_atom[i]; - struct atom_value *v = &val[i]; - if (!!deref != (*name == '*')) - continue; - if (deref) - name++; - if (strncmp(who, name, wholen)) - continue; - if (name[wholen] != 0 && - strcmp(name + wholen, "name") && - strcmp(name + wholen, "email") && - !starts_with(name + wholen, "date")) - continue; - if (!wholine) - wholine = find_wholine(who, wholen, buf, sz); - if (!wholine) - return; /* no point looking for it */ - if (name[wholen] == 0) - v->s = copy_line(wholine); - else if (!strcmp(name + wholen, "name")) - v->s = copy_name(wholine); - else if (!strcmp(name + wholen, "email")) - v->s = copy_email(wholine); - else if (starts_with(name + wholen, "date")) - grab_date(wholine, v, name); - } - - /* - * For a tag or a commit object, if "creator" or "creatordate" is - * requested, do something special. - */ - if (strcmp(who, "tagger") && strcmp(who, "committer")) - return; /* "author" for commit object is not wanted */ - if (!wholine) - wholine = find_wholine(who, wholen, buf, sz); - if (!wholine) - return; - for (i = 0; i < used_atom_cnt; i++) { - const char *name = used_atom[i]; - struct atom_value *v = &val[i]; - if (!!deref != (*name == '*')) - continue; - if (deref) - name++; - - if (starts_with(name, "creatordate")) - grab_date(wholine, v, name); - else if (!strcmp(name, "creator")) - v->s = copy_line(wholine); - } -} - -static void find_subpos(const char *buf, unsigned long sz, - const char **sub, unsigned long *sublen, - const char **body, unsigned long *bodylen, - unsigned long *nonsiglen, - const char **sig, unsigned long *siglen) -{ - const char *eol; - /* skip past header until we hit empty line */ - while (*buf && *buf != '\n') { - eol = strchrnul(buf, '\n'); - if (*eol) - eol++; - buf = eol; - } - /* skip any empty lines */ - while (*buf == '\n') - buf++; - - /* parse signature first; we might not even have a subject line */ - *sig = buf + parse_signature(buf, strlen(buf)); - *siglen = strlen(*sig); - - /* subject is first non-empty line */ - *sub = buf; - /* subject goes to first empty line */ - while (buf < *sig && *buf && *buf != '\n') { - eol = strchrnul(buf, '\n'); - if (*eol) - eol++; - buf = eol; - } - *sublen = buf - *sub; - /* drop trailing newline, if present */ - if (*sublen && (*sub)[*sublen - 1] == '\n') - *sublen -= 1; - - /* skip any empty lines */ - while (*buf == '\n') - buf++; - *body = buf; - *bodylen = strlen(buf); - *nonsiglen = *sig - buf; -} - -/* See grab_values */ -static void grab_sub_body_contents(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz) -{ - int i; - const char *subpos = NULL, *bodypos = NULL, *sigpos = NULL; - unsigned long sublen = 0, bodylen = 0, nonsiglen = 0, siglen = 0; - - for (i = 0; i < used_atom_cnt; i++) { - const char *name = used_atom[i]; - struct atom_value *v = &val[i]; - if (!!deref != (*name == '*')) - continue; - if (deref) - name++; - if (strcmp(name, "subject") && - strcmp(name, "body") && - strcmp(name, "contents") && - strcmp(name, "contents:subject") && - strcmp(name, "contents:body") && - strcmp(name, "contents:signature")) - continue; - if (!subpos) - find_subpos(buf, sz, - &subpos, &sublen, - &bodypos, &bodylen, &nonsiglen, - &sigpos, &siglen); - - if (!strcmp(name, "subject")) - v->s = copy_subject(subpos, sublen); - else if (!strcmp(name, "contents:subject")) - v->s = copy_subject(subpos, sublen); - else if (!strcmp(name, "body")) - v->s = xmemdupz(bodypos, bodylen); - else if (!strcmp(name, "contents:body")) - v->s = xmemdupz(bodypos, nonsiglen); - else if (!strcmp(name, "contents:signature")) - v->s = xmemdupz(sigpos, siglen); - else if (!strcmp(name, "contents")) - v->s = xstrdup(subpos); - } -} - -/* - * We want to have empty print-string for field requests - * that do not apply (e.g. "authordate" for a tag object) - */ -static void fill_missing_values(struct atom_value *val) -{ - int i; - for (i = 0; i < used_atom_cnt; i++) { - struct atom_value *v = &val[i]; - if (v->s == NULL) - v->s = ""; - } -} - -/* - * val is a list of atom_value to hold returned values. Extract - * the values for atoms in used_atom array out of (obj, buf, sz). - * when deref is false, (obj, buf, sz) is the object that is - * pointed at by the ref itself; otherwise it is the object the - * ref (which is a tag) refers to. - */ -static void grab_values(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz) -{ - grab_common_values(val, deref, obj, buf, sz); - switch (obj->type) { - case OBJ_TAG: - grab_tag_values(val, deref, obj, buf, sz); - grab_sub_body_contents(val, deref, obj, buf, sz); - grab_person("tagger", val, deref, obj, buf, sz); - break; - case OBJ_COMMIT: - grab_commit_values(val, deref, obj, buf, sz); - grab_sub_body_contents(val, deref, obj, buf, sz); - grab_person("author", val, deref, obj, buf, sz); - grab_person("committer", val, deref, obj, buf, sz); - break; - case OBJ_TREE: - /* grab_tree_values(val, deref, obj, buf, sz); */ - break; - case OBJ_BLOB: - /* grab_blob_values(val, deref, obj, buf, sz); */ - break; - default: - die("Eh? Object of type %d?", obj->type); - } -} - -static inline char *copy_advance(char *dst, const char *src) -{ - while (*src) - *dst++ = *src++; - return dst; -} - -/* - * Parse the object referred by ref, and grab needed value. - */ -static void populate_value(struct refinfo *ref) -{ - void *buf; - struct object *obj; - int eaten, i; - unsigned long size; - const unsigned char *tagged; - - ref->value = xcalloc(used_atom_cnt, sizeof(struct atom_value)); - - if (need_symref && (ref->flag & REF_ISSYMREF) && !ref->symref) { - unsigned char unused1[20]; - ref->symref = resolve_refdup(ref->refname, RESOLVE_REF_READING, - unused1, NULL); - if (!ref->symref) - ref->symref = ""; - } - - /* Fill in specials first */ - for (i = 0; i < used_atom_cnt; i++) { - const char *name = used_atom[i]; - struct atom_value *v = &ref->value[i]; - int deref = 0; - const char *refname; - const char *formatp; - struct branch *branch = NULL; - - if (*name == '*') { - deref = 1; - name++; - } - - if (starts_with(name, "refname")) - refname = ref->refname; - else if (starts_with(name, "symref")) - refname = ref->symref ? ref->symref : ""; - else if (starts_with(name, "upstream")) { - const char *branch_name; - /* only local branches may have an upstream */ - if (!skip_prefix(ref->refname, "refs/heads/", - &branch_name)) - continue; - branch = branch_get(branch_name); - - refname = branch_get_upstream(branch, NULL); - if (!refname) - continue; - } else if (starts_with(name, "push")) { - const char *branch_name; - if (!skip_prefix(ref->refname, "refs/heads/", - &branch_name)) - continue; - branch = branch_get(branch_name); - - refname = branch_get_push(branch, NULL); - if (!refname) - continue; - } else if (starts_with(name, "color:")) { - char color[COLOR_MAXLEN] = ""; - - if (color_parse(name + 6, color) < 0) - die(_("unable to parse format")); - v->s = xstrdup(color); - continue; - } else if (!strcmp(name, "flag")) { - char buf[256], *cp = buf; - if (ref->flag & REF_ISSYMREF) - cp = copy_advance(cp, ",symref"); - if (ref->flag & REF_ISPACKED) - cp = copy_advance(cp, ",packed"); - if (cp == buf) - v->s = ""; - else { - *cp = '\0'; - v->s = xstrdup(buf + 1); - } - continue; - } else if (!deref && grab_objectname(name, ref->objectname, v)) { - continue; - } else if (!strcmp(name, "HEAD")) { - const char *head; - unsigned char sha1[20]; - - head = resolve_ref_unsafe("HEAD", RESOLVE_REF_READING, - sha1, NULL); - if (!strcmp(ref->refname, head)) - v->s = "*"; - else - v->s = " "; - continue; - } else - continue; - - formatp = strchr(name, ':'); - if (formatp) { - int num_ours, num_theirs; - - formatp++; - if (!strcmp(formatp, "short")) - refname = shorten_unambiguous_ref(refname, - warn_ambiguous_refs); - else if (!strcmp(formatp, "track") && - (starts_with(name, "upstream") || - starts_with(name, "push"))) { - char buf[40]; - - if (stat_tracking_info(branch, &num_ours, - &num_theirs, NULL)) - continue; - - if (!num_ours && !num_theirs) - v->s = ""; - else if (!num_ours) { - sprintf(buf, "[behind %d]", num_theirs); - v->s = xstrdup(buf); - } else if (!num_theirs) { - sprintf(buf, "[ahead %d]", num_ours); - v->s = xstrdup(buf); - } else { - sprintf(buf, "[ahead %d, behind %d]", - num_ours, num_theirs); - v->s = xstrdup(buf); - } - continue; - } else if (!strcmp(formatp, "trackshort") && - (starts_with(name, "upstream") || - starts_with(name, "push"))) { - assert(branch); - - if (stat_tracking_info(branch, &num_ours, - &num_theirs, NULL)) - continue; - - if (!num_ours && !num_theirs) - v->s = "="; - else if (!num_ours) - v->s = "<"; - else if (!num_theirs) - v->s = ">"; - else - v->s = "<>"; - continue; - } else - die("unknown %.*s format %s", - (int)(formatp - name), name, formatp); - } - - if (!deref) - v->s = refname; - else { - int len = strlen(refname); - char *s = xmalloc(len + 4); - sprintf(s, "%s^{}", refname); - v->s = s; - } - } - - for (i = 0; i < used_atom_cnt; i++) { - struct atom_value *v = &ref->value[i]; - if (v->s == NULL) - goto need_obj; - } - return; - - need_obj: - buf = get_obj(ref->objectname, &obj, &size, &eaten); - if (!buf) - die("missing object %s for %s", - sha1_to_hex(ref->objectname), ref->refname); - if (!obj) - die("parse_object_buffer failed on %s for %s", - sha1_to_hex(ref->objectname), ref->refname); - - grab_values(ref->value, 0, obj, buf, size); - if (!eaten) - free(buf); - - /* - * If there is no atom that wants to know about tagged - * object, we are done. - */ - if (!need_tagged || (obj->type != OBJ_TAG)) - return; - - /* - * If it is a tag object, see if we use a value that derefs - * the object, and if we do grab the object it refers to. - */ - tagged = ((struct tag *)obj)->tagged->sha1; - - /* - * NEEDSWORK: This derefs tag only once, which - * is good to deal with chains of trust, but - * is not consistent with what deref_tag() does - * which peels the onion to the core. - */ - buf = get_obj(tagged, &obj, &size, &eaten); - if (!buf) - die("missing object %s for %s", - sha1_to_hex(tagged), ref->refname); - if (!obj) - die("parse_object_buffer failed on %s for %s", - sha1_to_hex(tagged), ref->refname); - grab_values(ref->value, 1, obj, buf, size); - if (!eaten) - free(buf); -} - -/* - * Given a ref, return the value for the atom. This lazily gets value - * out of the object by calling populate value. - */ -static void get_value(struct refinfo *ref, int atom, struct atom_value **v) -{ - if (!ref->value) { - populate_value(ref); - fill_missing_values(ref->value); - } - *v = &ref->value[atom]; -} - -struct grab_ref_cbdata { - struct refinfo **grab_array; - const char **grab_pattern; - int grab_cnt; -}; - -/* - * A call-back given to for_each_ref(). Filter refs and keep them for - * later object processing. - */ -static int grab_single_ref(const char *refname, const struct object_id *oid, - int flag, void *cb_data) -{ - struct grab_ref_cbdata *cb = cb_data; - struct refinfo *ref; - int cnt; - - if (flag & REF_BAD_NAME) { - warning("ignoring ref with broken name %s", refname); - return 0; - } - - if (*cb->grab_pattern) { - const char **pattern; - int namelen = strlen(refname); - for (pattern = cb->grab_pattern; *pattern; pattern++) { - const char *p = *pattern; - int plen = strlen(p); - - if ((plen <= namelen) && - !strncmp(refname, p, plen) && - (refname[plen] == '\0' || - refname[plen] == '/' || - p[plen-1] == '/')) - break; - if (!wildmatch(p, refname, WM_PATHNAME, NULL)) - break; - } - if (!*pattern) - return 0; - } - - /* - * We do not open the object yet; sort may only need refname - * to do its job and the resulting list may yet to be pruned - * by maxcount logic. - */ - ref = xcalloc(1, sizeof(*ref)); - ref->refname = xstrdup(refname); - hashcpy(ref->objectname, oid->hash); - ref->flag = flag; - - cnt = cb->grab_cnt; - REALLOC_ARRAY(cb->grab_array, cnt + 1); - cb->grab_array[cnt++] = ref; - cb->grab_cnt = cnt; - return 0; -} - -static int cmp_ref_sort(struct ref_sort *s, struct refinfo *a, struct refinfo *b) -{ - struct atom_value *va, *vb; - int cmp; - cmp_type cmp_type = used_atom_type[s->atom]; - - get_value(a, s->atom, &va); - get_value(b, s->atom, &vb); - switch (cmp_type) { - case FIELD_STR: - cmp = strcmp(va->s, vb->s); - break; - default: - if (va->ul < vb->ul) - cmp = -1; - else if (va->ul == vb->ul) - cmp = 0; - else - cmp = 1; - break; - } - return (s->reverse) ? -cmp : cmp; -} - -static struct ref_sort *ref_sort; -static int compare_refs(const void *a_, const void *b_) -{ - struct refinfo *a = *((struct refinfo **)a_); - struct refinfo *b = *((struct refinfo **)b_); - struct ref_sort *s; - - for (s = ref_sort; s; s = s->next) { - int cmp = cmp_ref_sort(s, a, b); - if (cmp) - return cmp; - } - return 0; -} - -static void sort_refs(struct ref_sort *sort, struct refinfo **refs, int num_refs) -{ - ref_sort = sort; - qsort(refs, num_refs, sizeof(struct refinfo *), compare_refs); -} - -static void print_value(struct atom_value *v, int quote_style) -{ - struct strbuf sb = STRBUF_INIT; - switch (quote_style) { - case QUOTE_NONE: - fputs(v->s, stdout); - break; - case QUOTE_SHELL: - sq_quote_buf(&sb, v->s); - break; - case QUOTE_PERL: - perl_quote_buf(&sb, v->s); - break; - case QUOTE_PYTHON: - python_quote_buf(&sb, v->s); - break; - case QUOTE_TCL: - tcl_quote_buf(&sb, v->s); - break; - } - if (quote_style != QUOTE_NONE) { - fputs(sb.buf, stdout); - strbuf_release(&sb); - } -} - -static int hex1(char ch) -{ - if ('0' <= ch && ch <= '9') - return ch - '0'; - else if ('a' <= ch && ch <= 'f') - return ch - 'a' + 10; - else if ('A' <= ch && ch <= 'F') - return ch - 'A' + 10; - return -1; -} -static int hex2(const char *cp) -{ - if (cp[0] && cp[1]) - return (hex1(cp[0]) << 4) | hex1(cp[1]); - else - return -1; -} - -static void emit(const char *cp, const char *ep) -{ - while (*cp && (!ep || cp < ep)) { - if (*cp == '%') { - if (cp[1] == '%') - cp++; - else { - int ch = hex2(cp + 1); - if (0 <= ch) { - putchar(ch); - cp += 3; - continue; - } - } - } - putchar(*cp); - cp++; - } -} - -static void show_ref(struct refinfo *info, const char *format, int quote_style) -{ - const char *cp, *sp, *ep; - - for (cp = format; *cp && (sp = find_next(cp)); cp = ep + 1) { - struct atom_value *atomv; - - ep = strchr(sp, ')'); - if (cp < sp) - emit(cp, sp); - get_value(info, parse_atom(sp + 2, ep), &atomv); - print_value(atomv, quote_style); - } - if (*cp) { - sp = cp + strlen(cp); - emit(cp, sp); - } - if (need_color_reset_at_eol) { - struct atom_value resetv; - char color[COLOR_MAXLEN] = ""; - - if (color_parse("reset", color) < 0) - die("BUG: couldn't parse 'reset' as a color"); - resetv.s = color; - print_value(&resetv, quote_style); - } - putchar('\n'); -} - -static struct ref_sort *default_sort(void) -{ - static const char cstr_name[] = "refname"; - - struct ref_sort *sort = xcalloc(1, sizeof(*sort)); - - sort->next = NULL; - sort->atom = parse_atom(cstr_name, cstr_name + strlen(cstr_name)); - return sort; -} - -static int opt_parse_sort(const struct option *opt, const char *arg, int unset) -{ - struct ref_sort **sort_tail = opt->value; - struct ref_sort *s; - int len; - - if (!arg) /* should --no-sort void the list ? */ - return -1; - - s = xcalloc(1, sizeof(*s)); - s->next = *sort_tail; - *sort_tail = s; - - if (*arg == '-') { - s->reverse = 1; - arg++; - } - len = strlen(arg); - s->atom = parse_atom(arg, arg+len); - return 0; -} +#include "ref-filter.h" static char const * const for_each_ref_usage[] = { N_("git for-each-ref [<options>] [<pattern>]"), + N_("git for-each-ref [--points-at <object>]"), + N_("git for-each-ref [(--merged | --no-merged) [<object>]]"), + N_("git for-each-ref [--contains [<object>]]"), NULL }; int cmd_for_each_ref(int argc, const char **argv, const char *prefix) { - int i, num_refs; + int i; const char *format = "%(objectname) %(objecttype)\t%(refname)"; - struct ref_sort *sort = NULL, **sort_tail = &sort; + struct ref_sorting *sorting = NULL, **sorting_tail = &sorting; int maxcount = 0, quote_style = 0; - struct refinfo **refs; - struct grab_ref_cbdata cbdata; + struct ref_array array; + struct ref_filter filter; struct option opts[] = { OPT_BIT('s', "shell", "e_style, @@ -1101,11 +35,20 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix) OPT_GROUP(""), OPT_INTEGER( 0 , "count", &maxcount, N_("show only <n> matched refs")), OPT_STRING( 0 , "format", &format, N_("format"), N_("format to use for the output")), - OPT_CALLBACK(0 , "sort", sort_tail, N_("key"), - N_("field name to sort on"), &opt_parse_sort), + OPT_CALLBACK(0 , "sort", sorting_tail, N_("key"), + N_("field name to sort on"), &parse_opt_ref_sorting), + OPT_CALLBACK(0, "points-at", &filter.points_at, + N_("object"), N_("print only refs which points at the given object"), + parse_opt_object_name), + OPT_MERGED(&filter, N_("print only refs that are merged")), + OPT_NO_MERGED(&filter, N_("print only refs that are not merged")), + OPT_CONTAINS(&filter.with_commit, N_("print only refs which contain the commit")), OPT_END(), }; + memset(&array, 0, sizeof(array)); + memset(&filter, 0, sizeof(filter)); + parse_options(argc, argv, prefix, opts, for_each_ref_usage, 0); if (maxcount < 0) { error("invalid --count argument: `%d'", maxcount); @@ -1115,26 +58,24 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix) error("more than one quoting style?"); usage_with_options(for_each_ref_usage, opts); } - if (verify_format(format)) + if (verify_ref_format(format)) usage_with_options(for_each_ref_usage, opts); - if (!sort) - sort = default_sort(); + if (!sorting) + sorting = ref_default_sorting(); /* for warn_ambiguous_refs */ git_config(git_default_config, NULL); - memset(&cbdata, 0, sizeof(cbdata)); - cbdata.grab_pattern = argv; - for_each_rawref(grab_single_ref, &cbdata); - refs = cbdata.grab_array; - num_refs = cbdata.grab_cnt; - - sort_refs(sort, refs, num_refs); + filter.name_patterns = argv; + filter.match_as_path = 1; + filter_refs(&array, &filter, FILTER_REFS_ALL | FILTER_REFS_INCLUDE_BROKEN); + ref_array_sort(sorting, &array); - if (!maxcount || num_refs < maxcount) - maxcount = num_refs; + if (!maxcount || array.nr < maxcount) + maxcount = array.nr; for (i = 0; i < maxcount; i++) - show_ref(refs[i], format, quote_style); + show_ref_array_item(array.items[i], format, quote_style); + ref_array_clear(&array); return 0; } diff --git a/builtin/fsck.c b/builtin/fsck.c index 4e8e2ee5b7..f01b81eebf 100644 --- a/builtin/fsck.c +++ b/builtin/fsck.c @@ -13,6 +13,7 @@ #include "dir.h" #include "progress.h" #include "streaming.h" +#include "decorate.h" #define REACHABLE 0x0001 #define SEEN 0x0002 @@ -23,8 +24,11 @@ static int show_tags; static int show_unreachable; static int include_reflogs = 1; static int check_full = 1; +static int connectivity_only; static int check_strict; static int keep_cache_objects; +static struct fsck_options fsck_walk_options = FSCK_OPTIONS_DEFAULT; +static struct fsck_options fsck_obj_options = FSCK_OPTIONS_DEFAULT; static struct object_id head_oid; static const char *head_points_at; static int errors_found; @@ -32,51 +36,73 @@ static int write_lost_and_found; static int verbose; static int show_progress = -1; static int show_dangling = 1; +static int name_objects; #define ERROR_OBJECT 01 #define ERROR_REACHABLE 02 #define ERROR_PACK 04 +#define ERROR_REFS 010 -#ifdef NO_D_INO_IN_DIRENT -#define SORT_DIRENT 0 -#define DIRENT_SORT_HINT(de) 0 -#else -#define SORT_DIRENT 1 -#define DIRENT_SORT_HINT(de) ((de)->d_ino) -#endif +static const char *describe_object(struct object *obj) +{ + static struct strbuf buf = STRBUF_INIT; + char *name = name_objects ? + lookup_decoration(fsck_walk_options.object_names, obj) : NULL; + + strbuf_reset(&buf); + strbuf_addstr(&buf, oid_to_hex(&obj->oid)); + if (name) + strbuf_addf(&buf, " (%s)", name); + + return buf.buf; +} + +static int fsck_config(const char *var, const char *value, void *cb) +{ + if (strcmp(var, "fsck.skiplist") == 0) { + const char *path; + struct strbuf sb = STRBUF_INIT; + + if (git_config_pathname(&path, var, value)) + return 1; + strbuf_addf(&sb, "skiplist=%s", path); + free((char *)path); + fsck_set_msg_types(&fsck_obj_options, sb.buf); + strbuf_release(&sb); + return 0; + } + + if (skip_prefix(var, "fsck.", &var)) { + fsck_set_msg_type(&fsck_obj_options, var, value); + return 0; + } + + return git_default_config(var, value, cb); +} -static void objreport(struct object *obj, const char *severity, - const char *err, va_list params) +static void objreport(struct object *obj, const char *msg_type, + const char *err) { - fprintf(stderr, "%s in %s %s: ", - severity, typename(obj->type), sha1_to_hex(obj->sha1)); - vfprintf(stderr, err, params); - fputs("\n", stderr); + fprintf(stderr, "%s in %s %s: %s\n", + msg_type, typename(obj->type), describe_object(obj), err); } -__attribute__((format (printf, 2, 3))) -static int objerror(struct object *obj, const char *err, ...) +static int objerror(struct object *obj, const char *err) { - va_list params; - va_start(params, err); errors_found |= ERROR_OBJECT; - objreport(obj, "error", err, params); - va_end(params); + objreport(obj, "error", err); return -1; } -__attribute__((format (printf, 3, 4))) -static int fsck_error_func(struct object *obj, int type, const char *err, ...) +static int fsck_error_func(struct fsck_options *o, + struct object *obj, int type, const char *message) { - va_list params; - va_start(params, err); - objreport(obj, (type == FSCK_WARN) ? "warning" : "error", err, params); - va_end(params); + objreport(obj, (type == FSCK_WARN) ? "warning" : "error", message); return (type == FSCK_WARN) ? 0 : 1; } static struct object_array pending; -static int mark_object(struct object *obj, int type, void *data) +static int mark_object(struct object *obj, int type, void *data, struct fsck_options *options) { struct object *parent = data; @@ -88,7 +114,7 @@ static int mark_object(struct object *obj, int type, void *data) 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)); + typename(parent->type), describe_object(parent)); printf("broken link from %7s %s\n", (type == OBJ_ANY ? "unknown" : typename(type)), "unknown"); errors_found |= ERROR_REACHABLE; @@ -103,11 +129,11 @@ static int mark_object(struct object *obj, int type, void *data) return 0; obj->flags |= REACHABLE; if (!(obj->flags & HAS_OBJ)) { - if (parent && !has_sha1_file(obj->sha1)) { + if (parent && !has_object_file(&obj->oid)) { printf("broken link from %7s %s\n", - typename(parent->type), sha1_to_hex(parent->sha1)); + typename(parent->type), describe_object(parent)); printf(" to %7s %s\n", - typename(obj->type), sha1_to_hex(obj->sha1)); + typename(obj->type), describe_object(obj)); errors_found |= ERROR_REACHABLE; } return 1; @@ -119,7 +145,7 @@ static int mark_object(struct object *obj, int type, void *data) static void mark_object_reachable(struct object *obj) { - mark_object(obj, OBJ_ANY, NULL); + mark_object(obj, OBJ_ANY, NULL, NULL); } static int traverse_one_object(struct object *obj) @@ -132,7 +158,7 @@ static int traverse_one_object(struct object *obj) if (parse_tree(tree) < 0) return 1; /* error already displayed */ } - result = fsck_walk(obj, mark_object, obj); + result = fsck_walk(obj, obj, &fsck_walk_options); if (tree) free_tree_buffer(tree); return result; @@ -158,7 +184,7 @@ static int traverse_reachable(void) return !!result; } -static int mark_used(struct object *obj, int type, void *data) +static int mark_used(struct object *obj, int type, void *data, struct fsck_options *options) { if (!obj) return 1; @@ -177,9 +203,12 @@ static void check_reachable_object(struct object *obj) * do a full fsck */ if (!(obj->flags & HAS_OBJ)) { - if (has_sha1_pack(obj->sha1)) + if (has_sha1_pack(obj->oid.hash)) return; /* it is in pack - forget about it */ - printf("missing %s %s\n", typename(obj->type), sha1_to_hex(obj->sha1)); + if (connectivity_only && has_object_file(&obj->oid)) + return; + printf("missing %s %s\n", typename(obj->type), + describe_object(obj)); errors_found |= ERROR_REACHABLE; return; } @@ -204,7 +233,8 @@ static void check_unreachable_object(struct object *obj) * since this is something that is prunable. */ if (show_unreachable) { - printf("unreachable %s %s\n", typename(obj->type), sha1_to_hex(obj->sha1)); + printf("unreachable %s %s\n", typename(obj->type), + describe_object(obj)); return; } @@ -223,27 +253,29 @@ static void check_unreachable_object(struct object *obj) if (!obj->used) { if (show_dangling) printf("dangling %s %s\n", typename(obj->type), - sha1_to_hex(obj->sha1)); + describe_object(obj)); if (write_lost_and_found) { - const char *filename = git_path("lost-found/%s/%s", + char *filename = git_pathdup("lost-found/%s/%s", obj->type == OBJ_COMMIT ? "commit" : "other", - sha1_to_hex(obj->sha1)); + describe_object(obj)); FILE *f; if (safe_create_leading_directories_const(filename)) { error("Could not create lost-found"); + free(filename); return; } if (!(f = fopen(filename, "w"))) die_errno("Could not open '%s'", filename); if (obj->type == OBJ_BLOB) { - if (stream_blob_to_fd(fileno(f), obj->sha1, NULL, 1)) + if (stream_blob_to_fd(fileno(f), &obj->oid, NULL, 1)) die_errno("Could not write '%s'", filename); } else - fprintf(f, "%s\n", sha1_to_hex(obj->sha1)); + fprintf(f, "%s\n", describe_object(obj)); if (fclose(f)) die_errno("Could not finish '%s'", filename); + free(filename); } return; } @@ -258,7 +290,7 @@ static void check_unreachable_object(struct object *obj) static void check_object(struct object *obj) { if (verbose) - fprintf(stderr, "Checking %s\n", sha1_to_hex(obj->sha1)); + fprintf(stderr, "Checking %s\n", describe_object(obj)); if (obj->flags & REACHABLE) check_reachable_object(obj); @@ -294,11 +326,11 @@ static int fsck_obj(struct object *obj) if (verbose) fprintf(stderr, "Checking %s %s\n", - typename(obj->type), sha1_to_hex(obj->sha1)); + typename(obj->type), describe_object(obj)); - if (fsck_walk(obj, mark_used, NULL)) + if (fsck_walk(obj, NULL, &fsck_obj_options)) objerror(obj, "broken links"); - if (fsck_object(obj, NULL, 0, check_strict, fsck_error_func)) + if (fsck_object(obj, NULL, 0, &fsck_obj_options)) return -1; if (obj->type == OBJ_TREE) { @@ -313,15 +345,17 @@ static int fsck_obj(struct object *obj) free_commit_buffer(commit); if (!commit->parents && show_root) - printf("root %s\n", sha1_to_hex(commit->object.sha1)); + printf("root %s\n", describe_object(&commit->object)); } if (obj->type == OBJ_TAG) { struct tag *tag = (struct tag *) obj; if (show_tags && tag->tagged) { - printf("tagged %s %s", typename(tag->tagged->type), sha1_to_hex(tag->tagged->sha1)); - printf(" (%s) in %s\n", tag->tag, sha1_to_hex(tag->object.sha1)); + printf("tagged %s %s", typename(tag->tagged->type), + describe_object(tag->tagged)); + printf(" (%s) in %s\n", tag->tag, + describe_object(&tag->object)); } } @@ -343,6 +377,10 @@ static int fsck_sha1(const unsigned char *sha1) static int fsck_obj_buffer(const unsigned char *sha1, enum object_type type, unsigned long size, void *buffer, int *eaten) { + /* + * Note, buffer may be NULL if type is OBJ_BLOB. See + * verify_packfile(), data_valid variable for details. + */ struct object *obj; obj = parse_object_buffer(sha1, type, size, buffer, eaten); if (!obj) { @@ -353,133 +391,48 @@ static int fsck_obj_buffer(const unsigned char *sha1, enum object_type type, return fsck_obj(obj); } -/* - * This is the sorting chunk size: make it reasonably - * big so that we can sort well.. - */ -#define MAX_SHA1_ENTRIES (1024) - -struct sha1_entry { - unsigned long ino; - unsigned char sha1[20]; -}; - -static struct { - unsigned long nr; - struct sha1_entry *entry[MAX_SHA1_ENTRIES]; -} sha1_list; - -static int ino_compare(const void *_a, const void *_b) -{ - const struct sha1_entry *a = _a, *b = _b; - unsigned long ino1 = a->ino, ino2 = b->ino; - return ino1 < ino2 ? -1 : ino1 > ino2 ? 1 : 0; -} - -static void fsck_sha1_list(void) -{ - int i, nr = sha1_list.nr; - - if (SORT_DIRENT) - qsort(sha1_list.entry, nr, - sizeof(struct sha1_entry *), ino_compare); - for (i = 0; i < nr; i++) { - struct sha1_entry *entry = sha1_list.entry[i]; - unsigned char *sha1 = entry->sha1; - - sha1_list.entry[i] = NULL; - if (fsck_sha1(sha1)) - errors_found |= ERROR_OBJECT; - free(entry); - } - sha1_list.nr = 0; -} - -static void add_sha1_list(unsigned char *sha1, unsigned long ino) -{ - struct sha1_entry *entry = xmalloc(sizeof(*entry)); - int nr; - - entry->ino = ino; - hashcpy(entry->sha1, sha1); - nr = sha1_list.nr; - if (nr == MAX_SHA1_ENTRIES) { - fsck_sha1_list(); - nr = 0; - } - sha1_list.entry[nr] = entry; - sha1_list.nr = ++nr; -} - -static 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 int default_refs; -static void fsck_dir(int i, char *path) +static void fsck_handle_reflog_sha1(const char *refname, unsigned char *sha1, + unsigned long timestamp) { - DIR *dir = opendir(path); - struct dirent *de; - char name[100]; - - if (!dir) - return; - - if (verbose) - fprintf(stderr, "Checking directory %s\n", path); - - sprintf(name, "%02x", i); - while ((de = readdir(dir)) != NULL) { - unsigned char sha1[20]; + struct object *obj; - if (is_dot_or_dotdot(de->d_name)) - continue; - if (is_loose_object_file(de, name, sha1)) { - add_sha1_list(sha1, DIRENT_SORT_HINT(de)); - continue; + if (!is_null_sha1(sha1)) { + obj = lookup_object(sha1); + if (obj) { + if (timestamp && name_objects) + add_decoration(fsck_walk_options.object_names, + obj, + xstrfmt("%s@{%ld}", refname, timestamp)); + obj->used = 1; + mark_object_reachable(obj); + } else { + error("%s: invalid reflog entry %s", refname, sha1_to_hex(sha1)); + errors_found |= ERROR_REACHABLE; } - if (starts_with(de->d_name, "tmp_obj_")) - continue; - fprintf(stderr, "bad sha1 file: %s/%s\n", path, de->d_name); } - closedir(dir); } -static int default_refs; - static int fsck_handle_reflog_ent(unsigned char *osha1, unsigned char *nsha1, const char *email, unsigned long timestamp, int tz, const char *message, void *cb_data) { - struct object *obj; + const char *refname = cb_data; if (verbose) fprintf(stderr, "Checking reflog %s->%s\n", sha1_to_hex(osha1), sha1_to_hex(nsha1)); - if (!is_null_sha1(osha1)) { - obj = lookup_object(osha1); - if (obj) { - obj->used = 1; - mark_object_reachable(obj); - } - } - obj = lookup_object(nsha1); - if (obj) { - obj->used = 1; - mark_object_reachable(obj); - } + fsck_handle_reflog_sha1(refname, osha1, 0); + fsck_handle_reflog_sha1(refname, nsha1, timestamp); return 0; } static int fsck_handle_reflog(const char *logname, const struct object_id *oid, int flag, void *cb_data) { - for_each_reflog_ent(logname, fsck_handle_reflog_ent, NULL); + for_each_reflog_ent(logname, fsck_handle_reflog_ent, (void *)logname); return 0; } @@ -495,10 +448,15 @@ static int fsck_handle_ref(const char *refname, const struct object_id *oid, /* We'll continue with the rest despite the error.. */ return 0; } - if (obj->type != OBJ_COMMIT && is_branch(refname)) + if (obj->type != OBJ_COMMIT && is_branch(refname)) { error("%s: not a commit", refname); + errors_found |= ERROR_REFS; + } default_refs++; obj->used = 1; + if (name_objects) + add_decoration(fsck_walk_options.object_names, + obj, xstrdup(refname)); mark_object_reachable(obj); return 0; @@ -530,9 +488,28 @@ static void get_default_heads(void) } } +static int fsck_loose(const unsigned char *sha1, const char *path, void *data) +{ + if (fsck_sha1(sha1)) + errors_found |= ERROR_OBJECT; + return 0; +} + +static int fsck_cruft(const char *basename, const char *path, void *data) +{ + if (!starts_with(basename, "tmp_obj_")) + fprintf(stderr, "bad sha1 file: %s\n", path); + return 0; +} + +static int fsck_subdir(int nr, const char *path, void *progress) +{ + display_progress(progress, nr + 1); + return 0; +} + static void fsck_object_dir(const char *path) { - int i; struct progress *progress = NULL; if (verbose) @@ -540,36 +517,38 @@ static void fsck_object_dir(const char *path) if (show_progress) progress = start_progress(_("Checking object directories"), 256); - for (i = 0; i < 256; i++) { - static char dir[4096]; - sprintf(dir, "%s/%02x", path, i); - fsck_dir(i, dir); - display_progress(progress, i+1); - } + + for_each_loose_file_in_objdir(path, fsck_loose, fsck_cruft, fsck_subdir, + progress); + display_progress(progress, 256); stop_progress(&progress); - fsck_sha1_list(); } static int fsck_head_link(void) { - int flag; int null_is_error = 0; if (verbose) fprintf(stderr, "Checking HEAD link\n"); - head_points_at = resolve_ref_unsafe("HEAD", 0, head_oid.hash, &flag); - if (!head_points_at) + head_points_at = resolve_ref_unsafe("HEAD", 0, head_oid.hash, NULL); + if (!head_points_at) { + errors_found |= ERROR_REFS; return error("Invalid HEAD"); + } if (!strcmp(head_points_at, "HEAD")) /* detached HEAD */ null_is_error = 1; - else if (!starts_with(head_points_at, "refs/heads/")) + else if (!starts_with(head_points_at, "refs/heads/")) { + errors_found |= ERROR_REFS; return error("HEAD points to something strange (%s)", head_points_at); + } if (is_null_oid(&head_oid)) { - if (null_is_error) + if (null_is_error) { + errors_found |= ERROR_REFS; return error("HEAD: detached HEAD points at nothing"); + } fprintf(stderr, "notice: HEAD points to an unborn branch (%s)\n", head_points_at + 11); } @@ -589,9 +568,13 @@ static int fsck_cache_tree(struct cache_tree *it) if (!obj) { error("%s: invalid sha1 pointer in cache-tree", sha1_to_hex(it->sha1)); + errors_found |= ERROR_REFS; return 1; } obj->used = 1; + if (name_objects) + add_decoration(fsck_walk_options.object_names, + obj, xstrdup(":")); mark_object_reachable(obj); if (obj->type != OBJ_TREE) err |= objerror(obj, "non-tree in cache-tree"); @@ -615,10 +598,12 @@ static struct option fsck_opts[] = { OPT_BOOL(0, "cache", &keep_cache_objects, N_("make index objects head nodes")), OPT_BOOL(0, "reflogs", &include_reflogs, N_("make reflogs head nodes (default)")), OPT_BOOL(0, "full", &check_full, N_("also consider packs and alternate objects")), + OPT_BOOL(0, "connectivity-only", &connectivity_only, N_("check only connectivity")), OPT_BOOL(0, "strict", &check_strict, N_("enable more strict checking")), OPT_BOOL(0, "lost-found", &write_lost_and_found, N_("write dangling objects in .git/lost-found")), OPT_BOOL(0, "progress", &show_progress, N_("show progress")), + OPT_BOOL(0, "name-objects", &name_objects, N_("show verbose names for reachable objects")), OPT_END(), }; @@ -632,6 +617,12 @@ int cmd_fsck(int argc, const char **argv, const char *prefix) argc = parse_options(argc, argv, prefix, fsck_opts, fsck_usage, 0); + fsck_walk_options.walk = mark_object; + fsck_obj_options.walk = mark_used; + fsck_obj_options.error_func = fsck_error_func; + if (check_strict) + fsck_obj_options.strict = 1; + if (show_progress == -1) show_progress = isatty(2); if (verbose) @@ -642,16 +633,19 @@ int cmd_fsck(int argc, const char **argv, const char *prefix) include_reflogs = 0; } + if (name_objects) + fsck_walk_options.object_names = + xcalloc(1, sizeof(struct decoration)); + + git_config(fsck_config, NULL); + fsck_head_link(); - fsck_object_dir(get_object_directory()); - - prepare_alt_odb(); - for (alt = alt_odb_list; alt; alt = alt->next) { - char namebuf[PATH_MAX]; - int namelen = alt->name - alt->base; - memcpy(namebuf, alt->base, namelen); - namebuf[namelen - 1] = 0; - fsck_object_dir(namebuf); + if (!connectivity_only) { + fsck_object_dir(get_object_directory()); + + prepare_alt_odb(); + for (alt = alt_odb_list; alt; alt = alt->next) + fsck_object_dir(alt->path); } if (check_full) { @@ -692,6 +686,9 @@ int cmd_fsck(int argc, const char **argv, const char *prefix) continue; obj->used = 1; + if (name_objects) + add_decoration(fsck_walk_options.object_names, + obj, xstrdup(arg)); mark_object_reachable(obj); heads++; continue; @@ -719,11 +716,15 @@ int cmd_fsck(int argc, const char **argv, const char *prefix) mode = active_cache[i]->ce_mode; if (S_ISGITLINK(mode)) continue; - blob = lookup_blob(active_cache[i]->sha1); + blob = lookup_blob(active_cache[i]->oid.hash); if (!blob) continue; obj = &blob->object; obj->used = 1; + if (name_objects) + add_decoration(fsck_walk_options.object_names, + obj, + xstrfmt(":%s", active_cache[i]->name)); mark_object_reachable(obj); } if (active_cache_tree) diff --git a/builtin/gc.c b/builtin/gc.c index 36fe33300f..331f219260 100644 --- a/builtin/gc.c +++ b/builtin/gc.c @@ -11,6 +11,7 @@ */ #include "builtin.h" +#include "tempfile.h" #include "lockfile.h" #include "parse-options.h" #include "run-command.h" @@ -27,7 +28,7 @@ static const char * const builtin_gc_usage[] = { static int pack_refs = 1; static int prune_reflogs = 1; -static int aggressive_depth = 250; +static int aggressive_depth = 50; static int aggressive_window = 250; static int gc_auto_threshold = 6700; static int gc_auto_pack_limit = 50; @@ -42,19 +43,23 @@ static struct argv_array prune = ARGV_ARRAY_INIT; static struct argv_array prune_worktrees = ARGV_ARRAY_INIT; static struct argv_array rerere = ARGV_ARRAY_INIT; -static char *pidfile; +static struct tempfile pidfile; +static struct lock_file log_lock; -static void remove_pidfile(void) +static struct string_list pack_garbage = STRING_LIST_INIT_DUP; + +static void clean_pack_garbage(void) { - if (pidfile) - unlink(pidfile); + int i; + for (i = 0; i < pack_garbage.nr; i++) + unlink_or_warn(pack_garbage.items[i].string); + string_list_clear(&pack_garbage, 0); } -static void remove_pidfile_on_signal(int signo) +static void report_pack_garbage(unsigned seen_bits, const char *path) { - remove_pidfile(); - sigchain_pop(signo); - raise(signo); + if (seen_bits == PACKDIR_FILE_IDX) + string_list_append(&pack_garbage, path); } static void git_config_date_string(const char *key, const char **output) @@ -68,6 +73,28 @@ static void git_config_date_string(const char *key, const char **output) } } +static void process_log_file(void) +{ + struct stat st; + if (!fstat(get_lock_file_fd(&log_lock), &st) && st.st_size) + commit_lock_file(&log_lock); + else + rollback_lock_file(&log_lock); +} + +static void process_log_file_at_exit(void) +{ + fflush(stderr); + process_log_file(); +} + +static void process_log_file_on_signal(int signo) +{ + process_log_file(); + sigchain_pop(signo); + raise(signo); +} + static void gc_config(void) { const char *value; @@ -85,7 +112,7 @@ static void gc_config(void) git_config_get_int("gc.autopacklimit", &gc_auto_pack_limit); git_config_get_bool("gc.autodetach", &detach_auto); git_config_date_string("gc.pruneexpire", &prune_expire); - git_config_date_string("gc.pruneworktreesexpire", &prune_worktrees_expire); + git_config_date_string("gc.worktreepruneexpire", &prune_worktrees_expire); git_config(git_default_config, NULL); } @@ -150,7 +177,7 @@ static int too_many_packs(void) */ cnt++; } - return gc_auto_pack_limit <= cnt; + return gc_auto_pack_limit < cnt; } static void add_repack_all_option(void) @@ -164,6 +191,11 @@ static void add_repack_all_option(void) } } +static void add_repack_incremental_option(void) +{ + argv_array_push(&repack, "--no-write-bitmap-index"); +} + static int need_to_gc(void) { /* @@ -181,7 +213,9 @@ static int need_to_gc(void) */ if (too_many_packs()) add_repack_all_option(); - else if (!too_many_loose_objects()) + else if (too_many_loose_objects()) + add_repack_incremental_option(); + else return 0; if (run_hook_le(NULL, "pre-auto-gc", NULL)) @@ -199,20 +233,22 @@ static const char *lock_repo_for_gc(int force, pid_t* ret_pid) uintmax_t pid; FILE *fp; int fd; + char *pidfile_path; - if (pidfile) + if (is_tempfile_active(&pidfile)) /* already locked */ return NULL; if (gethostname(my_host, sizeof(my_host))) - strcpy(my_host, "unknown"); + xsnprintf(my_host, sizeof(my_host), "unknown"); - fd = hold_lock_file_for_update(&lock, git_path("gc.pid"), + pidfile_path = git_pathdup("gc.pid"); + fd = hold_lock_file_for_update(&lock, pidfile_path, LOCK_DIE_ON_ERROR); if (!force) { static char locking_host[128]; int should_exit; - fp = fopen(git_path("gc.pid"), "r"); + fp = fopen(pidfile_path, "r"); memset(locking_host, 0, sizeof(locking_host)); should_exit = fp != NULL && @@ -227,7 +263,7 @@ static const char *lock_repo_for_gc(int force, pid_t* ret_pid) * running. */ time(NULL) - st.st_mtime <= 12 * 3600 && - fscanf(fp, "%"PRIuMAX" %127c", &pid, locking_host) == 2 && + fscanf(fp, "%"SCNuMAX" %127c", &pid, locking_host) == 2 && /* be gentle to concurrent "gc" on remote hosts */ (strcmp(locking_host, my_host) || !kill(pid, 0) || errno == EPERM); if (fp != NULL) @@ -236,6 +272,7 @@ static const char *lock_repo_for_gc(int force, pid_t* ret_pid) if (fd >= 0) rollback_lock_file(&lock); *ret_pid = pid; + free(pidfile_path); return locking_host; } } @@ -245,14 +282,29 @@ static const char *lock_repo_for_gc(int force, pid_t* ret_pid) write_in_full(fd, sb.buf, sb.len); strbuf_release(&sb); commit_lock_file(&lock); - - pidfile = git_pathdup("gc.pid"); - sigchain_push_common(remove_pidfile_on_signal); - atexit(remove_pidfile); - + register_tempfile(&pidfile, pidfile_path); + free(pidfile_path); return NULL; } +static int report_last_gc_error(void) +{ + struct strbuf sb = STRBUF_INIT; + int ret; + + ret = strbuf_read_file(&sb, git_path("gc.log"), 0); + if (ret > 0) + return error(_("The last gc run reported the following. " + "Please correct the root cause\n" + "and remove %s.\n" + "Automatic cleanup will not be performed " + "until the file is removed.\n\n" + "%s"), + git_path("gc.log"), sb.buf); + strbuf_release(&sb); + return 0; +} + static int gc_before_repack(void) { if (pack_refs && run_command_v_opt(pack_refs_cmd.argv, RUN_GIT_CMD)) @@ -274,6 +326,7 @@ int cmd_gc(int argc, const char **argv, const char *prefix) int force = 0; const char *name; pid_t pid; + int daemonized = 0; struct option builtin_gc_options[] = { OPT__QUIET(&quiet, N_("suppress progress reporting")), @@ -293,7 +346,7 @@ int cmd_gc(int argc, const char **argv, const char *prefix) argv_array_pushl(&reflog, "reflog", "expire", "--all", NULL); argv_array_pushl(&repack, "repack", "-d", "-l", NULL); argv_array_pushl(&prune, "prune", "--expire", NULL); - argv_array_pushl(&prune_worktrees, "prune", "--worktrees", "--expire", NULL); + argv_array_pushl(&prune_worktrees, "worktree", "prune", "--expire", NULL); argv_array_pushl(&rerere, "rerere", "gc", NULL); gc_config(); @@ -330,13 +383,16 @@ int cmd_gc(int argc, const char **argv, const char *prefix) fprintf(stderr, _("See \"git help gc\" for manual housekeeping.\n")); } if (detach_auto) { + if (report_last_gc_error()) + return -1; + if (gc_before_repack()) return -1; /* * failure to daemonize is ok, we'll continue * in foreground */ - daemonize(); + daemonized = !daemonize(); } } else add_repack_all_option(); @@ -349,18 +405,29 @@ int cmd_gc(int argc, const char **argv, const char *prefix) name, (uintmax_t)pid); } + if (daemonized) { + hold_lock_file_for_update(&log_lock, + git_path("gc.log"), + LOCK_DIE_ON_ERROR); + dup2(get_lock_file_fd(&log_lock), 2); + sigchain_push_common(process_log_file_on_signal); + atexit(process_log_file_at_exit); + } + if (gc_before_repack()) return -1; - if (run_command_v_opt(repack.argv, RUN_GIT_CMD)) - return error(FAILED_RUN, repack.argv[0]); + if (!repository_format_precious_objects) { + if (run_command_v_opt(repack.argv, RUN_GIT_CMD)) + return error(FAILED_RUN, repack.argv[0]); - if (prune_expire) { - argv_array_push(&prune, prune_expire); - if (quiet) - argv_array_push(&prune, "--no-progress"); - if (run_command_v_opt(prune.argv, RUN_GIT_CMD)) - return error(FAILED_RUN, prune.argv[0]); + if (prune_expire) { + argv_array_push(&prune, prune_expire); + if (quiet) + argv_array_push(&prune, "--no-progress"); + if (run_command_v_opt(prune.argv, RUN_GIT_CMD)) + return error(FAILED_RUN, prune.argv[0]); + } } if (prune_worktrees_expire) { @@ -372,6 +439,11 @@ int cmd_gc(int argc, const char **argv, const char *prefix) if (run_command_v_opt(rerere.argv, RUN_GIT_CMD)) return error(FAILED_RUN, rerere.argv[0]); + report_garbage = report_pack_garbage; + reprepare_packed_git(); + if (pack_garbage.nr > 0) + clean_pack_garbage(); + if (auto_gc && too_many_loose_objects()) warning(_("There are too many unreachable loose objects; " "run 'git prune' to remove them.")); diff --git a/builtin/get-tar-commit-id.c b/builtin/get-tar-commit-id.c index 6f4147ad02..e21c5416cd 100644 --- a/builtin/get-tar-commit-id.c +++ b/builtin/get-tar-commit-id.c @@ -8,7 +8,7 @@ #include "quote.h" static const char builtin_get_tar_commit_id_usage[] = -"git get-tar-commit-id < <tarfile>"; +"git get-tar-commit-id"; /* ustar header + extended global header content */ #define RECORDSIZE (512) diff --git a/builtin/grep.c b/builtin/grep.c index d04f4400d9..8887b6addb 100644 --- a/builtin/grep.c +++ b/builtin/grep.c @@ -24,11 +24,11 @@ static char const * const grep_usage[] = { NULL }; -static int use_threads = 1; +#define GREP_NUM_THREADS_DEFAULT 8 +static int num_threads; #ifndef NO_PTHREADS -#define THREADS 8 -static pthread_t threads[THREADS]; +static pthread_t *threads; /* We use one producer thread and THREADS consumer * threads. The producer adds struct work_items to 'todo' and the @@ -63,13 +63,13 @@ static pthread_mutex_t grep_mutex; static inline void grep_lock(void) { - if (use_threads) + if (num_threads) pthread_mutex_lock(&grep_mutex); } static inline void grep_unlock(void) { - if (use_threads) + if (num_threads) pthread_mutex_unlock(&grep_mutex); } @@ -206,7 +206,8 @@ static void start_threads(struct grep_opt *opt) strbuf_init(&todo[i].out, 0); } - for (i = 0; i < ARRAY_SIZE(threads); i++) { + threads = xcalloc(num_threads, sizeof(*threads)); + for (i = 0; i < num_threads; i++) { int err; struct grep_opt *o = grep_opt_dup(opt); o->output = strbuf_out; @@ -238,12 +239,14 @@ static int wait_all(void) pthread_cond_broadcast(&cond_add); grep_unlock(); - for (i = 0; i < ARRAY_SIZE(threads); i++) { + for (i = 0; i < num_threads; i++) { void *h; pthread_join(threads[i], &h); hit |= (int) (intptr_t) h; } + free(threads); + pthread_mutex_destroy(&grep_mutex); pthread_mutex_destroy(&grep_read_mutex); pthread_mutex_destroy(&grep_attr_mutex); @@ -267,6 +270,14 @@ static int grep_cmd_config(const char *var, const char *value, void *cb) int st = grep_config(var, value, cb); if (git_color_default_config(var, value, cb) < 0) st = -1; + + if (!strcmp(var, "grep.threads")) { + num_threads = git_config_int(var, value); + if (num_threads < 0) + die(_("invalid number of threads specified (%d) for %s"), + num_threads, var); + } + return st; } @@ -294,7 +305,7 @@ static int grep_sha1(struct grep_opt *opt, const unsigned char *sha1, } #ifndef NO_PTHREADS - if (use_threads) { + if (num_threads) { add_work(opt, GREP_SOURCE_SHA1, pathbuf.buf, path, sha1); strbuf_release(&pathbuf); return 0; @@ -323,7 +334,7 @@ static int grep_file(struct grep_opt *opt, const char *filename) strbuf_addstr(&buf, filename); #ifndef NO_PTHREADS - if (use_threads) { + if (num_threads) { add_work(opt, GREP_SOURCE_FILE, buf.buf, filename, filename); strbuf_release(&buf); return 0; @@ -354,17 +365,17 @@ static void append_path(struct grep_opt *opt, const void *data, size_t len) static void run_pager(struct grep_opt *opt, const char *prefix) { struct string_list *path_list = opt->output_priv; - const char **argv = xmalloc(sizeof(const char *) * (path_list->nr + 1)); + struct child_process child = CHILD_PROCESS_INIT; int i, status; for (i = 0; i < path_list->nr; i++) - argv[i] = path_list->items[i].string; - argv[path_list->nr] = NULL; + argv_array_push(&child.args, path_list->items[i].string); + child.dir = prefix; + child.use_shell = 1; - status = run_command_v_opt_cd_env(argv, RUN_USING_SHELL, prefix, NULL); + status = run_command(&child); if (status) exit(status); - free(argv); } static int grep_cache(struct grep_opt *opt, const struct pathspec *pathspec, int cached) @@ -385,9 +396,10 @@ static int grep_cache(struct grep_opt *opt, const struct pathspec *pathspec, int * cache version instead */ if (cached || (ce->ce_flags & CE_VALID) || ce_skip_worktree(ce)) { - if (ce_stage(ce)) + if (ce_stage(ce) || ce_intent_to_add(ce)) continue; - hit |= grep_sha1(opt, ce->sha1, ce->name, 0, ce->name); + hit |= grep_sha1(opt, ce->oid.hash, ce->name, 0, + ce->name); } else hit |= grep_file(opt, ce->name); @@ -427,7 +439,7 @@ static int grep_tree(struct grep_opt *opt, const struct pathspec *pathspec, strbuf_add(base, entry.path, te_len); if (S_ISREG(entry.mode)) { - hit |= grep_sha1(opt, entry.sha1, base->buf, tn_len, + hit |= grep_sha1(opt, entry.oid->hash, base->buf, tn_len, check_attr ? base->buf + tn_len : NULL); } else if (S_ISDIR(entry.mode)) { @@ -436,10 +448,10 @@ static int grep_tree(struct grep_opt *opt, const struct pathspec *pathspec, void *data; unsigned long size; - data = lock_and_read_sha1_file(entry.sha1, &type, &size); + data = lock_and_read_sha1_file(entry.oid->hash, &type, &size); if (!data) die(_("unable to read tree (%s)"), - sha1_to_hex(entry.sha1)); + oid_to_hex(entry.oid)); strbuf_addch(base, '/'); init_tree_desc(&sub, data, size); @@ -459,7 +471,7 @@ static int grep_object(struct grep_opt *opt, const struct pathspec *pathspec, struct object *obj, const char *name, const char *path) { if (obj->type == OBJ_BLOB) - return grep_sha1(opt, obj->sha1, name, 0, path); + return grep_sha1(opt, obj->oid.hash, name, 0, path); if (obj->type == OBJ_COMMIT || obj->type == OBJ_TREE) { struct tree_desc tree; void *data; @@ -468,12 +480,12 @@ static int grep_object(struct grep_opt *opt, const struct pathspec *pathspec, int hit, len; grep_read_lock(); - data = read_object_with_reference(obj->sha1, tree_type, + data = read_object_with_reference(obj->oid.hash, tree_type, &size, NULL); grep_read_unlock(); if (!data) - die(_("unable to read tree (%s)"), sha1_to_hex(obj->sha1)); + die(_("unable to read tree (%s)"), oid_to_hex(&obj->oid)); len = name ? strlen(name) : 0; strbuf_init(&base, PATH_MAX + len + 1); @@ -511,12 +523,14 @@ static int grep_objects(struct grep_opt *opt, const struct pathspec *pathspec, } static int grep_directory(struct grep_opt *opt, const struct pathspec *pathspec, - int exc_std) + int exc_std, int use_index) { struct dir_struct dir; int i, hit = 0; memset(&dir, 0, sizeof(dir)); + if (!use_index) + dir.flags |= DIR_NO_GITLINKS; if (exc_std) setup_standard_excludes(&dir); @@ -562,7 +576,7 @@ static int file_callback(const struct option *opt, const char *arg, int unset) patterns = from_stdin ? stdin : fopen(arg, "r"); if (!patterns) die_errno(_("cannot open '%s'"), arg); - while (strbuf_getline(&sb, patterns, '\n') == 0) { + while (strbuf_getline(&sb, patterns) == 0) { /* ignore empty line like grep does */ if (sb.len == 0) continue; @@ -612,11 +626,6 @@ static int pattern_callback(const struct option *opt, const char *arg, return 0; } -static int help_callback(const struct option *opt, const char *arg, int unset) -{ - return -1; -} - int cmd_grep(int argc, const char **argv, const char *prefix) { int hit = 0; @@ -702,6 +711,8 @@ int cmd_grep(int argc, const char **argv, const char *prefix) N_("show <n> context lines before matches")), OPT_INTEGER('A', "after-context", &opt.post_context, N_("show <n> context lines after matches")), + OPT_INTEGER(0, "threads", &num_threads, + N_("use <n> worker threads")), OPT_NUMBER_CALLBACK(&opt, N_("shortcut for -C NUM"), context_callback), OPT_BOOL('p', "show-function", &opt.funcname, @@ -738,18 +749,9 @@ int cmd_grep(int argc, const char **argv, const char *prefix) PARSE_OPT_OPTARG, NULL, (intptr_t)default_pager }, OPT_BOOL(0, "ext-grep", &external_grep_allowed__ignored, N_("allow calling of grep(1) (ignored by this build)")), - { OPTION_CALLBACK, 0, "help-all", NULL, NULL, N_("show usage"), - PARSE_OPT_HIDDEN | PARSE_OPT_NOARG, help_callback }, OPT_END() }; - /* - * 'git grep -h', unlike 'git grep -h <pattern>', is a request - * to show usage information and exit. - */ - if (argc == 2 && !strcmp(argv[1], "-h")) - usage_with_options(grep_usage, options); - init_grep_defaults(); git_config(grep_cmd_config, NULL); grep_init(&opt, prefix); @@ -766,13 +768,18 @@ int cmd_grep(int argc, const char **argv, const char *prefix) */ argc = parse_options(argc, argv, prefix, options, grep_usage, PARSE_OPT_KEEP_DASHDASH | - PARSE_OPT_STOP_AT_NON_OPTION | - PARSE_OPT_NO_INTERNAL_HELP); + PARSE_OPT_STOP_AT_NON_OPTION); grep_commit_pattern_type(pattern_type_arg, &opt); - if (use_index && !startup_info->have_repository) - /* die the same way as if we did it at the beginning */ - setup_git_directory(); + if (use_index && !startup_info->have_repository) { + int fallback = 0; + git_config_get_bool("grep.fallbacktonoindex", &fallback); + if (fallback) + use_index = 0; + else + /* die the same way as if we did it at the beginning */ + setup_git_directory(); + } /* * skip a -- separator; we know it cannot be @@ -801,7 +808,6 @@ int cmd_grep(int argc, const char **argv, const char *prefix) opt.output_priv = &path_list; opt.output = append_path; string_list_append(&path_list, show_in_pager); - use_threads = 0; } if (!opt.pattern_list) @@ -832,14 +838,18 @@ int cmd_grep(int argc, const char **argv, const char *prefix) } #ifndef NO_PTHREADS - if (list.nr || cached || online_cpus() == 1) - use_threads = 0; + if (list.nr || cached || show_in_pager) + num_threads = 0; + else if (num_threads == 0) + num_threads = GREP_NUM_THREADS_DEFAULT; + else if (num_threads < 0) + die(_("invalid number of threads specified (%d)"), num_threads); #else - use_threads = 0; + num_threads = 0; #endif #ifndef NO_PTHREADS - if (use_threads) { + if (num_threads) { if (!(opt.name_only || opt.unmatch_name_only || opt.count) && (opt.pre_context || opt.post_context || opt.file_break || opt.funcbody)) @@ -895,7 +905,7 @@ int cmd_grep(int argc, const char **argv, const char *prefix) int use_exclude = (opt_exclude < 0) ? use_index : !!opt_exclude; if (list.nr) die(_("--no-index or --untracked cannot be used with revs.")); - hit = grep_directory(&opt, &pathspec, use_exclude); + hit = grep_directory(&opt, &pathspec, use_exclude, use_index); } else if (0 <= opt_exclude) { die(_("--[no-]exclude-standard cannot be used for tracked contents.")); } else if (!list.nr) { @@ -909,7 +919,7 @@ int cmd_grep(int argc, const char **argv, const char *prefix) hit = grep_objects(&opt, &pathspec, &list); } - if (use_threads) + if (num_threads) hit |= wait_all(); if (hit && show_in_pager) run_pager(&opt, prefix); diff --git a/builtin/hash-object.c b/builtin/hash-object.c index 07fef3cc6b..9028e1fdcc 100644 --- a/builtin/hash-object.c +++ b/builtin/hash-object.c @@ -58,27 +58,28 @@ static void hash_object(const char *path, const char *type, const char *vpath, static void hash_stdin_paths(const char *type, int no_filters, unsigned flags, int literally) { - struct strbuf buf = STRBUF_INIT, nbuf = STRBUF_INIT; + struct strbuf buf = STRBUF_INIT; + struct strbuf unquoted = STRBUF_INIT; - while (strbuf_getline(&buf, stdin, '\n') != EOF) { + while (strbuf_getline(&buf, stdin) != EOF) { if (buf.buf[0] == '"') { - strbuf_reset(&nbuf); - if (unquote_c_style(&nbuf, buf.buf, NULL)) + strbuf_reset(&unquoted); + if (unquote_c_style(&unquoted, buf.buf, NULL)) die("line is badly quoted"); - strbuf_swap(&buf, &nbuf); + strbuf_swap(&buf, &unquoted); } hash_object(buf.buf, type, no_filters ? NULL : buf.buf, flags, literally); } strbuf_release(&buf); - strbuf_release(&nbuf); + strbuf_release(&unquoted); } int cmd_hash_object(int argc, const char **argv, const char *prefix) { static const char * const hash_object_usage[] = { N_("git hash-object [-t <type>] [-w] [--path=<file> | --no-filters] [--stdin] [--] <file>..."), - N_("git hash-object --stdin-paths < <list-of-paths>"), + N_("git hash-object --stdin-paths"), NULL }; const char *type = blob_type; @@ -86,6 +87,7 @@ int cmd_hash_object(int argc, const char **argv, const char *prefix) int stdin_paths = 0; int no_filters = 0; int literally = 0; + int nongit = 0; unsigned flags = HASH_FORMAT_CHECK; const char *vpath = NULL; const struct option hash_object_options[] = { @@ -106,12 +108,14 @@ int cmd_hash_object(int argc, const char **argv, const char *prefix) argc = parse_options(argc, argv, NULL, hash_object_options, hash_object_usage, 0); - if (flags & HASH_WRITE_OBJECT) { + if (flags & HASH_WRITE_OBJECT) prefix = setup_git_directory(); - prefix_length = prefix ? strlen(prefix) : 0; - if (vpath && prefix) - vpath = prefix_filename(prefix, prefix_length, vpath); - } + else + prefix = setup_git_directory_gently(&nongit); + + prefix_length = prefix ? strlen(prefix) : 0; + if (vpath && prefix) + vpath = prefix_filename(prefix, prefix_length, vpath); git_config(git_default_config, NULL); diff --git a/builtin/help.c b/builtin/help.c index 3422e73079..49f7a07f85 100644 --- a/builtin/help.c +++ b/builtin/help.c @@ -37,8 +37,10 @@ static int show_all = 0; static int show_guides = 0; static unsigned int colopts; static enum help_format help_format = HELP_FORMAT_NONE; +static int exclude_guides; static struct option builtin_help_options[] = { OPT_BOOL('a', "all", &show_all, N_("print all available commands")), + OPT_HIDDEN_BOOL(0, "exclude-guides", &exclude_guides, N_("exclude guides")), OPT_BOOL('g', "guides", &show_guides, N_("print list of useful guides")), OPT_SET_INT('m', "man", &help_format, N_("show man page"), HELP_FORMAT_MAN), OPT_SET_INT('w', "web", &help_format, N_("show manual in web browser"), @@ -127,7 +129,7 @@ static void exec_woman_emacs(const char *path, const char *page) path = "emacsclient"; strbuf_addf(&man_page, "(woman \"%s\")", page); execlp(path, "emacsclient", "-e", man_page.buf, (char *)NULL); - warning(_("failed to exec '%s': %s"), path, strerror(errno)); + warning_errno(_("failed to exec '%s'"), path); } } @@ -140,22 +142,15 @@ static void exec_man_konqueror(const char *path, const char *page) /* It's simpler to launch konqueror using kfmclient. */ if (path) { - const char *file = strrchr(path, '/'); - if (file && !strcmp(file + 1, "konqueror")) { - char *new = xstrdup(path); - char *dest = strrchr(new, '/'); - - /* strlen("konqueror") == strlen("kfmclient") */ - strcpy(dest + 1, "kfmclient"); - path = new; - } - if (file) - filename = file; + size_t len; + if (strip_suffix(path, "/konqueror", &len)) + path = xstrfmt("%.*s/kfmclient", (int)len, path); + filename = basename((char *)path); } else path = "kfmclient"; strbuf_addf(&man_page, "man:%s(1)", page); execlp(path, filename, "newTab", man_page.buf, (char *)NULL); - warning(_("failed to exec '%s': %s"), path, strerror(errno)); + warning_errno(_("failed to exec '%s'"), path); } } @@ -164,7 +159,7 @@ static void exec_man_man(const char *path, const char *page) if (!path) path = "man"; execlp(path, "man", page, (char *)NULL); - warning(_("failed to exec '%s': %s"), path, strerror(errno)); + warning_errno(_("failed to exec '%s'"), path); } static void exec_man_cmd(const char *cmd, const char *page) @@ -172,18 +167,16 @@ 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(SHELL_PATH, SHELL_PATH, "-c", shell_cmd.buf, (char *)NULL); - warning(_("failed to exec '%s': %s"), cmd, strerror(errno)); + warning(_("failed to exec '%s'"), cmd); } static void add_man_viewer(const char *name) { struct man_viewer_list **p = &man_viewer_list; - size_t len = strlen(name); while (*p) p = &((*p)->next); - *p = xcalloc(1, (sizeof(**p) + len + 1)); - strncpy((*p)->name, name, len); + FLEX_ALLOC_STR(*p, name, name); } static int supported_man_viewer(const char *name, size_t len) @@ -197,9 +190,8 @@ static void do_add_man_viewer_info(const char *name, size_t len, const char *value) { - struct man_viewer_info_list *new = xcalloc(1, sizeof(*new) + len + 1); - - strncpy(new->name, name, len); + struct man_viewer_info_list *new; + FLEX_ALLOC_MEM(new, name, name, len); new->info = xstrdup(value); new->next = man_viewer_info_list; man_viewer_info_list = new; @@ -295,16 +287,6 @@ static int is_git_command(const char *s) is_in_cmdlist(&other_cmds, s); } -static const char *prepend(const char *prefix, const char *cmd) -{ - size_t pre_len = strlen(prefix); - size_t cmd_len = strlen(cmd); - char *p = xmalloc(pre_len + cmd_len + 1); - memcpy(p, prefix, pre_len); - strcpy(p + pre_len, cmd); - return p; -} - static const char *cmd_to_page(const char *git_cmd) { if (!git_cmd) @@ -312,9 +294,9 @@ static const char *cmd_to_page(const char *git_cmd) else if (starts_with(git_cmd, "git")) return git_cmd; else if (is_git_command(git_cmd)) - return prepend("git-", git_cmd); + return xstrfmt("git-%s", git_cmd); else - return prepend("git", git_cmd); + return xstrfmt("git%s", git_cmd); } static void setup_man_path(void) @@ -399,17 +381,10 @@ static void get_html_page_path(struct strbuf *page_path, const char *page) free(to_free); } -/* - * If open_html is not defined in a platform-specific way (see for - * example compat/mingw.h), we use the script web--browse to display - * HTML. - */ -#ifndef open_html static void open_html(const char *path) { execl_git_cmd("web--browse", "-c", "help.browser", path, (char *)NULL); } -#endif static void show_html_page(const char *git_cmd) { @@ -453,10 +428,29 @@ static void list_common_guides_help(void) putchar('\n'); } +static const char *check_git_cmd(const char* cmd) +{ + char *alias; + + if (is_git_command(cmd)) + return cmd; + + alias = alias_lookup(cmd); + if (alias) { + printf_ln(_("`git %s' is aliased to `%s'"), cmd, alias); + free(alias); + exit(0); + } + + if (exclude_guides) + return help_unknown_cmd(cmd); + + return cmd; +} + int cmd_help(int argc, const char **argv, const char *prefix) { int nongit; - char *alias; enum help_format parsed_help_format; argc = parse_options(argc, argv, prefix, builtin_help_options, @@ -496,12 +490,7 @@ int cmd_help(int argc, const char **argv, const char *prefix) if (help_format == HELP_FORMAT_NONE) help_format = parse_help_format(DEFAULT_HELP_FORMAT); - alias = alias_lookup(argv[0]); - if (alias && !is_git_command(argv[0])) { - printf_ln(_("`git %s' is aliased to `%s'"), argv[0], alias); - free(alias); - return 0; - } + argv[0] = check_git_cmd(argv[0]); switch (help_format) { case HELP_FORMAT_NONE: diff --git a/builtin/index-pack.c b/builtin/index-pack.c index 2ecfbae239..f4b87c6c9f 100644 --- a/builtin/index-pack.c +++ b/builtin/index-pack.c @@ -75,7 +75,9 @@ static int nr_threads; static int from_stdin; static int strict; static int do_fsck_object; +static struct fsck_options fsck_options = FSCK_OPTIONS_STRICT; static int verbose; +static int show_resolving_progress; static int show_stat; static int check_self_contained_and_connected; @@ -85,6 +87,7 @@ static struct progress *progress; static unsigned char input_buffer[4096]; static unsigned int input_offset, input_len; static off_t consumed_bytes; +static off_t max_input_size; static unsigned deepest_delta; static git_SHA_CTX input_ctx; static uint32_t input_crc32; @@ -192,13 +195,13 @@ static void cleanup_thread(void) #endif -static int mark_link(struct object *obj, int type, void *data) +static int mark_link(struct object *obj, int type, void *data, struct fsck_options *options) { if (!obj) return -1; if (type != OBJ_ANY && obj->type != type) - die(_("object type mismatch at %s"), sha1_to_hex(obj->sha1)); + die(_("object type mismatch at %s"), oid_to_hex(&obj->oid)); obj->flags |= FLAG_LINK; return 0; @@ -216,13 +219,13 @@ static unsigned check_object(struct object *obj) if (!(obj->flags & FLAG_CHECKED)) { unsigned long size; - int type = sha1_object_info(obj->sha1, &size); + int type = sha1_object_info(obj->oid.hash, &size); if (type <= 0) die(_("did not receive expected object %s"), - sha1_to_hex(obj->sha1)); + oid_to_hex(&obj->oid)); if (type != obj->type) die(_("object %s: expected type %s, found %s"), - sha1_to_hex(obj->sha1), + oid_to_hex(&obj->oid), typename(obj->type), typename(type)); obj->flags |= FLAG_CHECKED; return 1; @@ -295,6 +298,8 @@ static void use(int bytes) if (signed_add_overflows(consumed_bytes, bytes)) die(_("pack too large for current definition of off_t")); consumed_bytes += bytes; + if (max_input_size && consumed_bytes > max_input_size) + die(_("pack exceeds maximum allowed size")); } static const char *open_pack_file(const char *pack_name) @@ -337,10 +342,10 @@ static void parse_pack_header(void) use(sizeof(struct pack_header)); } -static NORETURN void bad_object(unsigned long offset, const char *format, +static NORETURN void bad_object(off_t offset, const char *format, ...) __attribute__((format (printf, 2, 3))); -static NORETURN void bad_object(unsigned long offset, const char *format, ...) +static NORETURN void bad_object(off_t offset, const char *format, ...) { va_list params; char buf[1024]; @@ -348,7 +353,8 @@ static NORETURN void bad_object(unsigned long offset, const char *format, ...) va_start(params, format); vsnprintf(buf, sizeof(buf), format, params); va_end(params); - die(_("pack has bad object at offset %lu: %s"), offset, buf); + die(_("pack has bad object at offset %"PRIuMAX": %s"), + (uintmax_t)offset, buf); } static inline struct thread_local *get_thread_data(void) @@ -428,7 +434,7 @@ static int is_delta_type(enum object_type type) return (type == OBJ_REF_DELTA || type == OBJ_OFS_DELTA); } -static void *unpack_entry_data(unsigned long offset, unsigned long size, +static void *unpack_entry_data(off_t offset, unsigned long size, enum object_type type, unsigned char *sha1) { static char fixed_buf[8192]; @@ -440,7 +446,7 @@ static void *unpack_entry_data(unsigned long offset, unsigned long size, int hdrlen; if (!is_delta_type(type)) { - hdrlen = sprintf(hdr, "%s %lu", typename(type), size) + 1; + hdrlen = xsnprintf(hdr, sizeof(hdr), "%s %lu", typename(type), size) + 1; git_SHA1_Init(&c); git_SHA1_Update(&c, hdr, hdrlen); } else @@ -548,13 +554,13 @@ static void *unpack_data(struct object_entry *obj, void *cb_data) { off_t from = obj[0].idx.offset + obj[0].hdr_size; - unsigned long len = obj[1].idx.offset - from; + off_t len = obj[1].idx.offset - from; unsigned char *data, *inbuf; git_zstream stream; int status; data = xmallocz(consume ? 64*1024 : obj->size); - inbuf = xmalloc((len < 64*1024) ? len : 64*1024); + inbuf = xmalloc((len < 64*1024) ? (int)len : 64*1024); memset(&stream, 0, sizeof(stream)); git_inflate_init(&stream); @@ -562,15 +568,15 @@ static void *unpack_data(struct object_entry *obj, stream.avail_out = consume ? 64*1024 : obj->size; do { - ssize_t n = (len < 64*1024) ? len : 64*1024; + ssize_t n = (len < 64*1024) ? (ssize_t)len : 64*1024; n = xpread(get_thread_data()->pack_fd, inbuf, n, from); if (n < 0) die_errno(_("cannot pread pack file")); if (!n) - die(Q_("premature end of pack file, %lu byte missing", - "premature end of pack file, %lu bytes missing", - len), - len); + die(Q_("premature end of pack file, %"PRIuMAX" byte missing", + "premature end of pack file, %"PRIuMAX" bytes missing", + (unsigned int)len), + (uintmax_t)len); from += n; len -= n; stream.next_in = inbuf; @@ -781,13 +787,15 @@ static void sha1_object(const void *data, struct object_entry *obj_entry, const unsigned char *sha1) { void *new_data = NULL; - int collision_test_needed; + int collision_test_needed = 0; assert(data || obj_entry); - read_lock(); - collision_test_needed = has_sha1_file(sha1); - read_unlock(); + if (startup_info->have_repository) { + read_lock(); + collision_test_needed = has_sha1_file_with_flags(sha1, HAS_SHA1_QUICK); + read_unlock(); + } if (collision_test_needed && !data) { read_lock(); @@ -838,11 +846,10 @@ static void sha1_object(const void *data, struct object_entry *obj_entry, if (!obj) die(_("invalid %s"), typename(type)); if (do_fsck_object && - fsck_object(obj, buf, size, 1, - fsck_error_function)) + fsck_object(obj, buf, size, &fsck_options)) die(_("Error in object")); - if (fsck_walk(obj, mark_link, NULL)) - die(_("Not all child objects of %s are reachable"), sha1_to_hex(obj->sha1)); + if (fsck_walk(obj, NULL, &fsck_options)) + die(_("Not all child objects of %s are reachable"), oid_to_hex(&obj->oid)); if (obj->type == OBJ_TREE) { struct tree *item = (struct tree *) obj; @@ -1185,12 +1192,10 @@ static void resolve_deltas(void) return; /* Sort deltas by base SHA1/offset for fast searching */ - qsort(ofs_deltas, nr_ofs_deltas, sizeof(struct ofs_delta_entry), - compare_ofs_delta_entry); - qsort(ref_deltas, nr_ref_deltas, sizeof(struct ref_delta_entry), - compare_ref_delta_entry); + QSORT(ofs_deltas, nr_ofs_deltas, compare_ofs_delta_entry); + QSORT(ref_deltas, nr_ref_deltas, compare_ref_delta_entry); - if (verbose) + if (verbose || show_resolving_progress) progress = start_progress(_("Resolving deltas"), nr_ref_deltas + nr_ofs_deltas); @@ -1227,7 +1232,7 @@ static void resolve_deltas(void) * - append objects to convert thin pack to full pack if required * - write the final 20-byte SHA-1 */ -static void fix_unresolved_deltas(struct sha1file *f, int nr_unresolved); +static void fix_unresolved_deltas(struct sha1file *f); static void conclude_pack(int fix_thin_pack, const char *curr_pack, unsigned char *pack_sha1) { if (nr_ref_deltas + nr_ofs_deltas == nr_resolved_deltas) { @@ -1249,8 +1254,10 @@ static void conclude_pack(int fix_thin_pack, const char *curr_pack, unsigned cha memset(objects + nr_objects + 1, 0, nr_unresolved * sizeof(*objects)); f = sha1fd(output_fd, curr_pack); - fix_unresolved_deltas(f, nr_unresolved); - strbuf_addf(&msg, _("completed with %d local objects"), + fix_unresolved_deltas(f); + strbuf_addf(&msg, Q_("completed with %d local object", + "completed with %d local objects", + nr_objects - nr_objects_initial), nr_objects - nr_objects_initial); stop_progress_msg(&progress, msg.buf); strbuf_release(&msg); @@ -1331,10 +1338,10 @@ static int delta_pos_compare(const void *_a, const void *_b) return a->obj_no - b->obj_no; } -static void fix_unresolved_deltas(struct sha1file *f, int nr_unresolved) +static void fix_unresolved_deltas(struct sha1file *f) { struct ref_delta_entry **sorted_by_pos; - int i, n = 0; + int i; /* * Since many unresolved deltas may well be themselves base objects @@ -1346,12 +1353,12 @@ static void fix_unresolved_deltas(struct sha1file *f, int nr_unresolved) * before deltas depending on them, a good heuristic is to start * resolving deltas in the same order as their position in the pack. */ - sorted_by_pos = xmalloc(nr_unresolved * sizeof(*sorted_by_pos)); + ALLOC_ARRAY(sorted_by_pos, nr_ref_deltas); for (i = 0; i < nr_ref_deltas; i++) - sorted_by_pos[n++] = &ref_deltas[i]; - qsort(sorted_by_pos, n, sizeof(*sorted_by_pos), delta_pos_compare); + sorted_by_pos[i] = &ref_deltas[i]; + QSORT(sorted_by_pos, nr_ref_deltas, delta_pos_compare); - for (i = 0; i < n; i++) { + for (i = 0; i < nr_ref_deltas; i++) { struct ref_delta_entry *d = sorted_by_pos[i]; enum object_type type; struct base_data *base_obj = alloc_base_data(); @@ -1421,7 +1428,7 @@ static void final(const char *final_pack_name, const char *curr_pack_name, get_object_directory(), sha1_to_hex(sha1)); final_pack_name = name; } - if (move_temp_to_file(curr_pack_name, final_pack_name)) + if (finalize_object_file(curr_pack_name, final_pack_name)) die(_("cannot store pack file")); } else if (from_stdin) chmod(final_pack_name, 0444); @@ -1432,7 +1439,7 @@ static void final(const char *final_pack_name, const char *curr_pack_name, get_object_directory(), sha1_to_hex(sha1)); final_index_name = name; } - if (move_temp_to_file(curr_index_name, final_index_name)) + if (finalize_object_file(curr_index_name, final_index_name)) die(_("cannot store index file")); } else chmod(final_index_name, 0444); @@ -1514,6 +1521,7 @@ static void read_v2_anomalous_offsets(struct packed_git *p, if (!(off & 0x80000000)) continue; off = off & 0x7fffffff; + check_pack_index_ptr(p, &idx2[off * 2]); if (idx2[off * 2]) continue; /* @@ -1525,8 +1533,7 @@ static void read_v2_anomalous_offsets(struct packed_git *p, 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); + QSORT(opts->anomaly, opts->anomaly_nr, cmp_uint32); } static void read_idx_option(struct pack_idx_option *opts, const char *pack_name) @@ -1598,6 +1605,18 @@ static void show_pack_info(int stat_only) } } +static const char *derive_filename(const char *pack_name, const char *suffix, + struct strbuf *buf) +{ + size_t len; + if (!strip_suffix(pack_name, ".pack", &len)) + die(_("packfile name '%s' does not end with '.pack'"), + pack_name); + strbuf_add(buf, pack_name, len); + strbuf_addstr(buf, suffix); + return buf->buf; +} + int cmd_index_pack(int argc, const char **argv, const char *prefix) { int i, fix_thin_pack = 0, verify = 0, stat_only = 0; @@ -1610,11 +1629,13 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix) struct pack_idx_option opts; unsigned char pack_sha1[20]; unsigned foreign_nr = 1; /* zero is a "good" value, assume bad */ + int report_end_of_input = 0; if (argc == 2 && !strcmp(argv[1], "-h")) usage(index_pack_usage); check_replace_refs = 0; + fsck_options.walk = mark_link; reset_pack_idx_option(&opts); git_config(git_index_pack_config, &opts); @@ -1632,6 +1653,10 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix) } else if (!strcmp(arg, "--strict")) { strict = 1; do_fsck_object = 1; + } else if (skip_prefix(arg, "--strict=", &arg)) { + strict = 1; + do_fsck_object = 1; + fsck_set_msg_types(&fsck_options, arg); } else if (!strcmp(arg, "--check-self-contained-and-connected")) { strict = 1; check_self_contained_and_connected = 1; @@ -1674,6 +1699,10 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix) input_len = sizeof(*hdr); } else if (!strcmp(arg, "-v")) { verbose = 1; + } else if (!strcmp(arg, "--show-resolving-progress")) { + show_resolving_progress = 1; + } else if (!strcmp(arg, "--report-end-of-input")) { + report_end_of_input = 1; } else if (!strcmp(arg, "-o")) { if (index_name || (i+1) >= argc) usage(index_pack_usage); @@ -1687,6 +1716,8 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix) opts.off32_limit = strtoul(c+1, &c, 0); if (*c || opts.off32_limit & 0x80000000) die(_("bad %s"), arg); + } else if (skip_prefix(arg, "--max-input-size=", &arg)) { + max_input_size = strtoumax(arg, NULL, 10); } else usage(index_pack_usage); continue; @@ -1701,24 +1732,13 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix) usage(index_pack_usage); if (fix_thin_pack && !from_stdin) die(_("--fix-thin cannot be used without --stdin")); - if (!index_name && pack_name) { - size_t len; - if (!strip_suffix(pack_name, ".pack", &len)) - die(_("packfile name '%s' does not end with '.pack'"), - pack_name); - strbuf_add(&index_name_buf, pack_name, len); - strbuf_addstr(&index_name_buf, ".idx"); - index_name = index_name_buf.buf; - } - if (keep_msg && !keep_name && pack_name) { - size_t len; - if (!strip_suffix(pack_name, ".pack", &len)) - die(_("packfile name '%s' does not end with '.pack'"), - pack_name); - strbuf_add(&keep_name_buf, pack_name, len); - strbuf_addstr(&keep_name_buf, ".idx"); - keep_name = keep_name_buf.buf; - } + if (from_stdin && !startup_info->have_repository) + die(_("--stdin requires a git repository")); + if (!index_name && pack_name) + index_name = derive_filename(pack_name, ".idx", &index_name_buf); + if (keep_msg && !keep_name && pack_name) + keep_name = derive_filename(pack_name, ".keep", &keep_name_buf); + if (verify) { if (!index_name) die(_("--verify with no packfile name given")); @@ -1739,11 +1759,13 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix) curr_pack = open_pack_file(pack_name); parse_pack_header(); - objects = xcalloc(nr_objects + 1, sizeof(struct object_entry)); + objects = xcalloc(st_add(nr_objects, 1), sizeof(struct object_entry)); if (show_stat) - obj_stat = xcalloc(nr_objects + 1, sizeof(struct object_stat)); + obj_stat = xcalloc(st_add(nr_objects, 1), sizeof(struct object_stat)); ofs_deltas = xcalloc(nr_objects, sizeof(struct ofs_delta_entry)); parse_pack_objects(pack_sha1); + if (report_end_of_input) + write_in_full(2, "\0", 1); resolve_deltas(); conclude_pack(fix_thin_pack, curr_pack, pack_sha1); free(ofs_deltas); @@ -1754,7 +1776,7 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix) if (show_stat) show_pack_info(stat_only); - idx_objects = xmalloc((nr_objects) * sizeof(struct pack_idx_entry *)); + ALLOC_ARRAY(idx_objects, nr_objects); for (i = 0; i < nr_objects; i++) idx_objects[i] = &objects[i].idx; curr_index = write_idx_file(index_name, idx_objects, nr_objects, &opts, pack_sha1); diff --git a/builtin/init-db.c b/builtin/init-db.c index 4335738135..2399b97d90 100644 --- a/builtin/init-db.c +++ b/builtin/init-db.c @@ -4,6 +4,7 @@ * Copyright (C) Linus Torvalds, 2005 */ #include "cache.h" +#include "refs.h" #include "builtin.h" #include "exec_cmd.h" #include "parse-options.h" @@ -21,24 +22,12 @@ 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) -{ - if (mkdir(dir, 0777) < 0) { - if (errno != EEXIST) { - perror(dir); - exit(1); - } - } - else if (share && adjust_shared_perm(dir)) - die(_("Could not make %s writable by group"), dir); -} - -static void copy_templates_1(char *path, int baselen, - char *template, int template_baselen, +static void copy_templates_1(struct strbuf *path, struct strbuf *template, DIR *dir) { + size_t path_baselen = path->len; + size_t template_baselen = template->len; struct dirent *de; /* Note: if ".git/hooks" file exists in the repository being @@ -48,77 +37,66 @@ static void copy_templates_1(char *path, int baselen, * with the way the namespace under .git/ is organized, should * be really carefully chosen. */ - safe_create_dir(path, 1); + safe_create_dir(path->buf, 1); while ((de = readdir(dir)) != NULL) { struct stat st_git, st_template; - int namelen; int exists = 0; + strbuf_setlen(path, path_baselen); + strbuf_setlen(template, template_baselen); + if (de->d_name[0] == '.') continue; - namelen = strlen(de->d_name); - if ((PATH_MAX <= baselen + namelen) || - (PATH_MAX <= template_baselen + namelen)) - 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)) { + strbuf_addstr(path, de->d_name); + strbuf_addstr(template, de->d_name); + if (lstat(path->buf, &st_git)) { if (errno != ENOENT) - die_errno(_("cannot stat '%s'"), path); + die_errno(_("cannot stat '%s'"), path->buf); } else exists = 1; - if (lstat(template, &st_template)) - die_errno(_("cannot stat template '%s'"), template); + if (lstat(template->buf, &st_template)) + die_errno(_("cannot stat template '%s'"), template->buf); if (S_ISDIR(st_template.st_mode)) { - DIR *subdir = opendir(template); - int baselen_sub = baselen + namelen; - int template_baselen_sub = template_baselen + namelen; + DIR *subdir = opendir(template->buf); if (!subdir) - die_errno(_("cannot opendir '%s'"), template); - path[baselen_sub++] = - template[template_baselen_sub++] = '/'; - path[baselen_sub] = - template[template_baselen_sub] = 0; - copy_templates_1(path, baselen_sub, - template, template_baselen_sub, - subdir); + die_errno(_("cannot opendir '%s'"), template->buf); + strbuf_addch(path, '/'); + strbuf_addch(template, '/'); + copy_templates_1(path, template, subdir); closedir(subdir); } else if (exists) continue; else if (S_ISLNK(st_template.st_mode)) { - char lnk[256]; - int len; - len = readlink(template, lnk, sizeof(lnk)); - if (len < 0) - die_errno(_("cannot readlink '%s'"), template); - if (sizeof(lnk) <= len) - die(_("insanely long symlink %s"), template); - lnk[len] = 0; - if (symlink(lnk, path)) - die_errno(_("cannot symlink '%s' '%s'"), lnk, path); + struct strbuf lnk = STRBUF_INIT; + if (strbuf_readlink(&lnk, template->buf, 0) < 0) + die_errno(_("cannot readlink '%s'"), template->buf); + if (symlink(lnk.buf, path->buf)) + die_errno(_("cannot symlink '%s' '%s'"), + lnk.buf, path->buf); + strbuf_release(&lnk); } 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, - path); + if (copy_file(path->buf, template->buf, st_template.st_mode)) + die_errno(_("cannot copy '%s' to '%s'"), + template->buf, path->buf); } else - error(_("ignoring template %s"), template); + error(_("ignoring template %s"), template->buf); } } static void copy_templates(const char *template_dir) { - char path[PATH_MAX]; - char template_path[PATH_MAX]; - int template_len; + struct strbuf path = STRBUF_INIT; + struct strbuf template_path = STRBUF_INIT; + size_t template_len; + struct repository_format template_format; + struct strbuf err = STRBUF_INIT; DIR *dir; - const char *git_dir = get_git_dir(); - int len = strlen(git_dir); char *to_free = NULL; if (!template_dir) @@ -131,47 +109,43 @@ static void copy_templates(const char *template_dir) free(to_free); return; } - template_len = strlen(template_dir); - if (PATH_MAX <= (template_len+strlen("/config"))) - die(_("insanely long template path %s"), template_dir); - strcpy(template_path, template_dir); - if (template_path[template_len-1] != '/') { - template_path[template_len++] = '/'; - template_path[template_len] = 0; - } - dir = opendir(template_path); + + strbuf_addstr(&template_path, template_dir); + strbuf_complete(&template_path, '/'); + template_len = template_path.len; + + dir = opendir(template_path.buf); if (!dir) { warning(_("templates not found %s"), template_dir); goto free_return; } /* Make sure that template is from the correct vintage */ - strcpy(template_path + template_len, "config"); - repository_format_version = 0; - git_config_from_file(check_repository_format_version, - template_path, NULL); - template_path[template_len] = 0; - - if (repository_format_version && - repository_format_version != GIT_REPO_VERSION) { - warning(_("not copying templates of " - "a wrong format version %d from '%s'"), - repository_format_version, - template_dir); + strbuf_addstr(&template_path, "config"); + read_repository_format(&template_format, template_path.buf); + strbuf_setlen(&template_path, template_len); + + /* + * No mention of version at all is OK, but anything else should be + * verified. + */ + if (template_format.version >= 0 && + verify_repository_format(&template_format, &err) < 0) { + warning(_("not copying templates from '%s': %s"), + template_dir, err.buf); + strbuf_release(&err); goto close_free_return; } - memcpy(path, git_dir, len); - if (len && path[len - 1] != '/') - path[len++] = '/'; - path[len] = 0; - copy_templates_1(path, len, - template_path, template_len, - dir); + strbuf_addstr(&path, get_git_common_dir()); + strbuf_complete(&path, '/'); + copy_templates_1(&path, &template_path, dir); close_free_return: closedir(dir); free_return: free(to_free); + strbuf_release(&path); + strbuf_release(&template_path); } static int git_init_db_config(const char *k, const char *v, void *cb) @@ -196,63 +170,66 @@ static int needs_work_tree_config(const char *git_dir, const char *work_tree) return 1; } -static int create_default_files(const char *template_path) +static int create_default_files(const char *template_path, + const char *original_git_dir) { - const char *git_dir = get_git_dir(); - unsigned len = strlen(git_dir); - static char path[PATH_MAX]; struct stat st1; + struct strbuf buf = STRBUF_INIT; + char *path; char repo_version_string[10]; char junk[2]; int reinit; int filemode; - - if (len > sizeof(path)-50) - die(_("insane git directory %s"), git_dir); - memcpy(path, git_dir, len); - - if (len && path[len-1] != '/') - path[len++] = '/'; - - /* - * Create .git/refs/{heads,tags} - */ - safe_create_dir(git_path("refs"), 1); - safe_create_dir(git_path("refs/heads"), 1); - safe_create_dir(git_path("refs/tags"), 1); + struct strbuf err = STRBUF_INIT; /* Just look for `init.templatedir` */ git_config(git_init_db_config, NULL); - /* First copy the templates -- we might have the default + /* + * First copy the templates -- we might have the default * config file there, in which case we would want to read * from it after installing. + * + * Before reading that config, we also need to clear out any cached + * values (since we've just potentially changed what's available on + * disk). */ copy_templates(template_path); - + git_config_clear(); + reset_shared_repository(); git_config(git_default_config, NULL); - is_bare_repository_cfg = init_is_bare_repository; - /* reading existing config may have overwrote it */ + /* + * We must make sure command-line options continue to override any + * values we might have just re-read from the config. + */ + is_bare_repository_cfg = init_is_bare_repository; if (init_shared_repository != -1) - shared_repository = init_shared_repository; + set_shared_repository(init_shared_repository); /* * We would have created the above under user's umask -- under * shared-repository settings, we would need to fix them up. */ - if (shared_repository) { + if (get_shared_repository()) { adjust_shared_perm(get_git_dir()); - adjust_shared_perm(git_path("refs")); - adjust_shared_perm(git_path("refs/heads")); - adjust_shared_perm(git_path("refs/tags")); } /* + * We need to create a "refs" dir in any case so that older + * versions of git can tell that this is a repository. + */ + safe_create_dir(git_path("refs"), 1); + adjust_shared_perm(git_path("refs")); + + if (refs_init_db(&err)) + die("failed to set up refs db: %s", err.buf); + + /* * Create the default symlink from ".git/HEAD" to the "master" * branch, if it does not exist yet. */ - strcpy(path + len, "HEAD"); + path = git_path_buf(&buf, "HEAD"); reinit = (!access(path, R_OK) || readlink(path, junk, sizeof(junk)-1) != -1); if (!reinit) { @@ -261,13 +238,12 @@ static int create_default_files(const char *template_path) } /* This forces creation of new config file */ - sprintf(repo_version_string, "%d", GIT_REPO_VERSION); + xsnprintf(repo_version_string, sizeof(repo_version_string), + "%d", GIT_REPO_VERSION); git_config_set("core.repositoryformatversion", repo_version_string); - path[len] = 0; - strcpy(path + len, "config"); - /* Check filemode trustability */ + path = git_path_buf(&buf, "config"); filemode = TEST_FILEMODE; if (TEST_FILEMODE && !lstat(path, &st1)) { struct stat st2; @@ -287,15 +263,14 @@ static int create_default_files(const char *template_path) git_config_set("core.bare", "false"); /* allow template config file to override the default */ if (log_all_ref_updates == -1) - git_config_set("core.logallrefupdates", "true"); - if (needs_work_tree_config(git_dir, work_tree)) + git_config_set("core.logallrefupdates", "true"); + if (needs_work_tree_config(original_git_dir, work_tree)) git_config_set("core.worktree", work_tree); } if (!reinit) { /* Check if symlink is supported in the work tree */ - path[len] = 0; - strcpy(path + len, "tXXXXXX"); + path = git_path_buf(&buf, "tXXXXXX"); if (!close(xmkstemp(path)) && !unlink(path) && !symlink("testing", path) && @@ -306,60 +281,38 @@ static int create_default_files(const char *template_path) git_config_set("core.symlinks", "false"); /* Check if the filesystem is case-insensitive */ - path[len] = 0; - strcpy(path + len, "CoNfIg"); + path = git_path_buf(&buf, "CoNfIg"); if (!access(path, F_OK)) git_config_set("core.ignorecase", "true"); - probe_utf8_pathname_composition(path, len); + probe_utf8_pathname_composition(); } + strbuf_release(&buf); 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); + struct strbuf path = STRBUF_INIT; + size_t baselen; - safe_create_dir(object_directory, 1); - strcpy(path+len, "/pack"); - safe_create_dir(path, 1); - strcpy(path+len, "/info"); - safe_create_dir(path, 1); + strbuf_addstr(&path, get_object_directory()); + baselen = path.len; - 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; + safe_create_dir(path.buf, 1); - if (!exist_ok && !stat(git_dir, &st)) - die(_("%s already exists"), git_dir); + strbuf_setlen(&path, baselen); + strbuf_addstr(&path, "/pack"); + safe_create_dir(path.buf, 1); - if (!exist_ok && !stat(real_git_dir, &st)) - die(_("%s already exists"), real_git_dir); + strbuf_setlen(&path, baselen); + strbuf_addstr(&path, "/info"); + safe_create_dir(path.buf, 1); - /* - * 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)); - set_git_dir(real_path(real_git_dir)); - } - else { - set_git_dir(real_path(git_dir)); - git_link = NULL; - } - return 0; + strbuf_release(&path); } -static void separate_git_dir(const char *git_dir) +static void separate_git_dir(const char *git_dir, const char *git_link) { struct stat st; @@ -377,16 +330,34 @@ static void separate_git_dir(const char *git_dir) die_errno(_("unable to move %s to %s"), src, git_dir); } - write_file(git_link, 1, "gitdir: %s\n", git_dir); + write_file(git_link, "gitdir: %s", git_dir); } -int init_db(const char *template_dir, unsigned int flags) +int init_db(const char *git_dir, const char *real_git_dir, + const char *template_dir, unsigned int flags) { int reinit; - const char *git_dir = get_git_dir(); + int exist_ok = flags & INIT_DB_EXIST_OK; + char *original_git_dir = xstrdup(real_path(git_dir)); - if (git_link) - separate_git_dir(git_dir); + 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); + + set_git_dir(real_path(real_git_dir)); + git_dir = get_git_dir(); + separate_git_dir(git_dir, original_git_dir); + } + else { + set_git_dir(real_path(git_dir)); + git_dir = get_git_dir(); + } + startup_info->have_repository = 1; safe_create_dir(git_dir, 0); @@ -399,11 +370,11 @@ int init_db(const char *template_dir, unsigned int flags) */ check_repository_format(); - reinit = create_default_files(template_dir); + reinit = create_default_files(template_dir, original_git_dir); create_object_directory(); - if (shared_repository) { + if (get_shared_repository()) { char buf[10]; /* We do not spell "group" and such, so that * the configuration can be read by older version @@ -411,15 +382,15 @@ int init_db(const char *template_dir, unsigned int flags) * and compatibility values for PERM_GROUP and * PERM_EVERYBODY. */ - if (shared_repository < 0) + if (get_shared_repository() < 0) /* force to the mode value */ - sprintf(buf, "0%o", -shared_repository); - else if (shared_repository == PERM_GROUP) - sprintf(buf, "%d", OLD_PERM_GROUP); - else if (shared_repository == PERM_EVERYBODY) - sprintf(buf, "%d", OLD_PERM_EVERYBODY); + xsnprintf(buf, sizeof(buf), "0%o", -get_shared_repository()); + else if (get_shared_repository() == PERM_GROUP) + xsnprintf(buf, sizeof(buf), "%d", OLD_PERM_GROUP); + else if (get_shared_repository() == PERM_EVERYBODY) + xsnprintf(buf, sizeof(buf), "%d", OLD_PERM_EVERYBODY); else - die("oops"); + die("BUG: invalid value for shared_repository"); git_config_set("core.sharedrepository", buf); git_config_set("receive.denyNonFastforwards", "true"); } @@ -427,15 +398,19 @@ int init_db(const char *template_dir, unsigned int flags) if (!(flags & INIT_DB_QUIET)) { int len = strlen(git_dir); - /* 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] != '/' ? "/" : ""); + if (reinit) + printf(get_shared_repository() + ? _("Reinitialized existing shared Git repository in %s%s\n") + : _("Reinitialized existing Git repository in %s%s\n"), + git_dir, len && git_dir[len-1] != '/' ? "/" : ""); + else + printf(get_shared_repository() + ? _("Initialized empty shared Git repository in %s%s\n") + : _("Initialized empty Git repository in %s%s\n"), + git_dir, len && git_dir[len-1] != '/' ? "/" : ""); } + free(original_git_dir); return 0; } @@ -527,8 +502,8 @@ int cmd_init_db(int argc, const char **argv, const char *prefix) * and we know shared_repository should always be 0; * but just in case we play safe. */ - saved = shared_repository; - shared_repository = 0; + saved = get_shared_repository(); + set_shared_repository(0); switch (safe_create_leading_directories_const(argv[0])) { case SCLD_OK: case SCLD_PERMS: @@ -540,7 +515,7 @@ int cmd_init_db(int argc, const char **argv, const char *prefix) die_errno(_("cannot mkdir %s"), argv[0]); break; } - shared_repository = saved; + set_shared_repository(saved); if (mkdir(argv[0], 0777) < 0) die_errno(_("cannot mkdir %s"), argv[0]); mkdir_tried = 1; @@ -558,7 +533,7 @@ int cmd_init_db(int argc, const char **argv, const char *prefix) } if (init_shared_repository != -1) - shared_repository = init_shared_repository; + set_shared_repository(init_shared_repository); /* * GIT_WORK_TREE makes sense only in conjunction with GIT_DIR @@ -603,7 +578,6 @@ int cmd_init_db(int argc, const char **argv, const char *prefix) set_git_work_tree(work_tree); } - set_git_dir_init(git_dir, real_git_dir, 1); - - return init_db(template_dir, flags); + flags |= INIT_DB_EXIST_OK; + return init_db(git_dir, real_git_dir, template_dir, flags); } diff --git a/builtin/interpret-trailers.c b/builtin/interpret-trailers.c index 46838d24a9..175f14797b 100644 --- a/builtin/interpret-trailers.c +++ b/builtin/interpret-trailers.c @@ -12,16 +12,18 @@ #include "trailer.h" static const char * const git_interpret_trailers_usage[] = { - N_("git interpret-trailers [--trim-empty] [(--trailer <token>[(=|:)<value>])...] [<file>...]"), + N_("git interpret-trailers [--in-place] [--trim-empty] [(--trailer <token>[(=|:)<value>])...] [<file>...]"), NULL }; int cmd_interpret_trailers(int argc, const char **argv, const char *prefix) { + int in_place = 0; int trim_empty = 0; - struct string_list trailers = STRING_LIST_INIT_DUP; + struct string_list trailers = STRING_LIST_INIT_NODUP; struct option options[] = { + OPT_BOOL(0, "in-place", &in_place, N_("edit files in place")), OPT_BOOL(0, "trim-empty", &trim_empty, N_("trim empty trailers")), OPT_STRING_LIST(0, "trailer", &trailers, N_("trailer"), N_("trailer(s) to add")), @@ -34,9 +36,12 @@ int cmd_interpret_trailers(int argc, const char **argv, const char *prefix) if (argc) { int i; for (i = 0; i < argc; i++) - process_trailers(argv[i], trim_empty, &trailers); - } else - process_trailers(NULL, trim_empty, &trailers); + process_trailers(argv[i], in_place, trim_empty, &trailers); + } else { + if (in_place) + die(_("no input file given for in-place editing")); + process_trailers(NULL, in_place, trim_empty, &trailers); + } string_list_clear(&trailers, 0); diff --git a/builtin/log.c b/builtin/log.c index e67671e37a..55d20cc2d8 100644 --- a/builtin/log.c +++ b/builtin/log.c @@ -5,6 +5,7 @@ * 2006 Junio Hamano */ #include "cache.h" +#include "refs.h" #include "color.h" #include "commit.h" #include "diff.h" @@ -31,6 +32,8 @@ static const char *default_date_mode = NULL; static int default_abbrev_commit; static int default_show_root = 1; +static int default_follow; +static int default_show_signature; static int decoration_style; static int decoration_given; static int use_mailmap_config; @@ -98,10 +101,18 @@ static int log_line_range_callback(const struct option *option, const char *arg, return 0; } +static void init_log_defaults(void) +{ + init_grep_defaults(); + init_diff_ui_defaults(); +} + static void cmd_log_init_defaults(struct rev_info *rev) { if (fmt_pretty) get_commit_format(fmt_pretty, rev); + if (default_follow) + DIFF_OPT_SET(&rev->diffopt, DEFAULT_FOLLOW_RENAMES); rev->verbose_header = 1; DIFF_OPT_SET(&rev->diffopt, RECURSIVE); rev->diffopt.stat_width = -1; /* use full terminal width */ @@ -109,10 +120,11 @@ static void cmd_log_init_defaults(struct rev_info *rev) rev->abbrev_commit = default_abbrev_commit; rev->show_root_diff = default_show_root; rev->subject_prefix = fmt_patch_subject_prefix; + rev->show_signature = default_show_signature; DIFF_OPT_SET(&rev->diffopt, ALLOW_TEXTCONV); if (default_date_mode) - rev->date_mode = parse_date_format(default_date_mode); + parse_date_format(default_date_mode, &rev->date_mode); rev->diffopt.touched_flags = 0; } @@ -226,16 +238,17 @@ 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); + fprintf(rev->diffopt.file, _("Final output: %d %s\n"), nr, stage); } static struct itimerval early_output_timer; static void log_show_early(struct rev_info *revs, struct commit_list *list) { - int i = revs->early_output; + int i = revs->early_output, close_file = revs->diffopt.close_file; int show_header = 1; + revs->diffopt.close_file = 0; sort_in_topological_order(&list, revs->sort_order); while (list && i) { struct commit *commit = list->item; @@ -252,14 +265,19 @@ static void log_show_early(struct rev_info *revs, struct commit_list *list) case commit_ignore: break; case commit_error: + if (close_file) + fclose(revs->diffopt.file); return; } list = list->next; } /* Did we already get enough commits for the early output? */ - if (!i) + if (!i) { + if (close_file) + fclose(revs->diffopt.file); return; + } /* * ..if no, then repeat it twice a second until we @@ -321,7 +339,7 @@ static int cmd_log_walk(struct rev_info *rev) { struct commit *commit; int saved_nrl = 0; - int saved_dcctc = 0; + int saved_dcctc = 0, close_file = rev->diffopt.close_file; if (rev->early_output) setup_early_output(rev); @@ -337,9 +355,9 @@ static int cmd_log_walk(struct rev_info *rev) * and HAS_CHANGES being accumulated in rev->diffopt, so be careful to * retain that state information if replacing rev->diffopt in this loop */ + rev->diffopt.close_file = 0; while ((commit = get_revision(rev)) != NULL) { - if (!log_tree_commit(rev, commit) && - rev->max_count >= 0) + 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. @@ -358,6 +376,8 @@ static int cmd_log_walk(struct rev_info *rev) } rev->diffopt.degraded_cc_to_c = saved_dcctc; rev->diffopt.needed_rename_limit = saved_nrl; + if (close_file) + fclose(rev->diffopt.file); if (rev->diffopt.output_format & DIFF_FORMAT_CHECKDIFF && DIFF_OPT_TST(&rev->diffopt, CHECK_FAILED)) { @@ -390,12 +410,20 @@ static int git_log_config(const char *var, const char *value, void *cb) default_show_root = git_config_bool(var, value); return 0; } + if (!strcmp(var, "log.follow")) { + default_follow = git_config_bool(var, value); + return 0; + } if (skip_prefix(var, "color.decorate.", &slot_name)) return parse_decorate_color_config(var, slot_name, value); if (!strcmp(var, "log.mailmap")) { use_mailmap_config = git_config_bool(var, value); return 0; } + if (!strcmp(var, "log.showsignature")) { + default_show_signature = git_config_bool(var, value); + return 0; + } if (grep_config(var, value, cb) < 0) return -1; @@ -409,7 +437,7 @@ int cmd_whatchanged(int argc, const char **argv, const char *prefix) struct rev_info rev; struct setup_revision_opt opt; - init_grep_defaults(); + init_log_defaults(); git_config(git_log_config, NULL); init_revisions(&rev, prefix); @@ -432,27 +460,27 @@ static void show_tagger(char *buf, int len, struct rev_info *rev) 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); + fprintf(rev->diffopt.file, "%s", out.buf); strbuf_release(&out); } -static int show_blob_object(const unsigned char *sha1, struct rev_info *rev, const char *obj_name) +static int show_blob_object(const struct object_id *oid, struct rev_info *rev, const char *obj_name) { - unsigned char sha1c[20]; + struct object_id oidc; struct object_context obj_context; char *buf; unsigned long size; - fflush(stdout); + fflush(rev->diffopt.file); if (!DIFF_OPT_TOUCHED(&rev->diffopt, ALLOW_TEXTCONV) || !DIFF_OPT_TST(&rev->diffopt, ALLOW_TEXTCONV)) - return stream_blob_to_fd(1, sha1, NULL, 0); + return stream_blob_to_fd(1, oid, NULL, 0); - if (get_sha1_with_context(obj_name, 0, sha1c, &obj_context)) + if (get_sha1_with_context(obj_name, 0, oidc.hash, &obj_context)) die(_("Not a valid object name %s"), obj_name); if (!obj_context.path[0] || - !textconv_object(obj_context.path, obj_context.mode, sha1c, 1, &buf, &size)) - return stream_blob_to_fd(1, sha1, NULL, 0); + !textconv_object(obj_context.path, obj_context.mode, &oidc, 1, &buf, &size)) + return stream_blob_to_fd(1, oid, NULL, 0); if (!buf) die(_("git show %s: bad file"), obj_name); @@ -461,15 +489,15 @@ static int show_blob_object(const unsigned char *sha1, struct rev_info *rev, con return 0; } -static int show_tag_object(const unsigned char *sha1, struct rev_info *rev) +static int show_tag_object(const struct object_id *oid, struct rev_info *rev) { unsigned long size; enum object_type type; - char *buf = read_sha1_file(sha1, &type, &size); + char *buf = read_sha1_file(oid->hash, &type, &size); int offset = 0; if (!buf) - return error(_("Could not read object %s"), sha1_to_hex(sha1)); + return error(_("Could not read object %s"), oid_to_hex(oid)); assert(type == OBJ_TAG); while (offset < size && buf[offset] != '\n') { @@ -483,7 +511,7 @@ static int show_tag_object(const unsigned char *sha1, struct rev_info *rev) } if (offset < size) - fwrite(buf + offset, size - offset, 1, stdout); + fwrite(buf + offset, size - offset, 1, rev->diffopt.file); free(buf); return 0; } @@ -492,11 +520,13 @@ static int show_tree_object(const unsigned char *sha1, struct strbuf *base, const char *pathname, unsigned mode, int stage, void *context) { - printf("%s%s\n", pathname, S_ISDIR(mode) ? "/" : ""); + FILE *file = context; + fprintf(file, "%s%s\n", pathname, S_ISDIR(mode) ? "/" : ""); return 0; } -static void show_rev_tweak_rev(struct rev_info *rev, struct setup_revision_opt *opt) +static void show_setup_revisions_tweak(struct rev_info *rev, + struct setup_revision_opt *opt) { if (rev->ignore_merges) { /* There was no "-m" on the command line */ @@ -519,7 +549,7 @@ int cmd_show(int argc, const char **argv, const char *prefix) struct pathspec match_all; int i, count, ret = 0; - init_grep_defaults(); + init_log_defaults(); git_config(git_log_config, NULL); memset(&match_all, 0, sizeof(match_all)); @@ -531,7 +561,7 @@ int cmd_show(int argc, const char **argv, const char *prefix) memset(&opt, 0, sizeof(opt)); opt.def = "HEAD"; - opt.tweak = show_rev_tweak_rev; + opt.tweak = show_setup_revisions_tweak; cmd_log_init(argc, argv, prefix, &rev, &opt); if (!rev.no_walk) @@ -544,25 +574,25 @@ int cmd_show(int argc, const char **argv, const char *prefix) const char *name = objects[i].name; switch (o->type) { case OBJ_BLOB: - ret = show_blob_object(o->sha1, &rev, name); + ret = show_blob_object(&o->oid, &rev, name); break; case OBJ_TAG: { struct tag *t = (struct tag *)o; if (rev.shown_one) putchar('\n'); - printf("%stag %s%s\n", + fprintf(rev.diffopt.file, "%stag %s%s\n", diff_get_color_opt(&rev.diffopt, DIFF_COMMIT), t->tag, diff_get_color_opt(&rev.diffopt, DIFF_RESET)); - ret = show_tag_object(o->sha1, &rev); + ret = show_tag_object(&o->oid, &rev); rev.shown_one = 1; if (ret) break; - o = parse_object(t->tagged->sha1); + o = parse_object(t->tagged->oid.hash); if (!o) ret = error(_("Could not read object %s"), - sha1_to_hex(t->tagged->sha1)); + oid_to_hex(&t->tagged->oid)); objects[i].item = o; i--; break; @@ -570,12 +600,12 @@ int cmd_show(int argc, const char **argv, const char *prefix) case OBJ_TREE: if (rev.shown_one) putchar('\n'); - printf("%stree %s%s\n\n", + fprintf(rev.diffopt.file, "%stree %s%s\n\n", 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, &match_all, - show_tree_object, NULL); + show_tree_object, rev.diffopt.file); rev.shown_one = 1; break; case OBJ_COMMIT: @@ -600,7 +630,7 @@ int cmd_log_reflog(int argc, const char **argv, const char *prefix) struct rev_info rev; struct setup_revision_opt opt; - init_grep_defaults(); + init_log_defaults(); git_config(git_log_config, NULL); init_revisions(&rev, prefix); @@ -618,12 +648,28 @@ int cmd_log_reflog(int argc, const char **argv, const char *prefix) return cmd_log_walk(&rev); } +static void log_setup_revisions_tweak(struct rev_info *rev, + struct setup_revision_opt *opt) +{ + if (DIFF_OPT_TST(&rev->diffopt, DEFAULT_FOLLOW_RENAMES) && + rev->prune_data.nr == 1) + DIFF_OPT_SET(&rev->diffopt, FOLLOW_RENAMES); + + /* Turn --cc/-c into -p --cc/-c when -p was not given */ + if (!rev->diffopt.output_format && rev->combine_merges) + rev->diffopt.output_format = DIFF_FORMAT_PATCH; + + /* Turn -m on when --cc/-c was given */ + if (rev->combine_merges) + rev->ignore_merges = 0; +} + int cmd_log(int argc, const char **argv, const char *prefix) { struct rev_info rev; struct setup_revision_opt opt; - init_grep_defaults(); + init_log_defaults(); git_config(git_log_config, NULL); init_revisions(&rev, prefix); @@ -631,6 +677,7 @@ int cmd_log(int argc, const char **argv, const char *prefix) memset(&opt, 0, sizeof(opt)); opt.def = "HEAD"; opt.revarg_opt = REVARG_COMMITTISH; + opt.tweak = log_setup_revisions_tweak; cmd_log_init(argc, argv, prefix, &rev, &opt); return cmd_log_walk(&rev); } @@ -643,9 +690,9 @@ static int auto_number = 1; static char *default_attach = NULL; -static struct string_list extra_hdr; -static struct string_list extra_to; -static struct string_list extra_cc; +static struct string_list extra_hdr = STRING_LIST_INIT_NODUP; +static struct string_list extra_to = STRING_LIST_INIT_NODUP; +static struct string_list extra_cc = STRING_LIST_INIT_NODUP; static void add_header(const char *value) { @@ -671,9 +718,12 @@ static void add_header(const char *value) #define THREAD_DEEP 2 static int thread; static int do_signoff; +static int base_auto; +static char *from; static const char *signature = git_version_string; static const char *signature_file; static int config_cover_letter; +static const char *config_output_directory; enum { COVER_UNSET, @@ -752,15 +802,31 @@ static int git_format_config(const char *var, const char *value, void *cb) config_cover_letter = git_config_bool(var, value) ? COVER_ON : COVER_OFF; return 0; } + if (!strcmp(var, "format.outputdirectory")) + return git_config_string(&config_output_directory, var, value); + if (!strcmp(var, "format.useautobase")) { + base_auto = git_config_bool(var, value); + return 0; + } + if (!strcmp(var, "format.from")) { + int b = git_config_maybe_bool(var, value); + free(from); + if (b < 0) + from = xstrdup(value); + else if (b) + from = xstrdup(git_committer_info(IDENT_NO_DATE)); + else + from = NULL; + return 0; + } return git_log_config(var, value, cb); } -static FILE *realstdout = NULL; static const char *output_directory = NULL; static int outdir_offset; -static int reopen_stdout(struct commit *commit, const char *subject, +static int open_next_file(struct commit *commit, const char *subject, struct rev_info *rev, int quiet) { struct strbuf filename = STRBUF_INIT; @@ -771,8 +837,7 @@ static int reopen_stdout(struct commit *commit, const char *subject, if (filename.len >= PATH_MAX - FORMAT_PATCH_NAME_MAX - suffix_len) return error(_("name of output directory is too long")); - if (filename.buf[filename.len - 1] != '/') - strbuf_addch(&filename, '/'); + strbuf_complete(&filename, '/'); } if (rev->numbered_files) @@ -783,9 +848,9 @@ static int reopen_stdout(struct commit *commit, const char *subject, fmt_output_subject(&filename, subject, rev); if (!quiet) - fprintf(realstdout, "%s\n", filename.buf + outdir_offset); + printf("%s\n", filename.buf + outdir_offset); - if (freopen(filename.buf, "w", stdout) == NULL) + if ((rev->diffopt.file = fopen(filename.buf, "w")) == NULL) return error(_("Cannot open patch file %s"), filename.buf); strbuf_release(&filename); @@ -795,7 +860,7 @@ static int reopen_stdout(struct commit *commit, const char *subject, static void get_patch_ids(struct rev_info *rev, struct patch_ids *ids) { struct rev_info check_rev; - struct commit *commit; + struct commit *commit, *c1, *c2; struct object *o1, *o2; unsigned flags1, flags2; @@ -803,9 +868,11 @@ static void get_patch_ids(struct rev_info *rev, struct patch_ids *ids) die(_("Need exactly one range.")); o1 = rev->pending.objects[0].item; - flags1 = o1->flags; o2 = rev->pending.objects[1].item; + flags1 = o1->flags; flags2 = o2->flags; + c1 = lookup_commit_reference(o1->oid.hash); + c2 = lookup_commit_reference(o2->oid.hash); if ((flags1 & UNINTERESTING) == (flags2 & UNINTERESTING)) die(_("Not a range.")); @@ -827,10 +894,8 @@ static void get_patch_ids(struct rev_info *rev, struct patch_ids *ids) } /* reset for next revision walk */ - clear_commit_marks((struct commit *)o1, - SEEN | UNINTERESTING | SHOWN | ADDED); - clear_commit_marks((struct commit *)o2, - SEEN | UNINTERESTING | SHOWN | ADDED); + clear_commit_marks(c1, SEEN | UNINTERESTING | SHOWN | ADDED); + clear_commit_marks(c2, SEEN | UNINTERESTING | SHOWN | ADDED); o1->flags = flags1; o2->flags = flags2; } @@ -844,15 +909,15 @@ static void gen_message_id(struct rev_info *info, char *base) info->message_id = strbuf_detach(&buf, NULL); } -static void print_signature(void) +static void print_signature(FILE *file) { if (!signature || !*signature) return; - printf("-- \n%s", signature); + fprintf(file, "-- \n%s", signature); if (signature[strlen(signature)-1] != '\n') - putchar('\n'); - putchar('\n'); + putc('\n', file); + putc('\n', file); } static void add_branch_description(struct strbuf *buf, const char *branch_name) @@ -872,8 +937,8 @@ static void add_branch_description(struct strbuf *buf, const char *branch_name) static char *find_branch_name(struct rev_info *rev) { int i, positive = -1; - unsigned char branch_sha1[20]; - const unsigned char *tip_sha1; + struct object_id branch_oid; + const struct object_id *tip_oid; const char *ref, *v; char *full_ref, *branch = NULL; @@ -888,10 +953,10 @@ static char *find_branch_name(struct rev_info *rev) if (positive < 0) return NULL; ref = rev->cmdline.rev[positive].name; - tip_sha1 = rev->cmdline.rev[positive].item->sha1; - if (dwim_ref(ref, strlen(ref), branch_sha1, &full_ref) && + tip_oid = &rev->cmdline.rev[positive].item->oid; + if (dwim_ref(ref, strlen(ref), branch_oid.hash, &full_ref) && skip_prefix(full_ref, "refs/heads/", &v) && - !hashcmp(tip_sha1, branch_sha1)) + !oidcmp(tip_oid, &branch_oid)) branch = xstrdup(v); free(full_ref); return branch; @@ -915,13 +980,13 @@ static void make_cover_letter(struct rev_info *rev, int use_stdout, struct pretty_print_context pp = {0}; struct commit *head = list[0]; - if (rev->commit_format != CMIT_FMT_EMAIL) + if (!cmit_fmt_is_mail(rev->commit_format)) die(_("Cover letter needs email format")); committer = git_committer_info(0); if (!use_stdout && - reopen_stdout(NULL, rev->numbered_files ? NULL : "cover-letter", rev, quiet)) + open_next_file(NULL, rev->numbered_files ? NULL : "cover-letter", rev, quiet)) return; log_write_email_headers(rev, head, &pp.subject, &pp.after_subject, @@ -939,12 +1004,12 @@ static void make_cover_letter(struct rev_info *rev, int use_stdout, msg = body; pp.fmt = CMIT_FMT_EMAIL; - pp.date_mode = DATE_RFC2822; + pp.date_mode.type = DATE_RFC2822; pp_user_info(&pp, NULL, &sb, committer, encoding); pp_title_line(&pp, &msg, &sb, encoding, need_8bit_cte); pp_remainder(&pp, &msg, &sb, 0); add_branch_description(&sb, branch_name); - printf("%s\n", sb.buf); + fprintf(rev->diffopt.file, "%s\n", sb.buf); strbuf_release(&sb); @@ -953,6 +1018,7 @@ static void make_cover_letter(struct rev_info *rev, int use_stdout, log.wrap = 72; log.in1 = 2; log.in2 = 4; + log.file = rev->diffopt.file; for (i = 0; i < nr; i++) shortlog_add_commit(&log, list[i]); @@ -969,14 +1035,13 @@ static void make_cover_letter(struct rev_info *rev, int use_stdout, diff_setup_done(&opts); - diff_tree_sha1(origin->tree->object.sha1, - head->tree->object.sha1, + diff_tree_sha1(origin->tree->object.oid.hash, + head->tree->object.oid.hash, "", &opts); diffcore_std(&opts); diff_flush(&opts); - printf("\n"); - print_signature(); + fprintf(rev->diffopt.file, "\n"); } static const char *clean_message_id(const char *msg_id) @@ -1046,6 +1111,11 @@ static int subject_prefix_callback(const struct option *opt, const char *arg, return 0; } +static int rfc_callback(const struct option *opt, const char *arg, int unset) +{ + return subject_prefix_callback(opt, "RFC PATCH", unset); +} + static int numbered_cmdline_opt = 0; static int numbered_callback(const struct option *opt, const char *arg, @@ -1158,6 +1228,155 @@ static int from_callback(const struct option *opt, const char *arg, int unset) return 0; } +struct base_tree_info { + struct object_id base_commit; + int nr_patch_id, alloc_patch_id; + struct object_id *patch_id; +}; + +static struct commit *get_base_commit(const char *base_commit, + struct commit **list, + int total) +{ + struct commit *base = NULL; + struct commit **rev; + int i = 0, rev_nr = 0; + + if (base_commit && strcmp(base_commit, "auto")) { + base = lookup_commit_reference_by_name(base_commit); + if (!base) + die(_("Unknown commit %s"), base_commit); + } else if ((base_commit && !strcmp(base_commit, "auto")) || base_auto) { + struct branch *curr_branch = branch_get(NULL); + const char *upstream = branch_get_upstream(curr_branch, NULL); + if (upstream) { + struct commit_list *base_list; + struct commit *commit; + struct object_id oid; + + if (get_oid(upstream, &oid)) + die(_("Failed to resolve '%s' as a valid ref."), upstream); + commit = lookup_commit_or_die(oid.hash, "upstream base"); + base_list = get_merge_bases_many(commit, total, list); + /* There should be one and only one merge base. */ + if (!base_list || base_list->next) + die(_("Could not find exact merge base.")); + base = base_list->item; + free_commit_list(base_list); + } else { + die(_("Failed to get upstream, if you want to record base commit automatically,\n" + "please use git branch --set-upstream-to to track a remote branch.\n" + "Or you could specify base commit by --base=<base-commit-id> manually.")); + } + } + + ALLOC_ARRAY(rev, total); + for (i = 0; i < total; i++) + rev[i] = list[i]; + + rev_nr = total; + /* + * Get merge base through pair-wise computations + * and store it in rev[0]. + */ + while (rev_nr > 1) { + for (i = 0; i < rev_nr / 2; i++) { + struct commit_list *merge_base; + merge_base = get_merge_bases(rev[2 * i], rev[2 * i + 1]); + if (!merge_base || merge_base->next) + die(_("Failed to find exact merge base")); + + rev[i] = merge_base->item; + } + + if (rev_nr % 2) + rev[i] = rev[2 * i]; + rev_nr = (rev_nr + 1) / 2; + } + + if (!in_merge_bases(base, rev[0])) + die(_("base commit should be the ancestor of revision list")); + + for (i = 0; i < total; i++) { + if (base == list[i]) + die(_("base commit shouldn't be in revision list")); + } + + free(rev); + return base; +} + +static void prepare_bases(struct base_tree_info *bases, + struct commit *base, + struct commit **list, + int total) +{ + struct commit *commit; + struct rev_info revs; + struct diff_options diffopt; + int i; + + if (!base) + return; + + diff_setup(&diffopt); + DIFF_OPT_SET(&diffopt, RECURSIVE); + diff_setup_done(&diffopt); + + oidcpy(&bases->base_commit, &base->object.oid); + + init_revisions(&revs, NULL); + revs.max_parents = 1; + revs.topo_order = 1; + for (i = 0; i < total; i++) { + list[i]->object.flags &= ~UNINTERESTING; + add_pending_object(&revs, &list[i]->object, "rev_list"); + list[i]->util = (void *)1; + } + base->object.flags |= UNINTERESTING; + add_pending_object(&revs, &base->object, "base"); + + if (prepare_revision_walk(&revs)) + die(_("revision walk setup failed")); + /* + * Traverse the commits list, get prerequisite patch ids + * and stuff them in bases structure. + */ + while ((commit = get_revision(&revs)) != NULL) { + struct object_id oid; + struct object_id *patch_id; + if (commit->util) + continue; + if (commit_patch_id(commit, &diffopt, oid.hash, 0)) + die(_("cannot get patch id")); + ALLOC_GROW(bases->patch_id, bases->nr_patch_id + 1, bases->alloc_patch_id); + patch_id = bases->patch_id + bases->nr_patch_id; + oidcpy(patch_id, &oid); + bases->nr_patch_id++; + } +} + +static void print_bases(struct base_tree_info *bases, FILE *file) +{ + int i; + + /* Only do this once, either for the cover or for the first one */ + if (is_null_oid(&bases->base_commit)) + return; + + /* Show the base commit */ + fprintf(file, "\nbase-commit: %s\n", oid_to_hex(&bases->base_commit)); + + /* Show the prerequisite patches */ + for (i = bases->nr_patch_id - 1; i >= 0; i--) + fprintf(file, "prerequisite-patch-id: %s\n", oid_to_hex(&bases->patch_id[i])); + + free(bases->patch_id); + bases->nr_patch_id = 0; + bases->alloc_patch_id = 0; + oidclr(&bases->base_commit); +} + int cmd_format_patch(int argc, const char **argv, const char *prefix) { struct commit *commit; @@ -1172,6 +1391,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) int cover_letter = -1; int boundary_count = 0; int no_binary_diff = 0; + int zero_commit = 0; struct commit *origin = NULL; const char *in_reply_to = NULL; struct patch_ids ids; @@ -1180,7 +1400,9 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) int quiet = 0; int reroll_count = -1; char *branch_name = NULL; - char *from = NULL; + char *base_commit = NULL; + struct base_tree_info bases; + const struct option builtin_format_patch_options[] = { { OPTION_CALLBACK, 'n', "numbered", &numbered, NULL, N_("use [PATCH n/m] even with a single patch"), @@ -1201,6 +1423,9 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) N_("start numbering patches at <n> instead of 1")), OPT_INTEGER('v', "reroll-count", &reroll_count, N_("mark the series as Nth re-roll")), + { OPTION_CALLBACK, 0, "rfc", &rev, NULL, + N_("Use [RFC PATCH] instead of [PATCH]"), + PARSE_OPT_NOARG | PARSE_OPT_NONEG, rfc_callback }, { OPTION_CALLBACK, 0, "subject-prefix", &rev, N_("prefix"), N_("Use [<prefix>] instead of [PATCH]"), PARSE_OPT_NONEG, subject_prefix_callback }, @@ -1212,6 +1437,8 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) PARSE_OPT_NOARG | PARSE_OPT_NONEG, keep_callback }, OPT_BOOL(0, "no-binary", &no_binary_diff, N_("don't output binary diffs")), + OPT_BOOL(0, "zero-commit", &zero_commit, + N_("output all-zero hash in From header")), OPT_BOOL(0, "ignore-if-in-upstream", &ignore_if_in_upstream, N_("don't include a patch matching a commit upstream")), { OPTION_SET_INT, 'p', "no-stat", &use_patch_format, NULL, @@ -1241,6 +1468,8 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) PARSE_OPT_OPTARG, thread_callback }, OPT_STRING(0, "signature", &signature, N_("signature"), N_("add a signature")), + OPT_STRING(0, "base", &base_commit, N_("base-commit"), + N_("add prerequisite tree info to the patch series")), OPT_FILENAME(0, "signature-file", &signature_file, N_("add a signature from a file")), OPT__QUIET(&quiet, N_("don't print the patch filenames")), @@ -1250,10 +1479,11 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) extra_hdr.strdup_strings = 1; extra_to.strdup_strings = 1; extra_cc.strdup_strings = 1; - init_grep_defaults(); + init_log_defaults(); git_config(git_format_config, NULL); init_revisions(&rev, prefix); rev.commit_format = CMIT_FMT_EMAIL; + rev.expand_tabs_in_log_default = 0; rev.verbose_header = 1; rev.diff = 1; rev.max_parents = 1; @@ -1334,7 +1564,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) if (numbered && keep_subject) die (_("-n and -k are mutually exclusive.")); if (keep_subject && subject_prefix) - die (_("--subject-prefix and -k are mutually exclusive.")); + die (_("--subject-prefix/--rfc and -k are mutually exclusive.")); rev.preserve_subject = keep_subject; argc = setup_revisions(argc, argv, &rev, &s_r_opt); @@ -1356,18 +1586,25 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) /* Always generate a patch */ rev.diffopt.output_format |= DIFF_FORMAT_PATCH; + rev.zero_commit = zero_commit; + if (!DIFF_OPT_TST(&rev.diffopt, TEXT) && !no_binary_diff) DIFF_OPT_SET(&rev.diffopt, BINARY); if (rev.show_notes) init_display_notes(&rev.notes_opt); + if (!output_directory && !use_stdout) + output_directory = config_output_directory; + if (!use_stdout) output_directory = set_outdir(prefix, output_directory); else setup_pager(); if (output_directory) { + if (rev.diffopt.use_color != GIT_COLOR_ALWAYS) + rev.diffopt.use_color = GIT_COLOR_NEVER; if (use_stdout) die(_("standard output, or directory, which one?")); if (mkdir(output_directory, 0777) < 0 && errno != EEXIST) @@ -1398,10 +1635,10 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) check_head = 1; if (check_head) { - unsigned char sha1[20]; + struct object_id oid; const char *ref, *v; ref = resolve_ref_unsafe("HEAD", RESOLVE_REF_READING, - sha1, NULL); + oid.hash, NULL); if (ref && skip_prefix(ref, "refs/heads/", &v)) branch_name = xstrdup(v); else @@ -1419,15 +1656,12 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) /* Don't say anything if head and upstream are the same. */ if (rev.pending.nr == 2) { struct object_array_entry *o = rev.pending.objects; - if (hashcmp(o[0].item->sha1, o[1].item->sha1) == 0) + if (oidcmp(&o[0].item->oid, &o[1].item->oid) == 0) return 0; } get_patch_ids(&rev, &ids); } - if (!use_stdout) - realstdout = xfdopen(xdup(1), "w"); - if (prepare_revision_walk(&rev)) die(_("revision walk setup failed")); rev.boundary = 1; @@ -1438,8 +1672,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) continue; } - if (ignore_if_in_upstream && - has_commit_patch_id(commit, &ids)) + if (ignore_if_in_upstream && has_commit_patch_id(commit, &ids)) continue; nr++; @@ -1450,16 +1683,16 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) /* nothing to do */ return 0; total = nr; - if (!keep_subject && auto_number && total > 1) - numbered = 1; - if (numbered) - rev.total = total + start_number - 1; if (cover_letter == -1) { if (config_cover_letter == COVER_AUTO) cover_letter = (total > 1); else cover_letter = (config_cover_letter == COVER_ON); } + if (!keep_subject && auto_number && (total > 1 || cover_letter)) + numbered = 1; + if (numbered) + rev.total = total + start_number - 1; if (!signature) { ; /* --no-signature inhibits all signatures */ @@ -1473,6 +1706,13 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) signature = strbuf_detach(&buf, NULL); } + memset(&bases, 0, sizeof(bases)); + if (base_commit || base_auto) { + struct commit *base = get_base_commit(base_commit, list, nr); + reset_revision_walk(); + prepare_bases(&bases, base, list, nr); + } + if (in_reply_to || thread || cover_letter) rev.ref_message_ids = xcalloc(1, sizeof(struct string_list)); if (in_reply_to) { @@ -1486,6 +1726,8 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) gen_message_id(&rev, "cover"); make_cover_letter(&rev, use_stdout, origin, nr, list, branch_name, quiet); + print_bases(&bases, rev.diffopt.file); + print_signature(rev.diffopt.file); total++; start_number--; } @@ -1527,11 +1769,11 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) string_list_append(rev.ref_message_ids, rev.message_id); } - gen_message_id(&rev, sha1_to_hex(commit->object.sha1)); + gen_message_id(&rev, oid_to_hex(&commit->object.oid)); } if (!use_stdout && - reopen_stdout(rev.numbered_files ? NULL : commit, NULL, &rev, quiet)) + open_next_file(rev.numbered_files ? NULL : commit, NULL, &rev, quiet)) die(_("Failed to create output files")); shown = log_tree_commit(&rev, commit); free_commit_buffer(commit); @@ -1545,15 +1787,16 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) if (!use_stdout) rev.shown_one = 0; if (shown) { + print_bases(&bases, rev.diffopt.file); if (rev.mime_boundary) - printf("\n--%s%s--\n\n\n", + fprintf(rev.diffopt.file, "\n--%s%s--\n\n\n", mime_boundary_leader, rev.mime_boundary); else - print_signature(); + print_signature(rev.diffopt.file); } if (!use_stdout) - fclose(stdout); + fclose(rev.diffopt.file); } free(list); free(branch_name); @@ -1567,9 +1810,9 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) static int add_pending_commit(const char *arg, struct rev_info *revs, int flags) { - unsigned char sha1[20]; - if (get_sha1(arg, sha1) == 0) { - struct commit *commit = lookup_commit_reference(sha1); + struct object_id oid; + if (get_oid(arg, &oid) == 0) { + struct commit *commit = lookup_commit_reference(oid.hash); if (commit) { commit->object.flags |= flags; add_pending_object(revs, &commit->object, arg); @@ -1585,16 +1828,16 @@ static const char * const cherry_usage[] = { }; static void print_commit(char sign, struct commit *commit, int verbose, - int abbrev) + int abbrev, FILE *file) { if (!verbose) { - printf("%c %s\n", sign, - find_unique_abbrev(commit->object.sha1, abbrev)); + fprintf(file, "%c %s\n", sign, + find_unique_abbrev(commit->object.oid.hash, 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), + fprintf(file, "%c %s %s\n", sign, + find_unique_abbrev(commit->object.oid.hash, abbrev), buf.buf); strbuf_release(&buf); } @@ -1652,7 +1895,7 @@ int cmd_cherry(int argc, const char **argv, const char *prefix) /* Don't say anything if head and upstream are the same. */ if (revs.pending.nr == 2) { struct object_array_entry *o = revs.pending.objects; - if (hashcmp(o[0].item->sha1, o[1].item->sha1) == 0) + if (oidcmp(&o[0].item->oid, &o[1].item->oid) == 0) return 0; } @@ -1674,7 +1917,7 @@ int cmd_cherry(int argc, const char **argv, const char *prefix) commit = list->item; if (has_commit_patch_id(commit, &ids)) sign = '-'; - print_commit(sign, commit, verbose, abbrev); + print_commit(sign, commit, verbose, abbrev, revs.diffopt.file); list = list->next; } diff --git a/builtin/ls-files.c b/builtin/ls-files.c index 6fa2205734..1592290815 100644 --- a/builtin/ls-files.c +++ b/builtin/ls-files.c @@ -14,6 +14,7 @@ #include "resolve-undo.h" #include "string-list.h" #include "pathspec.h" +#include "run-command.h" static int abbrev; static int show_deleted; @@ -27,8 +28,12 @@ static int show_killed; static int show_valid_bit; static int line_terminator = '\n'; static int debug_mode; +static int show_eol; +static int recurse_submodules; +static struct argv_array submodules_options = ARGV_ARRAY_INIT; static const char *prefix; +static const char *super_prefix; static int max_prefix_len; static int prefix_len; static struct pathspec pathspec; @@ -47,14 +52,44 @@ static const char *tag_modified = ""; static const char *tag_skip_worktree = ""; static const char *tag_resolve_undo = ""; +static void write_eolinfo(const struct cache_entry *ce, const char *path) +{ + if (!show_eol) + return; + else { + struct stat st; + const char *i_txt = ""; + const char *w_txt = ""; + const char *a_txt = get_convert_attr_ascii(path); + if (ce && S_ISREG(ce->ce_mode)) + i_txt = get_cached_convert_stats_ascii(ce->name); + if (!lstat(path, &st) && S_ISREG(st.st_mode)) + w_txt = get_wt_convert_stats_ascii(path); + printf("i/%-5s w/%-5s attr/%-17s\t", i_txt, w_txt, a_txt); + } +} + static void write_name(const char *name) { /* + * Prepend the super_prefix to name to construct the full_name to be + * written. + */ + struct strbuf full_name = STRBUF_INIT; + if (super_prefix) { + strbuf_addstr(&full_name, super_prefix); + strbuf_addstr(&full_name, name); + name = full_name.buf; + } + + /* * With "--full-name", prefix_len=0; this caller needs to pass * an empty string in that case (a NULL is good for ""). */ write_name_quoted_relative(name, prefix_len ? prefix : NULL, stdout, line_terminator); + + strbuf_release(&full_name); } static void show_dir_entry(const char *tag, struct dir_entry *ent) @@ -68,6 +103,7 @@ static void show_dir_entry(const char *tag, struct dir_entry *ent) return; fputs(tag, stdout); + write_eolinfo(NULL, ent->name); write_name(ent->name); } @@ -99,7 +135,8 @@ static void show_killed_files(struct dir_struct *dir) */ pos = cache_name_pos(ent->name, ent->len); if (0 <= pos) - die("bug in show-killed-files"); + die("BUG: killed-file %.*s not found", + ent->len, ent->name); pos = -pos - 1; while (pos < active_nr && ce_stage(active_cache[pos])) @@ -132,54 +169,117 @@ static void show_killed_files(struct dir_struct *dir) } } +/* + * Compile an argv_array with all of the options supported by --recurse_submodules + */ +static void compile_submodule_options(const struct dir_struct *dir, int show_tag) +{ + if (line_terminator == '\0') + argv_array_push(&submodules_options, "-z"); + if (show_tag) + argv_array_push(&submodules_options, "-t"); + if (show_valid_bit) + argv_array_push(&submodules_options, "-v"); + if (show_cached) + argv_array_push(&submodules_options, "--cached"); + if (show_eol) + argv_array_push(&submodules_options, "--eol"); + if (debug_mode) + argv_array_push(&submodules_options, "--debug"); +} + +/** + * Recursively call ls-files on a submodule + */ +static void show_gitlink(const struct cache_entry *ce) +{ + struct child_process cp = CHILD_PROCESS_INIT; + int status; + int i; + + argv_array_pushf(&cp.args, "--super-prefix=%s%s/", + super_prefix ? super_prefix : "", + ce->name); + argv_array_push(&cp.args, "ls-files"); + argv_array_push(&cp.args, "--recurse-submodules"); + + /* add supported options */ + argv_array_pushv(&cp.args, submodules_options.argv); + + /* + * Pass in the original pathspec args. The submodule will be + * responsible for prepending the 'submodule_prefix' prior to comparing + * against the pathspec for matches. + */ + argv_array_push(&cp.args, "--"); + for (i = 0; i < pathspec.nr; i++) + argv_array_push(&cp.args, pathspec.items[i].original); + + cp.git_cmd = 1; + cp.dir = ce->name; + status = run_command(&cp); + if (status) + exit(status); +} + static void show_ce_entry(const char *tag, const struct cache_entry *ce) { + struct strbuf name = STRBUF_INIT; int len = max_prefix_len; + if (super_prefix) + strbuf_addstr(&name, super_prefix); + strbuf_addstr(&name, ce->name); if (len >= ce_namelen(ce)) die("git ls-files: internal error - cache entry not superset of prefix"); - if (!match_pathspec(&pathspec, ce->name, ce_namelen(ce), - len, ps_matched, - S_ISDIR(ce->ce_mode) || S_ISGITLINK(ce->ce_mode))) - return; + if (recurse_submodules && S_ISGITLINK(ce->ce_mode) && + submodule_path_match(&pathspec, name.buf, ps_matched)) { + show_gitlink(ce); + } else if (match_pathspec(&pathspec, name.buf, name.len, + len, ps_matched, + S_ISDIR(ce->ce_mode) || + S_ISGITLINK(ce->ce_mode))) { + if (tag && *tag && show_valid_bit && + (ce->ce_flags & CE_VALID)) { + static char alttag[4]; + memcpy(alttag, tag, 3); + if (isalpha(tag[0])) + alttag[0] = tolower(tag[0]); + else if (tag[0] == '?') + alttag[0] = '!'; + else { + alttag[0] = 'v'; + alttag[1] = tag[0]; + alttag[2] = ' '; + alttag[3] = 0; + } + tag = alttag; + } - if (tag && *tag && show_valid_bit && - (ce->ce_flags & CE_VALID)) { - static char alttag[4]; - memcpy(alttag, tag, 3); - if (isalpha(tag[0])) - alttag[0] = tolower(tag[0]); - else if (tag[0] == '?') - alttag[0] = '!'; - else { - alttag[0] = 'v'; - alttag[1] = tag[0]; - alttag[2] = ' '; - alttag[3] = 0; + if (!show_stage) { + fputs(tag, stdout); + } else { + printf("%s%06o %s %d\t", + tag, + ce->ce_mode, + find_unique_abbrev(ce->oid.hash, abbrev), + ce_stage(ce)); + } + write_eolinfo(ce, ce->name); + write_name(ce->name); + if (debug_mode) { + const struct stat_data *sd = &ce->ce_stat_data; + + printf(" ctime: %d:%d\n", sd->sd_ctime.sec, sd->sd_ctime.nsec); + printf(" mtime: %d:%d\n", sd->sd_mtime.sec, sd->sd_mtime.nsec); + printf(" dev: %d\tino: %d\n", sd->sd_dev, sd->sd_ino); + printf(" uid: %d\tgid: %d\n", sd->sd_uid, sd->sd_gid); + printf(" size: %d\tflags: %x\n", sd->sd_size, ce->ce_flags); } - tag = alttag; } - if (!show_stage) { - fputs(tag, stdout); - } else { - printf("%s%06o %s %d\t", - tag, - ce->ce_mode, - find_unique_abbrev(ce->sha1,abbrev), - ce_stage(ce)); - } - write_name(ce->name); - if (debug_mode) { - const struct stat_data *sd = &ce->ce_stat_data; - - printf(" ctime: %d:%d\n", sd->sd_ctime.sec, sd->sd_ctime.nsec); - printf(" mtime: %d:%d\n", sd->sd_mtime.sec, sd->sd_mtime.nsec); - printf(" dev: %d\tino: %d\n", sd->sd_dev, sd->sd_ino); - printf(" uid: %d\tgid: %d\n", sd->sd_uid, sd->sd_gid); - printf(" size: %d\tflags: %x\n", sd->sd_size, ce->ce_flags); - } + strbuf_release(&name); } static void show_ru_info(void) @@ -359,14 +459,6 @@ static const char * const ls_files_usage[] = { NULL }; -static int option_parse_z(const struct option *opt, - const char *arg, int unset) -{ - line_terminator = unset ? '\n' : '\0'; - - return 0; -} - static int option_parse_exclude(const struct option *opt, const char *arg, int unset) { @@ -408,9 +500,9 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix) struct exclude_list *el; struct string_list exclude_list = STRING_LIST_INIT_NODUP; struct option builtin_ls_files_options[] = { - { OPTION_CALLBACK, 'z', NULL, NULL, NULL, - N_("paths are separated with NUL character"), - PARSE_OPT_NOARG, option_parse_z }, + /* Think twice before adding "--nul" synonym to this */ + OPT_SET_INT('z', NULL, &line_terminator, + N_("paths are separated with NUL character"), '\0'), OPT_BOOL('t', NULL, &show_tag, N_("identify the file status with tags")), OPT_BOOL('v', NULL, &show_valid_bit, @@ -433,6 +525,7 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix) OPT_BIT(0, "directory", &dir.flags, N_("show 'other' directories' names only"), DIR_SHOW_OTHER_DIRECTORIES), + OPT_BOOL(0, "eol", &show_eol, N_("show line endings of files")), OPT_NEGBIT(0, "empty-directory", &dir.flags, N_("don't show empty directories"), DIR_HIDE_EMPTY_DIRECTORIES), @@ -454,6 +547,8 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix) { OPTION_SET_INT, 0, "full-name", &prefix_len, NULL, N_("make the output relative to the project top directory"), PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL }, + OPT_BOOL(0, "recurse-submodules", &recurse_submodules, + N_("recurse through submodules")), OPT_BOOL(0, "error-unmatch", &error_unmatch, N_("if any <file> is not in the index, treat this as an error")), OPT_STRING(0, "with-tree", &with_tree, N_("tree-ish"), @@ -470,6 +565,7 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix) prefix = cmd_prefix; if (prefix) prefix_len = strlen(prefix); + super_prefix = get_super_prefix(); git_config(git_default_config, NULL); if (read_cache() < 0) @@ -505,18 +601,37 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix) if (require_work_tree && !is_inside_work_tree()) setup_work_tree(); + if (recurse_submodules) + compile_submodule_options(&dir, show_tag); + + if (recurse_submodules && + (show_stage || show_deleted || show_others || show_unmerged || + show_killed || show_modified || show_resolve_undo || with_tree)) + die("ls-files --recurse-submodules unsupported mode"); + + if (recurse_submodules && error_unmatch) + die("ls-files --recurse-submodules does not support " + "--error-unmatch"); + parse_pathspec(&pathspec, 0, PATHSPEC_PREFER_CWD | PATHSPEC_STRIP_SUBMODULE_SLASH_CHEAP, prefix, argv); - /* Find common prefix for all pathspec's */ - max_prefix = common_prefix(&pathspec); + /* + * Find common prefix for all pathspec's + * This is used as a performance optimization which unfortunately cannot + * be done when recursing into submodules + */ + if (recurse_submodules) + max_prefix = NULL; + else + max_prefix = common_prefix(&pathspec); max_prefix_len = max_prefix ? strlen(max_prefix) : 0; /* Treat unmatching pathspec elements as errors */ if (pathspec.nr && error_unmatch) - ps_matched = xcalloc(1, pathspec.nr); + ps_matched = xcalloc(pathspec.nr, 1); if ((dir.flags & DIR_SHOW_IGNORED) && !exc_given) die("ls-files --ignored needs some exclude pattern"); diff --git a/builtin/ls-remote.c b/builtin/ls-remote.c index 4554dbc8a9..66cdd45cc1 100644 --- a/builtin/ls-remote.c +++ b/builtin/ls-remote.c @@ -3,9 +3,12 @@ #include "transport.h" #include "remote.h" -static const char ls_remote_usage[] = -"git ls-remote [--heads] [--tags] [-u <exec> | --upload-pack <exec>]\n" -" [-q | --quiet] [--exit-code] [--get-url] [<repository> [<refs>...]]"; +static const char * const ls_remote_usage[] = { + N_("git ls-remote [--heads] [--tags] [--refs] [--upload-pack=<exec>]\n" + " [-q | --quiet] [--exit-code] [--get-url]\n" + " [--symref] [<repository> [<refs>...]]"), + NULL +}; /* * Is there one among the list of patterns that match the tail part @@ -30,12 +33,12 @@ static int tail_match(const char **pattern, const char *path) int cmd_ls_remote(int argc, const char **argv, const char *prefix) { - int i; const char *dest = NULL; unsigned flags = 0; int get_url = 0; int quiet = 0; int status = 0; + int show_symref_target = 0; const char *uploadpack = NULL; const char **pattern = NULL; @@ -43,63 +46,36 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix) struct transport *transport; const struct ref *ref; - if (argc == 2 && !strcmp("-h", argv[1])) - usage(ls_remote_usage); + struct option options[] = { + OPT__QUIET(&quiet, N_("do not print remote URL")), + OPT_STRING(0, "upload-pack", &uploadpack, N_("exec"), + N_("path of git-upload-pack on the remote host")), + { OPTION_STRING, 0, "exec", &uploadpack, N_("exec"), + N_("path of git-upload-pack on the remote host"), + PARSE_OPT_HIDDEN }, + OPT_BIT('t', "tags", &flags, N_("limit to tags"), REF_TAGS), + OPT_BIT('h', "heads", &flags, N_("limit to heads"), REF_HEADS), + OPT_BIT(0, "refs", &flags, N_("do not show peeled tags"), REF_NORMAL), + OPT_BOOL(0, "get-url", &get_url, + N_("take url.<base>.insteadOf into account")), + OPT_SET_INT(0, "exit-code", &status, + N_("exit with exit code 2 if no matching refs are found"), 2), + OPT_BOOL(0, "symref", &show_symref_target, + N_("show underlying ref in addition to the object pointed by it")), + OPT_END() + }; - for (i = 1; i < argc; i++) { - const char *arg = argv[i]; + argc = parse_options(argc, argv, prefix, options, ls_remote_usage, + PARSE_OPT_STOP_AT_NON_OPTION); + dest = argv[0]; - if (*arg == '-') { - if (starts_with(arg, "--upload-pack=")) { - uploadpack = arg + 14; - continue; - } - if (starts_with(arg, "--exec=")) { - uploadpack = arg + 7; - continue; - } - if (!strcmp("--tags", arg) || !strcmp("-t", arg)) { - flags |= REF_TAGS; - continue; - } - if (!strcmp("--heads", arg) || !strcmp("-h", arg)) { - flags |= REF_HEADS; - continue; - } - if (!strcmp("--refs", arg)) { - flags |= REF_NORMAL; - continue; - } - if (!strcmp("--quiet", arg) || !strcmp("-q", arg)) { - 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; - i++; - break; + if (argc > 1) { + int i; + pattern = xcalloc(argc, sizeof(const char *)); + for (i = 1; i < argc; i++) + pattern[i - 1] = xstrfmt("*/%s", argv[i]); } - if (argv[i]) { - int j; - pattern = xcalloc(argc - i + 1, sizeof(const char *)); - for (j = i; j < argc; j++) { - int len = strlen(argv[j]); - char *p = xmalloc(len + 3); - sprintf(p, "*/%s", argv[j]); - pattern[j - i] = p; - } - } remote = remote_get(dest); if (!remote) { if (dest) @@ -129,7 +105,9 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix) continue; if (!tail_match(pattern, ref->name)) continue; - printf("%s %s\n", sha1_to_hex(ref->old_sha1), ref->name); + if (show_symref_target && ref->symref) + printf("ref: %s\t%s\n", ref->symref, ref->name); + printf("%s\t%s\n", oid_to_hex(&ref->old_oid), ref->name); status = 0; /* we found something */ } return status; diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c index 3b04a0f082..0e30d86230 100644 --- a/builtin/ls-tree.c +++ b/builtin/ls-tree.c @@ -96,12 +96,13 @@ static int show_tree(const unsigned char *sha1, struct strbuf *base, if (!strcmp(type, blob_type)) { unsigned long size; if (sha1_object_info(sha1, &size) == OBJ_BAD) - strcpy(size_text, "BAD"); + xsnprintf(size_text, sizeof(size_text), + "BAD"); else - snprintf(size_text, sizeof(size_text), - "%lu", size); + xsnprintf(size_text, sizeof(size_text), + "%lu", size); } else - strcpy(size_text, "-"); + xsnprintf(size_text, sizeof(size_text), "-"); printf("%06o %s %s %7s\t", mode, type, find_unique_abbrev(sha1, abbrev), size_text); diff --git a/builtin/mailinfo.c b/builtin/mailinfo.c index 999a5250fb..e3b62f2fc7 100644 --- a/builtin/mailinfo.c +++ b/builtin/mailinfo.c @@ -6,1064 +6,49 @@ #include "builtin.h" #include "utf8.h" #include "strbuf.h" +#include "mailinfo.h" -static FILE *cmitmsg, *patchfile, *fin, *fout; - -static int keep_subject; -static int keep_non_patch_brackets_in_subject; -static const char *metainfo_charset; -static struct strbuf line = STRBUF_INIT; -static struct strbuf name = STRBUF_INIT; -static struct strbuf email = STRBUF_INIT; -static char *message_id; - -static enum { - TE_DONTCARE, TE_QP, TE_BASE64 -} transfer_encoding; - -static struct strbuf charset = STRBUF_INIT; -static int patch_lines; -static struct strbuf **p_hdr_data, **s_hdr_data; -static int use_scissors; -static int add_message_id; -static int use_inbody_headers = 1; - -#define MAX_HDR_PARSED 10 -#define MAX_BOUNDARIES 5 - -static void cleanup_space(struct strbuf *sb); - - -static void get_sane_name(struct strbuf *out, struct strbuf *name, struct strbuf *email) -{ - struct strbuf *src = name; - if (name->len < 3 || 60 < name->len || strchr(name->buf, '@') || - strchr(name->buf, '<') || strchr(name->buf, '>')) - src = email; - else if (name == out) - return; - strbuf_reset(out); - strbuf_addbuf(out, src); -} - -static void parse_bogus_from(const struct strbuf *line) -{ - /* John Doe <johndoe> */ - - char *bra, *ket; - /* This is fallback, so do not bother if we already have an - * e-mail address. - */ - if (email.len) - return; - - bra = strchr(line->buf, '<'); - if (!bra) - return; - ket = strchr(bra, '>'); - if (!ket) - return; - - strbuf_reset(&email); - strbuf_add(&email, bra + 1, ket - bra - 1); - - strbuf_reset(&name); - strbuf_add(&name, line->buf, bra - line->buf); - strbuf_trim(&name); - get_sane_name(&name, &name, &email); -} - -static void handle_from(const struct strbuf *from) -{ - char *at; - size_t el; - struct strbuf f; - - strbuf_init(&f, from->len); - strbuf_addbuf(&f, from); - - at = strchr(f.buf, '@'); - if (!at) { - parse_bogus_from(from); - return; - } - - /* - * If we already have one email, don't take any confusing lines - */ - if (email.len && strchr(at + 1, '@')) { - strbuf_release(&f); - return; - } - - /* Pick up the string around '@', possibly delimited with <> - * pair; that is the email part. - */ - while (at > f.buf) { - char c = at[-1]; - if (isspace(c)) - break; - if (c == '<') { - at[-1] = ' '; - break; - } - at--; - } - el = strcspn(at, " \n\t\r\v\f>"); - strbuf_reset(&email); - strbuf_add(&email, at, el); - strbuf_remove(&f, at - f.buf, el + (at[el] ? 1 : 0)); - - /* The remainder is name. It could be - * - * - "John Doe <john.doe@xz>" (a), or - * - "john.doe@xz (John Doe)" (b), or - * - "John (zzz) Doe <john.doe@xz> (Comment)" (c) - * - * but we have removed the email part, so - * - * - remove extra spaces which could stay after email (case 'c'), and - * - trim from both ends, possibly removing the () pair at the end - * (cases 'a' and 'b'). - */ - cleanup_space(&f); - strbuf_trim(&f); - if (f.buf[0] == '(' && f.len && f.buf[f.len - 1] == ')') { - strbuf_remove(&f, 0, 1); - strbuf_setlen(&f, f.len - 1); - } - - get_sane_name(&name, &f, &email); - strbuf_release(&f); -} - -static void handle_header(struct strbuf **out, const struct strbuf *line) -{ - if (!*out) { - *out = xmalloc(sizeof(struct strbuf)); - strbuf_init(*out, line->len); - } else - strbuf_reset(*out); - - strbuf_addbuf(*out, line); -} - -/* NOTE NOTE NOTE. We do not claim we do full MIME. We just attempt - * to have enough heuristics to grok MIME encoded patches often found - * on our mailing lists. For example, we do not even treat header lines - * case insensitively. - */ - -static int slurp_attr(const char *line, const char *name, struct strbuf *attr) -{ - const char *ends, *ap = strcasestr(line, name); - size_t sz; - - strbuf_setlen(attr, 0); - if (!ap) - return 0; - ap += strlen(name); - if (*ap == '"') { - ap++; - ends = "\""; - } - else - ends = "; \t"; - sz = strcspn(ap, ends); - strbuf_add(attr, ap, sz); - return 1; -} - -static struct strbuf *content[MAX_BOUNDARIES]; - -static struct strbuf **content_top = content; - -static void handle_content_type(struct strbuf *line) -{ - struct strbuf *boundary = xmalloc(sizeof(struct strbuf)); - strbuf_init(boundary, line->len); - - if (slurp_attr(line->buf, "boundary=", boundary)) { - strbuf_insert(boundary, 0, "--", 2); - if (++content_top > &content[MAX_BOUNDARIES]) { - fprintf(stderr, "Too many boundaries to handle\n"); - exit(1); - } - *content_top = boundary; - boundary = NULL; - } - slurp_attr(line->buf, "charset=", &charset); - - if (boundary) { - strbuf_release(boundary); - free(boundary); - } -} - -static void handle_message_id(const struct strbuf *line) -{ - if (add_message_id) - message_id = strdup(line->buf); -} - -static void handle_content_transfer_encoding(const struct strbuf *line) -{ - if (strcasestr(line->buf, "base64")) - transfer_encoding = TE_BASE64; - else if (strcasestr(line->buf, "quoted-printable")) - transfer_encoding = TE_QP; - else - transfer_encoding = TE_DONTCARE; -} - -static int is_multipart_boundary(const struct strbuf *line) -{ - return (((*content_top)->len <= line->len) && - !memcmp(line->buf, (*content_top)->buf, (*content_top)->len)); -} - -static void cleanup_subject(struct strbuf *subject) -{ - size_t at = 0; - - while (at < subject->len) { - char *pos; - size_t remove; - - switch (subject->buf[at]) { - case 'r': case 'R': - if (subject->len <= at + 3) - break; - if ((subject->buf[at + 1] == 'e' || - subject->buf[at + 1] == 'E') && - subject->buf[at + 2] == ':') { - strbuf_remove(subject, at, 3); - continue; - } - at++; - break; - case ' ': case '\t': case ':': - strbuf_remove(subject, at, 1); - continue; - case '[': - pos = strchr(subject->buf + at, ']'); - if (!pos) - break; - remove = pos - subject->buf + at + 1; - if (!keep_non_patch_brackets_in_subject || - (7 <= remove && - memmem(subject->buf + at, remove, "PATCH", 5))) - strbuf_remove(subject, at, remove); - else { - at += remove; - /* - * If the input had a space after the ], keep - * it. We don't bother with finding the end of - * the space, since we later normalize it - * anyway. - */ - if (isspace(subject->buf[at])) - at += 1; - } - continue; - } - break; - } - strbuf_trim(subject); -} - -static void cleanup_space(struct strbuf *sb) -{ - size_t pos, cnt; - for (pos = 0; pos < sb->len; pos++) { - if (isspace(sb->buf[pos])) { - sb->buf[pos] = ' '; - for (cnt = 0; isspace(sb->buf[pos + cnt + 1]); cnt++); - strbuf_remove(sb, pos + 1, cnt); - } - } -} - -static void decode_header(struct strbuf *line); -static const char *header[MAX_HDR_PARSED] = { - "From","Subject","Date", -}; - -static inline int cmp_header(const struct strbuf *line, const char *hdr) -{ - int len = strlen(hdr); - return !strncasecmp(line->buf, hdr, len) && line->len > len && - line->buf[len] == ':' && isspace(line->buf[len + 1]); -} - -static int is_format_patch_separator(const char *line, int len) -{ - static const char SAMPLE[] = - "From e6807f3efca28b30decfecb1732a56c7db1137ee Mon Sep 17 00:00:00 2001\n"; - const char *cp; - - if (len != strlen(SAMPLE)) - return 0; - if (!skip_prefix(line, "From ", &cp)) - return 0; - if (strspn(cp, "0123456789abcdef") != 40) - return 0; - cp += 40; - return !memcmp(SAMPLE + (cp - line), cp, strlen(SAMPLE) - (cp - line)); -} - -static int check_header(const struct strbuf *line, - struct strbuf *hdr_data[], int overwrite) -{ - int i, ret = 0, len; - struct strbuf sb = STRBUF_INIT; - /* search for the interesting parts */ - for (i = 0; header[i]; i++) { - int len = strlen(header[i]); - if ((!hdr_data[i] || overwrite) && cmp_header(line, header[i])) { - /* Unwrap inline B and Q encoding, and optionally - * normalize the meta information to utf8. - */ - strbuf_add(&sb, line->buf + len + 2, line->len - len - 2); - decode_header(&sb); - handle_header(&hdr_data[i], &sb); - ret = 1; - goto check_header_out; - } - } - - /* Content stuff */ - if (cmp_header(line, "Content-Type")) { - len = strlen("Content-Type: "); - strbuf_add(&sb, line->buf + len, line->len - len); - decode_header(&sb); - strbuf_insert(&sb, 0, "Content-Type: ", len); - handle_content_type(&sb); - ret = 1; - goto check_header_out; - } - if (cmp_header(line, "Content-Transfer-Encoding")) { - len = strlen("Content-Transfer-Encoding: "); - strbuf_add(&sb, line->buf + len, line->len - len); - decode_header(&sb); - handle_content_transfer_encoding(&sb); - ret = 1; - goto check_header_out; - } - if (cmp_header(line, "Message-Id")) { - len = strlen("Message-Id: "); - strbuf_add(&sb, line->buf + len, line->len - len); - decode_header(&sb); - handle_message_id(&sb); - ret = 1; - goto check_header_out; - } - - /* for inbody stuff */ - if (starts_with(line->buf, ">From") && isspace(line->buf[5])) { - ret = is_format_patch_separator(line->buf + 1, line->len - 1); - goto check_header_out; - } - if (starts_with(line->buf, "[PATCH]") && isspace(line->buf[7])) { - for (i = 0; header[i]; i++) { - if (!strcmp("Subject", header[i])) { - handle_header(&hdr_data[i], line); - ret = 1; - goto check_header_out; - } - } - } - -check_header_out: - strbuf_release(&sb); - return ret; -} - -static int is_rfc2822_header(const struct strbuf *line) -{ - /* - * The section that defines the loosest possible - * field name is "3.6.8 Optional fields". - * - * optional-field = field-name ":" unstructured CRLF - * field-name = 1*ftext - * ftext = %d33-57 / %59-126 - */ - int ch; - char *cp = line->buf; - - /* Count mbox From headers as headers */ - if (starts_with(cp, "From ") || starts_with(cp, ">From ")) - return 1; - - while ((ch = *cp++)) { - if (ch == ':') - return 1; - if ((33 <= ch && ch <= 57) || - (59 <= ch && ch <= 126)) - continue; - break; - } - return 0; -} - -static int read_one_header_line(struct strbuf *line, FILE *in) -{ - /* Get the first part of the line. */ - if (strbuf_getline(line, in, '\n')) - return 0; - - /* - * Is it an empty line or not a valid rfc2822 header? - * If so, stop here, and return false ("not a header") - */ - strbuf_rtrim(line); - if (!line->len || !is_rfc2822_header(line)) { - /* Re-add the newline */ - strbuf_addch(line, '\n'); - return 0; - } - - /* - * Now we need to eat all the continuation lines.. - * Yuck, 2822 header "folding" - */ - for (;;) { - int peek; - struct strbuf continuation = STRBUF_INIT; - - peek = fgetc(in); ungetc(peek, in); - if (peek != ' ' && peek != '\t') - break; - if (strbuf_getline(&continuation, in, '\n')) - break; - continuation.buf[0] = ' '; - strbuf_rtrim(&continuation); - strbuf_addbuf(line, &continuation); - } - - return 1; -} - -static struct strbuf *decode_q_segment(const struct strbuf *q_seg, int rfc2047) -{ - const char *in = q_seg->buf; - int c; - struct strbuf *out = xmalloc(sizeof(struct strbuf)); - strbuf_init(out, q_seg->len); - - while ((c = *in++) != 0) { - if (c == '=') { - int d = *in++; - if (d == '\n' || !d) - break; /* drop trailing newline */ - strbuf_addch(out, (hexval(d) << 4) | hexval(*in++)); - continue; - } - if (rfc2047 && c == '_') /* rfc2047 4.2 (2) */ - c = 0x20; - strbuf_addch(out, c); - } - return out; -} - -static struct strbuf *decode_b_segment(const struct strbuf *b_seg) -{ - /* Decode in..ep, possibly in-place to ot */ - int c, pos = 0, acc = 0; - const char *in = b_seg->buf; - struct strbuf *out = xmalloc(sizeof(struct strbuf)); - strbuf_init(out, b_seg->len); - - while ((c = *in++) != 0) { - if (c == '+') - c = 62; - else if (c == '/') - c = 63; - else if ('A' <= c && c <= 'Z') - c -= 'A'; - else if ('a' <= c && c <= 'z') - c -= 'a' - 26; - else if ('0' <= c && c <= '9') - c -= '0' - 52; - else - continue; /* garbage */ - switch (pos++) { - case 0: - acc = (c << 2); - break; - case 1: - strbuf_addch(out, (acc | (c >> 4))); - acc = (c & 15) << 4; - break; - case 2: - strbuf_addch(out, (acc | (c >> 2))); - acc = (c & 3) << 6; - break; - case 3: - strbuf_addch(out, (acc | c)); - acc = pos = 0; - break; - } - } - return out; -} - -static void convert_to_utf8(struct strbuf *line, const char *charset) -{ - char *out; - - if (!charset || !*charset) - return; - - if (same_encoding(metainfo_charset, charset)) - return; - out = reencode_string(line->buf, metainfo_charset, charset); - if (!out) - die("cannot convert from %s to %s", - charset, metainfo_charset); - strbuf_attach(line, out, strlen(out), strlen(out)); -} - -static int decode_header_bq(struct strbuf *it) -{ - char *in, *ep, *cp; - struct strbuf outbuf = STRBUF_INIT, *dec; - struct strbuf charset_q = STRBUF_INIT, piecebuf = STRBUF_INIT; - int rfc2047 = 0; - - in = it->buf; - while (in - it->buf <= it->len && (ep = strstr(in, "=?")) != NULL) { - int encoding; - strbuf_reset(&charset_q); - strbuf_reset(&piecebuf); - rfc2047 = 1; - - if (in != ep) { - /* - * We are about to process an encoded-word - * that begins at ep, but there is something - * before the encoded word. - */ - char *scan; - for (scan = in; scan < ep; scan++) - if (!isspace(*scan)) - break; - - if (scan != ep || in == it->buf) { - /* - * We should not lose that "something", - * unless we have just processed an - * encoded-word, and there is only LWS - * before the one we are about to process. - */ - strbuf_add(&outbuf, in, ep - in); - } - } - /* E.g. - * ep : "=?iso-2022-jp?B?GyR...?= foo" - * ep : "=?ISO-8859-1?Q?Foo=FCbar?= baz" - */ - ep += 2; - - if (ep - it->buf >= it->len || !(cp = strchr(ep, '?'))) - goto decode_header_bq_out; - - if (cp + 3 - it->buf > it->len) - goto decode_header_bq_out; - strbuf_add(&charset_q, ep, cp - ep); - - encoding = cp[1]; - if (!encoding || cp[2] != '?') - goto decode_header_bq_out; - ep = strstr(cp + 3, "?="); - if (!ep) - goto decode_header_bq_out; - strbuf_add(&piecebuf, cp + 3, ep - cp - 3); - switch (tolower(encoding)) { - default: - goto decode_header_bq_out; - case 'b': - dec = decode_b_segment(&piecebuf); - break; - case 'q': - dec = decode_q_segment(&piecebuf, 1); - break; - } - if (metainfo_charset) - convert_to_utf8(dec, charset_q.buf); - - strbuf_addbuf(&outbuf, dec); - strbuf_release(dec); - free(dec); - in = ep + 2; - } - strbuf_addstr(&outbuf, in); - strbuf_reset(it); - strbuf_addbuf(it, &outbuf); -decode_header_bq_out: - strbuf_release(&outbuf); - strbuf_release(&charset_q); - strbuf_release(&piecebuf); - return rfc2047; -} - -static void decode_header(struct strbuf *it) -{ - if (decode_header_bq(it)) - return; - /* otherwise "it" is a straight copy of the input. - * This can be binary guck but there is no charset specified. - */ - if (metainfo_charset) - convert_to_utf8(it, ""); -} - -static void decode_transfer_encoding(struct strbuf *line) -{ - struct strbuf *ret; - - switch (transfer_encoding) { - case TE_QP: - ret = decode_q_segment(line, 0); - break; - case TE_BASE64: - ret = decode_b_segment(line); - break; - case TE_DONTCARE: - default: - return; - } - strbuf_reset(line); - strbuf_addbuf(line, ret); - strbuf_release(ret); - free(ret); -} - -static void handle_filter(struct strbuf *line); - -static int find_boundary(void) -{ - while (!strbuf_getline(&line, fin, '\n')) { - if (*content_top && is_multipart_boundary(&line)) - return 1; - } - return 0; -} - -static int handle_boundary(void) -{ - struct strbuf newline = STRBUF_INIT; - - strbuf_addch(&newline, '\n'); -again: - if (line.len >= (*content_top)->len + 2 && - !memcmp(line.buf + (*content_top)->len, "--", 2)) { - /* we hit an end boundary */ - /* pop the current boundary off the stack */ - strbuf_release(*content_top); - free(*content_top); - *content_top = NULL; - - /* technically won't happen as is_multipart_boundary() - will fail first. But just in case.. - */ - if (--content_top < content) { - fprintf(stderr, "Detected mismatched boundaries, " - "can't recover\n"); - exit(1); - } - handle_filter(&newline); - strbuf_release(&newline); - - /* skip to the next boundary */ - if (!find_boundary()) - return 0; - goto again; - } - - /* set some defaults */ - transfer_encoding = TE_DONTCARE; - strbuf_reset(&charset); - - /* slurp in this section's info */ - while (read_one_header_line(&line, fin)) - check_header(&line, p_hdr_data, 0); - - strbuf_release(&newline); - /* replenish line */ - if (strbuf_getline(&line, fin, '\n')) - return 0; - strbuf_addch(&line, '\n'); - return 1; -} - -static inline int patchbreak(const struct strbuf *line) -{ - size_t i; - - /* Beginning of a "diff -" header? */ - if (starts_with(line->buf, "diff -")) - return 1; - - /* CVS "Index: " line? */ - if (starts_with(line->buf, "Index: ")) - return 1; - - /* - * "--- <filename>" starts patches without headers - * "---<sp>*" is a manual separator - */ - if (line->len < 4) - return 0; - - if (starts_with(line->buf, "---")) { - /* space followed by a filename? */ - if (line->buf[3] == ' ' && !isspace(line->buf[4])) - return 1; - /* Just whitespace? */ - for (i = 3; i < line->len; i++) { - unsigned char c = line->buf[i]; - if (c == '\n') - return 1; - if (!isspace(c)) - break; - } - return 0; - } - return 0; -} - -static int is_scissors_line(const struct strbuf *line) -{ - size_t i, len = line->len; - int scissors = 0, gap = 0; - int first_nonblank = -1; - int last_nonblank = 0, visible, perforation = 0, in_perforation = 0; - const char *buf = line->buf; - - for (i = 0; i < len; i++) { - if (isspace(buf[i])) { - if (in_perforation) { - perforation++; - gap++; - } - continue; - } - last_nonblank = i; - if (first_nonblank < 0) - first_nonblank = i; - if (buf[i] == '-') { - in_perforation = 1; - perforation++; - continue; - } - if (i + 1 < len && - (!memcmp(buf + i, ">8", 2) || !memcmp(buf + i, "8<", 2) || - !memcmp(buf + i, ">%", 2) || !memcmp(buf + i, "%<", 2))) { - in_perforation = 1; - perforation += 2; - scissors += 2; - i++; - continue; - } - in_perforation = 0; - } - - /* - * The mark must be at least 8 bytes long (e.g. "-- >8 --"). - * Even though there can be arbitrary cruft on the same line - * (e.g. "cut here"), in order to avoid misidentification, the - * perforation must occupy more than a third of the visible - * width of the line, and dashes and scissors must occupy more - * than half of the perforation. - */ - - visible = last_nonblank - first_nonblank + 1; - return (scissors && 8 <= visible && - visible < perforation * 3 && - gap * 2 < perforation); -} - -static int handle_commit_msg(struct strbuf *line) -{ - static int still_looking = 1; - - if (!cmitmsg) - return 0; - - if (still_looking) { - if (!line->len || (line->len == 1 && line->buf[0] == '\n')) - return 0; - } - - if (use_inbody_headers && still_looking) { - still_looking = check_header(line, s_hdr_data, 0); - if (still_looking) - return 0; - } else - /* Only trim the first (blank) line of the commit message - * when ignoring in-body headers. - */ - still_looking = 0; - - /* normalize the log message to UTF-8. */ - if (metainfo_charset) - convert_to_utf8(line, charset.buf); - - if (use_scissors && is_scissors_line(line)) { - int i; - if (fseek(cmitmsg, 0L, SEEK_SET)) - die_errno("Could not rewind output message file"); - if (ftruncate(fileno(cmitmsg), 0)) - die_errno("Could not truncate output message file at scissors"); - still_looking = 1; - - /* - * We may have already read "secondary headers"; purge - * them to give ourselves a clean restart. - */ - for (i = 0; header[i]; i++) { - if (s_hdr_data[i]) - strbuf_release(s_hdr_data[i]); - s_hdr_data[i] = NULL; - } - return 0; - } - - if (patchbreak(line)) { - if (message_id) - fprintf(cmitmsg, "Message-Id: %s\n", message_id); - fclose(cmitmsg); - cmitmsg = NULL; - return 1; - } - - fputs(line->buf, cmitmsg); - return 0; -} - -static void handle_patch(const struct strbuf *line) -{ - fwrite(line->buf, 1, line->len, patchfile); - patch_lines++; -} - -static void handle_filter(struct strbuf *line) -{ - static int filter = 0; - - /* filter tells us which part we left off on */ - switch (filter) { - case 0: - if (!handle_commit_msg(line)) - break; - filter++; - case 1: - handle_patch(line); - break; - } -} - -static void handle_body(void) -{ - struct strbuf prev = STRBUF_INIT; - - /* Skip up to the first boundary */ - if (*content_top) { - if (!find_boundary()) - goto handle_body_out; - } - - do { - /* process any boundary lines */ - if (*content_top && is_multipart_boundary(&line)) { - /* flush any leftover */ - if (prev.len) { - handle_filter(&prev); - strbuf_reset(&prev); - } - if (!handle_boundary()) - goto handle_body_out; - } - - /* Unwrap transfer encoding */ - decode_transfer_encoding(&line); - - switch (transfer_encoding) { - case TE_BASE64: - case TE_QP: - { - struct strbuf **lines, **it, *sb; - - /* Prepend any previous partial lines */ - strbuf_insert(&line, 0, prev.buf, prev.len); - strbuf_reset(&prev); - - /* - * This is a decoded line that may contain - * multiple new lines. Pass only one chunk - * at a time to handle_filter() - */ - lines = strbuf_split(&line, '\n'); - for (it = lines; (sb = *it); it++) { - if (*(it + 1) == NULL) /* The last line */ - if (sb->buf[sb->len - 1] != '\n') { - /* Partial line, save it for later. */ - strbuf_addbuf(&prev, sb); - break; - } - handle_filter(sb); - } - /* - * The partial chunk is saved in "prev" and will be - * appended by the next iteration of read_line_with_nul(). - */ - strbuf_list_free(lines); - break; - } - default: - handle_filter(&line); - } - - } while (!strbuf_getwholeline(&line, fin, '\n')); - -handle_body_out: - strbuf_release(&prev); -} - -static void output_header_lines(FILE *fout, const char *hdr, const struct strbuf *data) -{ - const char *sp = data->buf; - while (1) { - char *ep = strchr(sp, '\n'); - int len; - if (!ep) - len = strlen(sp); - else - len = ep - sp; - fprintf(fout, "%s: %.*s\n", hdr, len, sp); - if (!ep) - break; - sp = ep + 1; - } -} - -static void handle_info(void) -{ - struct strbuf *hdr; - int i; - - for (i = 0; header[i]; i++) { - /* only print inbody headers if we output a patch file */ - if (patch_lines && s_hdr_data[i]) - hdr = s_hdr_data[i]; - else if (p_hdr_data[i]) - hdr = p_hdr_data[i]; - else - continue; - - if (!strcmp(header[i], "Subject")) { - if (!keep_subject) { - cleanup_subject(hdr); - cleanup_space(hdr); - } - output_header_lines(fout, "Subject", hdr); - } else if (!strcmp(header[i], "From")) { - cleanup_space(hdr); - handle_from(hdr); - fprintf(fout, "Author: %s\n", name.buf); - fprintf(fout, "Email: %s\n", email.buf); - } else { - cleanup_space(hdr); - fprintf(fout, "%s: %s\n", header[i], hdr->buf); - } - } - fprintf(fout, "\n"); -} - -static int mailinfo(FILE *in, FILE *out, const char *msg, const char *patch) -{ - int peek; - fin = in; - fout = out; - - cmitmsg = fopen(msg, "w"); - if (!cmitmsg) { - perror(msg); - return -1; - } - patchfile = fopen(patch, "w"); - if (!patchfile) { - perror(patch); - fclose(cmitmsg); - return -1; - } - - p_hdr_data = xcalloc(MAX_HDR_PARSED, sizeof(*p_hdr_data)); - s_hdr_data = xcalloc(MAX_HDR_PARSED, sizeof(*s_hdr_data)); - - do { - peek = fgetc(in); - } while (isspace(peek)); - ungetc(peek, in); - - /* process the email header */ - while (read_one_header_line(&line, fin)) - check_header(&line, p_hdr_data, 1); - - handle_body(); - handle_info(); - - return 0; -} +static const char mailinfo_usage[] = + "git mailinfo [-k | -b] [-m | --message-id] [-u | --encoding=<encoding> | -n] [--scissors | --no-scissors] <msg> <patch> < mail >info"; -static int git_mailinfo_config(const char *var, const char *value, void *unused) +static char *prefix_copy(const char *prefix, const char *filename) { - if (!starts_with(var, "mailinfo.")) - return git_default_config(var, value, unused); - if (!strcmp(var, "mailinfo.scissors")) { - use_scissors = git_config_bool(var, value); - return 0; - } - /* perhaps others here */ - return 0; + if (!prefix || is_absolute_path(filename)) + return xstrdup(filename); + return xstrdup(prefix_filename(prefix, strlen(prefix), filename)); } -static const char mailinfo_usage[] = - "git mailinfo [-k | -b] [-m | --message-id] [-u | --encoding=<encoding> | -n] [--scissors | --no-scissors] <msg> <patch> < mail >info"; - int cmd_mailinfo(int argc, const char **argv, const char *prefix) { const char *def_charset; + struct mailinfo mi; + int status; + char *msgfile, *patchfile; - /* NEEDSWORK: might want to do the optional .git/ directory - * discovery - */ - git_config(git_mailinfo_config, NULL); + setup_mailinfo(&mi); def_charset = get_commit_output_encoding(); - metainfo_charset = def_charset; + mi.metainfo_charset = def_charset; while (1 < argc && argv[1][0] == '-') { if (!strcmp(argv[1], "-k")) - keep_subject = 1; + mi.keep_subject = 1; else if (!strcmp(argv[1], "-b")) - keep_non_patch_brackets_in_subject = 1; + mi.keep_non_patch_brackets_in_subject = 1; else if (!strcmp(argv[1], "-m") || !strcmp(argv[1], "--message-id")) - add_message_id = 1; + mi.add_message_id = 1; else if (!strcmp(argv[1], "-u")) - metainfo_charset = def_charset; + mi.metainfo_charset = def_charset; else if (!strcmp(argv[1], "-n")) - metainfo_charset = NULL; + mi.metainfo_charset = NULL; else if (starts_with(argv[1], "--encoding=")) - metainfo_charset = argv[1] + 11; + mi.metainfo_charset = argv[1] + 11; else if (!strcmp(argv[1], "--scissors")) - use_scissors = 1; + mi.use_scissors = 1; else if (!strcmp(argv[1], "--no-scissors")) - use_scissors = 0; + mi.use_scissors = 0; else if (!strcmp(argv[1], "--no-inbody-headers")) - use_inbody_headers = 0; + mi.use_inbody_headers = 0; else usage(mailinfo_usage); argc--; argv++; @@ -1072,5 +57,16 @@ int cmd_mailinfo(int argc, const char **argv, const char *prefix) if (argc != 3) usage(mailinfo_usage); - return !!mailinfo(stdin, stdout, argv[1], argv[2]); + mi.input = stdin; + mi.output = stdout; + + msgfile = prefix_copy(prefix, argv[1]); + patchfile = prefix_copy(prefix, argv[2]); + + status = !!mailinfo(&mi, msgfile, patchfile); + clear_mailinfo(&mi); + + free(msgfile); + free(patchfile); + return status; } diff --git a/builtin/mailsplit.c b/builtin/mailsplit.c index 8e02ea109a..30681681c1 100644 --- a/builtin/mailsplit.c +++ b/builtin/mailsplit.c @@ -45,6 +45,19 @@ static int is_from_line(const char *line, int len) static struct strbuf buf = STRBUF_INIT; static int keep_cr; +static int mboxrd; + +static int is_gtfrom(const struct strbuf *buf) +{ + size_t min = strlen(">From "); + size_t ngt; + + if (buf->len < min) + return 0; + + ngt = strspn(buf->buf, ">"); + return ngt && starts_with(buf->buf + ngt, "From "); +} /* Called with the first line (potentially partial) * already in buf[] -- normally that should begin with @@ -77,6 +90,9 @@ static int split_one(FILE *mbox, const char *name, int allow_bare) strbuf_addch(&buf, '\n'); } + if (mboxrd && is_gtfrom(&buf)) + strbuf_remove(&buf, 0, 1); + if (fwrite(buf.buf, 1, buf.len, output) != buf.len) die_errno("cannot write output"); @@ -98,30 +114,37 @@ static int populate_maildir_list(struct string_list *list, const char *path) { DIR *dir; struct dirent *dent; - char name[PATH_MAX]; + char *name = NULL; char *subs[] = { "cur", "new", NULL }; char **sub; + int ret = -1; for (sub = subs; *sub; ++sub) { - snprintf(name, sizeof(name), "%s/%s", path, *sub); + free(name); + name = xstrfmt("%s/%s", path, *sub); if ((dir = opendir(name)) == NULL) { if (errno == ENOENT) continue; - error("cannot opendir %s (%s)", name, strerror(errno)); - return -1; + error_errno("cannot opendir %s", name); + goto out; } while ((dent = readdir(dir)) != NULL) { if (dent->d_name[0] == '.') continue; - snprintf(name, sizeof(name), "%s/%s", *sub, dent->d_name); + free(name); + name = xstrfmt("%s/%s", *sub, dent->d_name); string_list_insert(list, name); } closedir(dir); } - return 0; + ret = 0; + +out: + free(name); + return ret; } static int maildir_filename_cmp(const char *a, const char *b) @@ -148,8 +171,8 @@ static int maildir_filename_cmp(const char *a, const char *b) static int split_maildir(const char *maildir, const char *dir, int nr_prec, int skip) { - char file[PATH_MAX]; - char name[PATH_MAX]; + char *file = NULL; + FILE *f = NULL; int ret = -1; int i; struct string_list list = STRING_LIST_INIT_DUP; @@ -160,27 +183,35 @@ static int split_maildir(const char *maildir, const char *dir, goto out; for (i = 0; i < list.nr; i++) { - FILE *f; - snprintf(file, sizeof(file), "%s/%s", maildir, list.items[i].string); + char *name; + + free(file); + file = xstrfmt("%s/%s", maildir, list.items[i].string); + f = fopen(file, "r"); if (!f) { - error("cannot open mail %s (%s)", file, strerror(errno)); + error_errno("cannot open mail %s", file); goto out; } if (strbuf_getwholeline(&buf, f, '\n')) { - error("cannot read mail %s (%s)", file, strerror(errno)); + error_errno("cannot read mail %s", file); goto out; } - sprintf(name, "%s/%0*d", dir, nr_prec, ++skip); + name = xstrfmt("%s/%0*d", dir, nr_prec, ++skip); split_one(f, name, 1); + free(name); fclose(f); + f = NULL; } ret = skip; out: + if (f) + fclose(f); + free(file); string_list_clear(&list, 1); return ret; } @@ -188,7 +219,6 @@ out: static int split_mbox(const char *file, const char *dir, int allow_bare, int nr_prec, int skip) { - char name[PATH_MAX]; int ret = -1; int peek; @@ -196,7 +226,7 @@ static int split_mbox(const char *file, const char *dir, int allow_bare, int file_done = 0; if (!f) { - error("cannot open mbox %s", file); + error_errno("cannot open mbox %s", file); goto out; } @@ -215,8 +245,9 @@ static int split_mbox(const char *file, const char *dir, int allow_bare, } while (!file_done) { - sprintf(name, "%s/%0*d", dir, nr_prec, ++skip); + char *name = xstrfmt("%s/%0*d", dir, nr_prec, ++skip); file_done = split_one(f, name, allow_bare); + free(name); } if (f != stdin) @@ -256,6 +287,8 @@ int cmd_mailsplit(int argc, const char **argv, const char *prefix) keep_cr = 1; } else if ( arg[1] == 'o' && arg[2] ) { dir = arg+2; + } else if (!strcmp(arg, "--mboxrd")) { + mboxrd = 1; } else if ( arg[1] == '-' && !arg[2] ) { argp++; /* -- marks end of options */ break; @@ -303,7 +336,7 @@ int cmd_mailsplit(int argc, const char **argv, const char *prefix) } if (stat(arg, &argstat) == -1) { - error("cannot stat %s (%s)", arg, strerror(errno)); + error_errno("cannot stat %s", arg); return 1; } diff --git a/builtin/merge-base.c b/builtin/merge-base.c index 08a8217890..b572a37c26 100644 --- a/builtin/merge-base.c +++ b/builtin/merge-base.c @@ -16,7 +16,7 @@ static int show_merge_base(struct commit **rev, int rev_nr, int show_all) return 1; while (result) { - printf("%s\n", sha1_to_hex(result->item->object.sha1)); + printf("%s\n", oid_to_hex(&result->item->object.oid)); if (!show_all) return 0; result = result->next; @@ -62,7 +62,7 @@ static int handle_independent(int count, const char **args) return 1; while (result) { - printf("%s\n", sha1_to_hex(result->item->object.sha1)); + printf("%s\n", oid_to_hex(&result->item->object.oid)); result = result->next; } return 0; @@ -83,7 +83,7 @@ static int handle_octopus(int count, const char **args, int show_all) return 1; while (result) { - printf("%s\n", sha1_to_hex(result->item->object.sha1)); + printf("%s\n", oid_to_hex(&result->item->object.oid)); if (!show_all) return 0; result = result->next; @@ -173,6 +173,9 @@ static int handle_fork_point(int argc, const char **argv) revs.initial = 1; for_each_reflog_ent(refname, collect_one_reflog_ent, &revs); + if (!revs.nr && !get_sha1(refname, sha1)) + add_one_commit(sha1, &revs); + for (i = 0; i < revs.nr; i++) revs.commit[i]->object.flags &= ~TMP_MARK; @@ -196,7 +199,7 @@ static int handle_fork_point(int argc, const char **argv) goto cleanup_return; } - printf("%s\n", sha1_to_hex(bases->item->object.sha1)); + printf("%s\n", oid_to_hex(&bases->item->object.oid)); cleanup_return: free_commit_list(bases); @@ -252,7 +255,7 @@ int cmd_merge_base(int argc, const char **argv, const char *prefix) if (argc < 2) usage_with_options(merge_base_usage, options); - rev = xmalloc(argc * sizeof(*rev)); + ALLOC_ARRAY(rev, argc); while (argc-- > 0) rev[rev_nr++] = get_commit_reference(*argv++); return show_merge_base(rev, rev_nr, show_all); diff --git a/builtin/merge-file.c b/builtin/merge-file.c index ea8093f676..13e22a2f0b 100644 --- a/builtin/merge-file.c +++ b/builtin/merge-file.c @@ -62,8 +62,7 @@ int cmd_merge_file(int argc, const char **argv, const char *prefix) usage_with_options(merge_file_usage, options); if (quiet) { if (!freopen("/dev/null", "w", stderr)) - return error("failed to redirect stderr to /dev/null: " - "%s", strerror(errno)); + return error_errno("failed to redirect stderr to /dev/null"); } if (prefix) @@ -75,7 +74,8 @@ int cmd_merge_file(int argc, const char **argv, const char *prefix) names[i] = argv[i]; if (read_mmfile(mmfs + i, fname)) return -1; - if (buffer_is_binary(mmfs[i].ptr, mmfs[i].size)) + if (mmfs[i].size > MAX_XDIFF_SIZE || + buffer_is_binary(mmfs[i].ptr, mmfs[i].size)) return error("Cannot merge binary files: %s", argv[i]); } @@ -94,14 +94,18 @@ int cmd_merge_file(int argc, const char **argv, const char *prefix) FILE *f = to_stdout ? stdout : fopen(fpath, "wb"); if (!f) - ret = error("Could not open %s for writing", filename); + ret = error_errno("Could not open %s for writing", + filename); else if (result.size && fwrite(result.ptr, result.size, 1, f) != 1) - ret = error("Could not write to %s", filename); + ret = error_errno("Could not write to %s", filename); else if (fclose(f)) - ret = error("Could not close %s", filename); + ret = error_errno("Could not close %s", filename); free(result.ptr); } + if (ret > 127) + ret = 127; + return ret; } diff --git a/builtin/merge-index.c b/builtin/merge-index.c index 1a1eafa6fd..ce356b1da1 100644 --- a/builtin/merge-index.c +++ b/builtin/merge-index.c @@ -9,7 +9,7 @@ static int merge_entry(int pos, const char *path) { int found; const char *arguments[] = { pgm, "", "", "", path, "", "", "", NULL }; - char hexbuf[4][60]; + char hexbuf[4][GIT_SHA1_HEXSZ + 1]; char ownbuf[4][60]; if (pos >= active_nr) @@ -22,8 +22,8 @@ static int merge_entry(int pos, const char *path) if (strcmp(ce->name, path)) break; found++; - strcpy(hexbuf[stage], sha1_to_hex(ce->sha1)); - sprintf(ownbuf[stage], "%o", ce->ce_mode); + sha1_to_hex_r(hexbuf[stage], ce->oid.hash); + xsnprintf(ownbuf[stage], sizeof(ownbuf[stage]), "%o", ce->ce_mode); arguments[stage] = hexbuf[stage]; arguments[stage + 4] = ownbuf[stage]; } while (++pos < active_nr); diff --git a/builtin/merge-recursive.c b/builtin/merge-recursive.c index a90f28f34d..0dd9021958 100644 --- a/builtin/merge-recursive.c +++ b/builtin/merge-recursive.c @@ -9,22 +9,22 @@ static const char builtin_merge_recursive_usage[] = static const char *better_branch_name(const char *branch) { - static char githead_env[8 + 40 + 1]; + static char githead_env[8 + GIT_SHA1_HEXSZ + 1]; char *name; - if (strlen(branch) != 40) + if (strlen(branch) != GIT_SHA1_HEXSZ) return branch; - sprintf(githead_env, "GITHEAD_%s", branch); + xsnprintf(githead_env, sizeof(githead_env), "GITHEAD_%s", branch); name = getenv(githead_env); return name ? name : branch; } int cmd_merge_recursive(int argc, const char **argv, const char *prefix) { - const unsigned char *bases[21]; + const struct object_id *bases[21]; unsigned bases_count = 0; int i, failed; - unsigned char h1[20], h2[20]; + struct object_id h1, h2; struct merge_options o; struct commit *result; @@ -42,38 +42,41 @@ int cmd_merge_recursive(int argc, const char **argv, const char *prefix) if (!arg[2]) break; if (parse_merge_opt(&o, arg + 2)) - die("Unknown option %s", arg); + die(_("unknown option %s"), arg); continue; } if (bases_count < ARRAY_SIZE(bases)-1) { - unsigned char *sha = xmalloc(20); - if (get_sha1(argv[i], sha)) - die("Could not parse object '%s'", argv[i]); - bases[bases_count++] = sha; + struct object_id *oid = xmalloc(sizeof(struct object_id)); + if (get_oid(argv[i], oid)) + die(_("could not parse object '%s'"), argv[i]); + bases[bases_count++] = oid; } else - warning("Cannot handle more than %d bases. " - "Ignoring %s.", + warning(Q_("cannot handle more than %d base. " + "Ignoring %s.", + "cannot handle more than %d bases. " + "Ignoring %s.", + (int)ARRAY_SIZE(bases)-1), (int)ARRAY_SIZE(bases)-1, argv[i]); } if (argc - i != 3) /* "--" "<head>" "<remote>" */ - die("Not handling anything other than two heads merge."); + die(_("not handling anything other than two heads merge.")); o.branch1 = argv[++i]; o.branch2 = argv[++i]; - if (get_sha1(o.branch1, h1)) - die("Could not resolve ref '%s'", o.branch1); - if (get_sha1(o.branch2, h2)) - die("Could not resolve ref '%s'", o.branch2); + if (get_oid(o.branch1, &h1)) + die(_("could not resolve ref '%s'"), o.branch1); + if (get_oid(o.branch2, &h2)) + die(_("could not resolve ref '%s'"), o.branch2); o.branch1 = better_branch_name(o.branch1); o.branch2 = better_branch_name(o.branch2); if (o.verbosity >= 3) - printf("Merging %s with %s\n", o.branch1, o.branch2); + printf(_("Merging %s with %s\n"), o.branch1, o.branch2); - failed = merge_recursive_generic(&o, h1, h2, bases_count, bases, &result); + failed = merge_recursive_generic(&o, &h1, &h2, bases_count, bases, &result); if (failed < 0) return 128; /* die() error code */ return failed; diff --git a/builtin/merge-tree.c b/builtin/merge-tree.c index f9ab48597e..5b7ab9b967 100644 --- a/builtin/merge-tree.c +++ b/builtin/merge-tree.c @@ -60,7 +60,7 @@ static void *result(struct merge_list *entry, unsigned long *size) const char *path = entry->path; if (!entry->stage) - return read_sha1_file(entry->blob->object.sha1, &type, size); + return read_sha1_file(entry->blob->object.oid.hash, &type, size); base = NULL; if (entry->stage == 1) { base = entry->blob; @@ -82,7 +82,7 @@ static void *origin(struct merge_list *entry, unsigned long *size) enum object_type type; while (entry) { if (entry->stage == 2) - return read_sha1_file(entry->blob->object.sha1, &type, size); + return read_sha1_file(entry->blob->object.oid.hash, &type, size); entry = entry->link; } return NULL; @@ -118,7 +118,8 @@ static void show_diff(struct merge_list *entry) if (!dst.ptr) size = 0; dst.size = size; - xdi_diff(&src, &dst, &xpp, &xecfg, &ecb); + if (xdi_diff(&src, &dst, &xpp, &xecfg, &ecb)) + die("unable to generate diff"); free(src.ptr); free(dst.ptr); } @@ -129,7 +130,7 @@ static void show_result_list(struct merge_list *entry) do { struct merge_list *link = entry->link; static const char *desc[4] = { "result", "base", "our", "their" }; - printf(" %-6s %o %s %s\n", desc[entry->stage], entry->mode, sha1_to_hex(entry->blob->object.sha1), entry->path); + printf(" %-6s %o %s %s\n", desc[entry->stage], entry->mode, oid_to_hex(&entry->blob->object.oid), entry->path); entry = link; } while (entry); } @@ -149,15 +150,15 @@ static void show_result(void) /* An empty entry never compares same, not even to another empty entry */ static int same_entry(struct name_entry *a, struct name_entry *b) { - return a->sha1 && - b->sha1 && - !hashcmp(a->sha1, b->sha1) && + return a->oid && + b->oid && + !oidcmp(a->oid, b->oid) && a->mode == b->mode; } static int both_empty(struct name_entry *a, struct name_entry *b) { - return !(a->sha1 || b->sha1); + return !(a->oid || b->oid); } static struct merge_list *create_entry(unsigned stage, unsigned mode, const unsigned char *sha1, const char *path) @@ -173,7 +174,7 @@ static struct merge_list *create_entry(unsigned stage, unsigned mode, const unsi static char *traverse_path(const struct traverse_info *info, const struct name_entry *n) { - char *path = xmalloc(traverse_path_len(info, n) + 1); + char *path = xmallocz(traverse_path_len(info, n)); return make_traverse_path(path, info, n); } @@ -187,8 +188,8 @@ static void resolve(const struct traverse_info *info, struct name_entry *ours, s return; path = traverse_path(info, result); - orig = create_entry(2, ours->mode, ours->sha1, path); - final = create_entry(0, result->mode, result->sha1, path); + orig = create_entry(2, ours->mode, ours->oid->hash, path); + final = create_entry(0, result->mode, result->oid->hash, path); final->link = orig; @@ -212,7 +213,7 @@ static void unresolved_directory(const struct traverse_info *info, newbase = traverse_path(info, p); -#define ENTRY_SHA1(e) (((e)->mode && S_ISDIR((e)->mode)) ? (e)->sha1 : NULL) +#define ENTRY_SHA1(e) (((e)->mode && S_ISDIR((e)->mode)) ? (e)->oid->hash : NULL) buf0 = fill_tree_descriptor(t+0, ENTRY_SHA1(n + 0)); buf1 = fill_tree_descriptor(t+1, ENTRY_SHA1(n + 1)); buf2 = fill_tree_descriptor(t+2, ENTRY_SHA1(n + 2)); @@ -238,7 +239,7 @@ static struct merge_list *link_entry(unsigned stage, const struct traverse_info path = entry->path; else path = traverse_path(info, n); - link = create_entry(stage, n->mode, n->sha1, path); + link = create_entry(stage, n->mode, n->oid->hash, path); link->link = entry; return link; } @@ -313,7 +314,7 @@ static int threeway_callback(int n, unsigned long mask, unsigned long dirmask, s } if (same_entry(entry+0, entry+1)) { - if (entry[2].sha1 && !S_ISDIR(entry[2].mode)) { + if (entry[2].oid && !S_ISDIR(entry[2].mode)) { /* We did not touch, they modified -- take theirs */ resolve(info, entry+1, entry+2); return mask; diff --git a/builtin/merge.c b/builtin/merge.c index 85c54dcd5a..b65eeaa87d 100644 --- a/builtin/merge.c +++ b/builtin/merge.c @@ -30,6 +30,7 @@ #include "fmt-merge-msg.h" #include "gpg-interface.h" #include "sequencer.h" +#include "string-list.h" #define DEFAULT_TWOHEAD (1<<0) #define DEFAULT_OCTOPUS (1<<1) @@ -64,6 +65,7 @@ static int option_renormalize; static int verbosity; static int allow_rerere_auto; static int abort_current_merge; +static int allow_unrelated_histories; static int show_progress = -1; static int default_to_upstream = 1; static const char *sign_commit; @@ -210,7 +212,7 @@ static struct option builtin_merge_options[] = { PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, FF_ONLY }, OPT_RERERE_AUTOUPDATE(&allow_rerere_auto), OPT_BOOL(0, "verify-signatures", &verify_signatures, - N_("Verify that the named commit has a valid GPG signature")), + N_("verify that the named commit has a valid GPG signature")), OPT_CALLBACK('s', "strategy", &use_strategies, N_("strategy"), N_("merge strategy to use"), option_parse_strategy), OPT_CALLBACK('X', "strategy-option", &xopts, N_("option=value"), @@ -221,6 +223,8 @@ static struct option builtin_merge_options[] = { OPT__VERBOSITY(&verbosity), OPT_BOOL(0, "abort", &abort_current_merge, N_("abort the current in-progress merge")), + OPT_BOOL(0, "allow-unrelated-histories", &allow_unrelated_histories, + N_("allow merging unrelated histories")), OPT_SET_INT(0, "progress", &show_progress, N_("force progress reporting"), 1), { OPTION_STRING, 'S', "gpg-sign", &sign_commit, N_("key-id"), N_("GPG sign commit"), PARSE_OPT_OPTARG, NULL, (intptr_t) "" }, @@ -231,9 +235,9 @@ static struct option builtin_merge_options[] = { /* Cleans up metadata that is uninteresting after a succeeded merge. */ static void drop_save(void) { - unlink(git_path("MERGE_HEAD")); - unlink(git_path("MERGE_MSG")); - unlink(git_path("MERGE_MODE")); + unlink(git_path_merge_head()); + unlink(git_path_merge_msg()); + unlink(git_path_merge_mode()); } static int save_state(unsigned char *stash) @@ -333,15 +337,9 @@ static void squash_message(struct commit *commit, struct commit_list *remotehead struct rev_info rev; struct strbuf out = STRBUF_INIT; struct commit_list *j; - const char *filename; - int fd; struct pretty_print_context ctx = {0}; printf(_("Squash commit -- not updating HEAD\n")); - filename = git_path("SQUASH_MSG"); - fd = open(filename, O_WRONLY | O_CREAT, 0666); - if (fd < 0) - die_errno(_("Could not write to '%s'"), filename); init_revisions(&rev, NULL); rev.ignore_merges = 1; @@ -365,13 +363,10 @@ static void squash_message(struct commit *commit, struct commit_list *remotehead while ((commit = get_revision(&rev)) != NULL) { strbuf_addch(&out, '\n'); strbuf_addf(&out, "commit %s\n", - sha1_to_hex(commit->object.sha1)); + oid_to_hex(&commit->object.oid)); pretty_print_commit(&ctx, commit, &out); } - if (write_in_full(fd, out.buf, out.len) != out.len) - die_errno(_("Writing SQUASH_MSG")); - if (close(fd)) - die_errno(_("Finishing SQUASH_MSG")); + write_file_buf(git_path_squash_msg(), out.buf, out.len); strbuf_release(&out); } @@ -380,7 +375,7 @@ static void finish(struct commit *head_commit, const unsigned char *new_head, const char *msg) { struct strbuf reflog_message = STRBUF_INIT; - const unsigned char *head = head_commit->object.sha1; + const unsigned char *head = head_commit->object.oid.hash; if (!msg) strbuf_addstr(&reflog_message, getenv("GIT_REFLOG_ACTION")); @@ -404,6 +399,7 @@ static void finish(struct commit *head_commit, * We ignore errors in 'gc --auto', since the * user should see them. */ + close_all_packs(); run_command_v_opt(argv_gc_auto, RUN_GIT_CMD); } } @@ -497,7 +493,7 @@ static void merge_name(const char *remote, struct strbuf *msg) if (ref_exists(truname.buf)) { strbuf_addf(msg, "%s\t\tbranch '%s'%s of .\n", - sha1_to_hex(remote_head->object.sha1), + oid_to_hex(&remote_head->object.oid), truname.buf + 11, (early ? " (early part)" : "")); strbuf_release(&truname); @@ -511,7 +507,7 @@ static void merge_name(const char *remote, struct strbuf *msg) desc = merge_remote_util(remote_head); if (desc && desc->obj && desc->obj->type == OBJ_TAG) { strbuf_addf(msg, "%s\t\t%s '%s'\n", - sha1_to_hex(desc->obj->sha1), + oid_to_hex(&desc->obj->oid), typename(desc->obj->type), remote); goto cleanup; @@ -519,7 +515,7 @@ static void merge_name(const char *remote, struct strbuf *msg) } strbuf_addf(msg, "%s\t\tcommit '%s'\n", - sha1_to_hex(remote_head->object.sha1), remote); + oid_to_hex(&remote_head->object.oid), remote); cleanup: strbuf_release(&buf); strbuf_release(&bname); @@ -678,6 +674,8 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common, hold_locked_index(&lock, 1); clean = merge_recursive(&o, head, remoteheads->item, reversed, &result); + if (clean < 0) + exit(128); if (active_cache_changed && write_locked_index(&the_index, &lock, COMMIT_LOCK)) die (_("unable to write %s"), get_index_file()); @@ -708,42 +706,17 @@ static int count_unmerged_entries(void) return ret; } -static void split_merge_strategies(const char *string, struct strategy **list, - int *nr, int *alloc) -{ - char *p, *q, *buf; - - if (!string) - return; - - buf = xstrdup(string); - q = buf; - for (;;) { - p = strchr(q, ' '); - if (!p) { - ALLOC_GROW(*list, *nr + 1, *alloc); - (*list)[(*nr)++].name = xstrdup(q); - free(buf); - return; - } else { - *p = '\0'; - ALLOC_GROW(*list, *nr + 1, *alloc); - (*list)[(*nr)++].name = xstrdup(q); - q = ++p; - } - } -} - static void add_strategies(const char *string, unsigned attr) { - struct strategy *list = NULL; - int list_alloc = 0, list_nr = 0, i; - - memset(&list, 0, sizeof(list)); - split_merge_strategies(string, &list, &list_nr, &list_alloc); - if (list) { - for (i = 0; i < list_nr; i++) - append_strategy(get_strategy(list[i].name)); + int i; + + if (string) { + struct string_list list = STRING_LIST_INIT_DUP; + struct string_list_item *item; + string_list_split(&list, string, ' ', -1); + for_each_string_list_item(item, &list) + append_strategy(get_strategy(item->string)); + string_list_clear(&list, 0); return; } for (i = 0; i < ARRAY_SIZE(all_strategy); i++) @@ -752,21 +725,9 @@ static void add_strategies(const char *string, unsigned attr) } -static void write_merge_msg(struct strbuf *msg) -{ - const char *filename = git_path("MERGE_MSG"); - int fd = open(filename, O_WRONLY | O_CREAT, 0666); - if (fd < 0) - die_errno(_("Could not open '%s' for writing"), - filename); - if (write_in_full(fd, msg->buf, msg->len) != msg->len) - die_errno(_("Could not write to '%s'"), filename); - close(fd); -} - static void read_merge_msg(struct strbuf *msg) { - const char *filename = git_path("MERGE_MSG"); + const char *filename = git_path_merge_msg(); strbuf_reset(msg); if (strbuf_read_file(msg, filename, 0) < 0) die_errno(_("Could not read from '%s'"), filename); @@ -797,16 +758,16 @@ static void prepare_to_commit(struct commit_list *remoteheads) strbuf_addch(&msg, '\n'); if (0 < option_edit) strbuf_commented_addf(&msg, _(merge_editor_comment), comment_line_char); - write_merge_msg(&msg); + write_file_buf(git_path_merge_msg(), msg.buf, msg.len); if (run_commit_hook(0 < option_edit, get_index_file(), "prepare-commit-msg", - git_path("MERGE_MSG"), "merge", NULL)) + git_path_merge_msg(), "merge", NULL)) abort_commit(remoteheads, NULL); if (0 < option_edit) { - if (launch_editor(git_path("MERGE_MSG"), NULL, NULL)) + if (launch_editor(git_path_merge_msg(), NULL, NULL)) abort_commit(remoteheads, NULL); } read_merge_msg(&msg); - stripspace(&msg, 0 < option_edit); + strbuf_stripspace(&msg, 0 < option_edit); if (!msg.len) abort_commit(remoteheads, _("Empty commit message.")); strbuf_release(&merge_msg); @@ -818,6 +779,14 @@ static int merge_trivial(struct commit *head, struct commit_list *remoteheads) { unsigned char result_tree[20], result_commit[20]; struct commit_list *parents, **pptr = &parents; + static struct lock_file lock; + + hold_locked_index(&lock, 1); + refresh_cache(REFRESH_QUIET); + if (active_cache_changed && + write_locked_index(&the_index, &lock, COMMIT_LOCK)) + return error(_("Unable to write index.")); + rollback_lock_file(&lock); write_tree_trivial(result_tree); printf(_("Wonderful.\n")); @@ -865,7 +834,7 @@ static int suggest_conflicts(void) FILE *fp; struct strbuf msgbuf = STRBUF_INIT; - filename = git_path("MERGE_MSG"); + filename = git_path_merge_msg(); fp = fopen(filename, "a"); if (!fp) die_errno(_("Could not open '%s' for writing"), filename); @@ -892,7 +861,7 @@ static struct commit *is_old_style_invocation(int argc, const char **argv, second_token = lookup_commit_reference_gently(second_sha1, 0); if (!second_token) die(_("'%s' is not a commit"), argv[1]); - if (hashcmp(second_token->object.sha1, head)) + if (hashcmp(second_token->object.oid.hash, head)) return NULL; } return second_token; @@ -938,7 +907,7 @@ static int setup_with_upstream(const char ***argv) if (!branch->merge_nr) die(_("No default upstream defined for the current branch.")); - args = xcalloc(branch->merge_nr + 1, sizeof(char *)); + args = xcalloc(st_add(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"), @@ -952,41 +921,27 @@ static int setup_with_upstream(const char ***argv) static void write_merge_state(struct commit_list *remoteheads) { - const char *filename; - int fd; struct commit_list *j; struct strbuf buf = STRBUF_INIT; for (j = remoteheads; j; j = j->next) { - unsigned const char *sha1; + struct object_id *oid; struct commit *c = j->item; if (c->util && merge_remote_util(c)->obj) { - sha1 = merge_remote_util(c)->obj->sha1; + oid = &merge_remote_util(c)->obj->oid; } else { - sha1 = c->object.sha1; + oid = &c->object.oid; } - strbuf_addf(&buf, "%s\n", sha1_to_hex(sha1)); + strbuf_addf(&buf, "%s\n", oid_to_hex(oid)); } - filename = git_path("MERGE_HEAD"); - fd = open(filename, O_WRONLY | O_CREAT, 0666); - if (fd < 0) - die_errno(_("Could not open '%s' for writing"), filename); - if (write_in_full(fd, buf.buf, buf.len) != buf.len) - die_errno(_("Could not write to '%s'"), filename); - close(fd); + write_file_buf(git_path_merge_head(), buf.buf, buf.len); strbuf_addch(&merge_msg, '\n'); - write_merge_msg(&merge_msg); + write_file_buf(git_path_merge_msg(), merge_msg.buf, merge_msg.len); - filename = git_path("MERGE_MODE"); - fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0666); - if (fd < 0) - die_errno(_("Could not open '%s' for writing"), filename); strbuf_reset(&buf); if (fast_forward == FF_NO) - strbuf_addf(&buf, "no-ff"); - if (write_in_full(fd, buf.buf, buf.len) != buf.len) - die_errno(_("Could not write to '%s'"), filename); - close(fd); + strbuf_addstr(&buf, "no-ff"); + write_file_buf(git_path_merge_mode(), buf.buf, buf.len); } static int default_edit_option(void) @@ -1002,7 +957,7 @@ static int default_edit_option(void) if (e) { int v = git_config_maybe_bool(name, e); if (v < 0) - die("Bad value '%s' in environment '%s'", e, name); + die(_("Bad value '%s' in environment '%s'"), e, name); return v; } @@ -1019,7 +974,7 @@ static struct commit_list *reduce_parents(struct commit *head_commit, int *head_subsumed, struct commit_list *remoteheads) { - struct commit_list *parents, *next, **remotes = &remoteheads; + struct commit_list *parents, **remotes; /* * Is the current HEAD reachable from another commit being @@ -1033,16 +988,14 @@ static struct commit_list *reduce_parents(struct commit *head_commit, /* Find what parents to record by checking independent ones. */ parents = reduce_heads(remoteheads); - for (remoteheads = NULL, remotes = &remoteheads; - parents; - parents = next) { - struct commit *commit = parents->item; - next = parents->next; + remoteheads = NULL; + remotes = &remoteheads; + while (parents) { + struct commit *commit = pop_commit(&parents); if (commit == head_commit) *head_subsumed = 0; else remotes = &commit_list_insert(commit, remotes)->next; - free(parents); } return remoteheads; } @@ -1070,7 +1023,7 @@ static void handle_fetch_head(struct commit_list **remotes, struct strbuf *merge if (!merge_names) merge_names = &fetch_head_file; - filename = git_path("FETCH_HEAD"); + filename = git_path_fetch_head(); fd = open(filename, O_RDONLY); if (fd < 0) die_errno(_("could not open '%s' for reading"), filename); @@ -1105,7 +1058,7 @@ static void handle_fetch_head(struct commit_list **remotes, struct strbuf *merge if (!commit) { if (ptr) *ptr = '\0'; - die("not something we can merge in %s: %s", + die(_("not something we can merge in %s: %s"), filename, merge_names->buf + pos); } remotes = &commit_list_insert(commit, remotes)->next; @@ -1139,7 +1092,7 @@ static struct commit_list *collect_parents(struct commit *head_commit, struct commit *commit = get_merge_parent(argv[i]); if (!commit) help_unknown_ref(argv[i], "merge", - "not something we can merge"); + _("not something we can merge")); remotes = &commit_list_insert(commit, remotes)->next; } remoteheads = reduce_parents(head_commit, head_subsumed, remoteheads); @@ -1166,7 +1119,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) struct commit *head_commit; struct strbuf buf = STRBUF_INIT; const char *head_arg; - int flag, i, ret = 0, head_subsumed; + int i, ret = 0, head_subsumed; int best_cnt = -1, merge_was_ok = 0, automerge_was_ok = 0; struct commit_list *common = NULL; const char *best_strategy = NULL, *wt_strategy = NULL; @@ -1180,7 +1133,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) * Check if we are _not_ on a detached HEAD, i.e. if there is a * current branch. */ - branch = branch_to_free = resolve_refdup("HEAD", 0, head_sha1, &flag); + branch = branch_to_free = resolve_refdup("HEAD", 0, head_sha1, NULL); if (branch && starts_with(branch, "refs/heads/")) branch += 11; if (!branch || is_null_sha1(head_sha1)) @@ -1188,6 +1141,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) else head_commit = lookup_commit_or_die(head_sha1, "HEAD"); + init_diff_ui_defaults(); git_config(git_merge_config, NULL); if (branch_mergeoptions) @@ -1204,7 +1158,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) int nargc = 2; const char *nargv[] = {"reset", "--merge", NULL}; - if (!file_exists(git_path("MERGE_HEAD"))) + if (!file_exists(git_path_merge_head())) die(_("There is no merge to abort (MERGE_HEAD missing).")); /* Invoke 'git reset --merge' */ @@ -1215,7 +1169,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) if (read_cache_unmerged()) die_resolve_conflict("merge"); - if (file_exists(git_path("MERGE_HEAD"))) { + if (file_exists(git_path_merge_head())) { /* * There is no unmerged entry, don't advise 'git * add/rm <file>', just 'git commit'. @@ -1226,7 +1180,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) else die(_("You have not concluded your merge (MERGE_HEAD exists).")); } - if (file_exists(git_path("CHERRY_PICK_HEAD"))) { + 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 merge.")); @@ -1258,12 +1212,12 @@ int cmd_merge(int argc, const char **argv, const char *prefix) builtin_merge_options); if (!head_commit) { - struct commit *remote_head; /* * If the merged head is a valid one there is no reason * to forbid "git merge" into a branch yet to be born. * We do the same for "git pull". */ + unsigned char *remote_head_sha1; if (squash) die(_("Squash commit into empty head not supported yet")); if (fast_forward == FF_NO) @@ -1271,13 +1225,13 @@ int cmd_merge(int argc, const char **argv, const char *prefix) "an empty head")); remoteheads = collect_parents(head_commit, &head_subsumed, argc, argv, NULL); - remote_head = remoteheads->item; - if (!remote_head) + if (!remoteheads) die(_("%s - not something we can merge"), argv[0]); if (remoteheads->next) die(_("Can merge only exactly one commit into empty head")); - read_empty(remote_head->object.sha1, 0); - update_ref("initial pull", "HEAD", remote_head->object.sha1, + remote_head_sha1 = remoteheads->item->object.oid.hash; + read_empty(remote_head_sha1, 0); + update_ref("initial pull", "HEAD", remote_head_sha1, NULL, 0, UPDATE_REFS_DIE_ON_ERR); goto done; } @@ -1291,7 +1245,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) * additional safety measure to check for it. */ if (!have_message && - is_old_style_invocation(argc, argv, head_commit->object.sha1)) { + is_old_style_invocation(argc, argv, head_commit->object.oid.hash)) { warning("old-style 'git merge <msg> HEAD <commit>' is deprecated."); strbuf_addstr(&merge_msg, argv[0]); head_arg = argv[1]; @@ -1319,13 +1273,13 @@ int cmd_merge(int argc, const char **argv, const char *prefix) if (verify_signatures) { for (p = remoteheads; p; p = p->next) { struct commit *commit = p->item; - char hex[41]; + char hex[GIT_SHA1_HEXSZ + 1]; struct signature_check signature_check; memset(&signature_check, 0, sizeof(signature_check)); check_commit_signature(commit, &signature_check); - strcpy(hex, find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV)); + find_unique_abbrev_r(hex, commit->object.oid.hash, DEFAULT_ABBREV); switch (signature_check.result) { case 'G': break; @@ -1355,7 +1309,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) for (p = remoteheads; p; p = p->next) { struct commit *commit = p->item; strbuf_addf(&buf, "GITHEAD_%s", - sha1_to_hex(commit->object.sha1)); + oid_to_hex(&commit->object.oid)); setenv(buf.buf, merge_remote_util(commit)->name, 1); strbuf_reset(&buf); if (fast_forward != FF_ONLY && @@ -1395,35 +1349,37 @@ int cmd_merge(int argc, const char **argv, const char *prefix) free(list); } - update_ref("updating ORIG_HEAD", "ORIG_HEAD", head_commit->object.sha1, + update_ref("updating ORIG_HEAD", "ORIG_HEAD", head_commit->object.oid.hash, NULL, 0, UPDATE_REFS_DIE_ON_ERR); - if (remoteheads && !common) - ; /* No common ancestors found. We need a real merge. */ - else if (!remoteheads || + if (remoteheads && !common) { + /* No common ancestors found. */ + if (!allow_unrelated_histories) + die(_("refusing to merge unrelated histories")); + /* otherwise, we need a real merge. */ + } else if (!remoteheads || (!remoteheads->next && !common->next && common->item == remoteheads->item)) { /* * If head can reach all the merge then we are up to date. * but first the most common case of merging one remote. */ - finish_up_to_date("Already up-to-date."); + finish_up_to_date(_("Already up-to-date.")); goto done; } else if (fast_forward != FF_NO && !remoteheads->next && !common->next && - !hashcmp(common->item->object.sha1, head_commit->object.sha1)) { + !oidcmp(&common->item->object.oid, &head_commit->object.oid)) { /* Again the most common case of merging one remote. */ struct strbuf msg = STRBUF_INIT; struct commit *commit; - char hex[41]; - - strcpy(hex, find_unique_abbrev(head_commit->object.sha1, DEFAULT_ABBREV)); - if (verbosity >= 0) + if (verbosity >= 0) { printf(_("Updating %s..%s\n"), - hex, - find_unique_abbrev(remoteheads->item->object.sha1, - DEFAULT_ABBREV)); + find_unique_abbrev(head_commit->object.oid.hash, + DEFAULT_ABBREV), + find_unique_abbrev(remoteheads->item->object.oid.hash, + DEFAULT_ABBREV)); + } strbuf_addstr(&msg, "Fast-forward"); if (have_message) strbuf_addstr(&msg, @@ -1434,14 +1390,14 @@ int cmd_merge(int argc, const char **argv, const char *prefix) goto done; } - if (checkout_fast_forward(head_commit->object.sha1, - commit->object.sha1, + if (checkout_fast_forward(head_commit->object.oid.hash, + commit->object.oid.hash, overwrite_ignore)) { ret = 1; goto done; } - finish(head_commit, remoteheads, commit->object.sha1, msg.buf); + finish(head_commit, remoteheads, commit->object.oid.hash, msg.buf); drop_save(); goto done; } else if (!remoteheads->next && common->next) @@ -1460,9 +1416,9 @@ int cmd_merge(int argc, const char **argv, const char *prefix) /* See if it is really trivial. */ git_committer_info(IDENT_STRICT); printf(_("Trying really trivial in-index merge...\n")); - if (!read_tree_trivial(common->item->object.sha1, - head_commit->object.sha1, - remoteheads->item->object.sha1)) { + if (!read_tree_trivial(common->item->object.oid.hash, + head_commit->object.oid.hash, + remoteheads->item->object.oid.hash)) { ret = merge_trivial(head_commit, remoteheads); goto done; } @@ -1485,14 +1441,13 @@ int cmd_merge(int argc, const char **argv, const char *prefix) * HEAD^^" would be missed. */ common_one = get_merge_bases(head_commit, j->item); - if (hashcmp(common_one->item->object.sha1, - j->item->object.sha1)) { + if (oidcmp(&common_one->item->object.oid, &j->item->object.oid)) { up_to_date = 0; break; } } if (up_to_date) { - finish_up_to_date("Already up-to-date. Yeeah!"); + finish_up_to_date(_("Already up-to-date. Yeeah!")); goto done; } } @@ -1516,13 +1471,13 @@ int cmd_merge(int argc, const char **argv, const char *prefix) * Stash away the local changes so that we can try more than one. */ save_state(stash)) - hashcpy(stash, null_sha1); + hashclr(stash); for (i = 0; i < use_strategies_nr; i++) { int ret; if (i) { printf(_("Rewinding the tree to pristine...\n")); - restore_state(head_commit->object.sha1, stash); + restore_state(head_commit->object.oid.hash, stash); } if (use_strategies_nr != 1) printf(_("Trying merge strategy %s...\n"), @@ -1588,7 +1543,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) * it up. */ if (!best_strategy) { - restore_state(head_commit->object.sha1, stash); + restore_state(head_commit->object.oid.hash, stash); if (use_strategies_nr > 1) fprintf(stderr, _("No merge strategy handled the merge.\n")); @@ -1601,7 +1556,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) ; /* We already have its result in the working tree. */ else { printf(_("Rewinding the tree to pristine...\n")); - restore_state(head_commit->object.sha1, stash); + restore_state(head_commit->object.oid.hash, stash); printf(_("Using the %s to prepare resolving by hand.\n"), best_strategy); try_merge_strategy(best_strategy, common, remoteheads, diff --git a/builtin/mktag.c b/builtin/mktag.c index 640ab64f41..031b750f06 100644 --- a/builtin/mktag.c +++ b/builtin/mktag.c @@ -154,7 +154,7 @@ int cmd_mktag(int argc, const char **argv, const char *prefix) unsigned char result_sha1[20]; if (argc != 1) - usage("git mktag < signaturefile"); + usage("git mktag"); if (strbuf_read(&buf, 0, 4096) < 0) { die_errno("could not read from stdin"); diff --git a/builtin/mktree.c b/builtin/mktree.c index a964d6be52..de9b40fc63 100644 --- a/builtin/mktree.c +++ b/builtin/mktree.c @@ -19,16 +19,17 @@ static int alloc, used; static void append_to_tree(unsigned mode, unsigned char *sha1, char *path) { struct treeent *ent; - int len = strlen(path); + size_t len = strlen(path); if (strchr(path, '/')) die("path %s contains slash", path); - ALLOC_GROW(entries, used + 1, alloc); - ent = entries[used++] = xmalloc(sizeof(**entries) + len + 1); + FLEX_ALLOC_MEM(ent, name, path, len); ent->mode = mode; ent->len = len; hashcpy(ent->sha1, sha1); - memcpy(ent->name, path, len+1); + + ALLOC_GROW(entries, used + 1, alloc); + entries[used++] = ent; } static int ent_compare(const void *a_, const void *b_) @@ -45,7 +46,7 @@ static void write_tree(unsigned char *sha1) size_t size; int i; - qsort(entries, used, sizeof(*entries), ent_compare); + QSORT(entries, used, ent_compare); for (size = i = 0; i < used; i++) size += 32 + entries[i]->len; @@ -65,7 +66,7 @@ static const char *mktree_usage[] = { NULL }; -static void mktree_line(char *buf, size_t len, int line_termination, int allow_missing) +static void mktree_line(char *buf, size_t len, int nul_term_line, int allow_missing) { char *ptr, *ntr; unsigned mode; @@ -97,7 +98,7 @@ static void mktree_line(char *buf, size_t len, int line_termination, int allow_m *ntr++ = 0; /* now at the beginning of SHA1 */ path = ntr + 41; /* at the beginning of name */ - if (line_termination && path[0] == '"') { + if (!nul_term_line && path[0] == '"') { struct strbuf p_uq = STRBUF_INIT; if (unquote_c_style(&p_uq, path, NULL)) die("invalid quoting"); @@ -141,23 +142,25 @@ int cmd_mktree(int ac, const char **av, const char *prefix) { struct strbuf sb = STRBUF_INIT; unsigned char sha1[20]; - int line_termination = '\n'; + int nul_term_line = 0; int allow_missing = 0; int is_batch_mode = 0; int got_eof = 0; + strbuf_getline_fn getline_fn; const struct option option[] = { - OPT_SET_INT('z', NULL, &line_termination, N_("input is NUL terminated"), '\0'), + OPT_BOOL('z', NULL, &nul_term_line, N_("input is NUL terminated")), OPT_SET_INT( 0 , "missing", &allow_missing, N_("allow missing objects"), 1), OPT_SET_INT( 0 , "batch", &is_batch_mode, N_("allow creation of more than one tree"), 1), OPT_END() }; ac = parse_options(ac, av, prefix, option, mktree_usage, 0); + getline_fn = nul_term_line ? strbuf_getline_nul : strbuf_getline_lf; while (!got_eof) { while (1) { - if (strbuf_getline(&sb, stdin, line_termination) == EOF) { + if (getline_fn(&sb, stdin) == EOF) { got_eof = 1; break; } @@ -167,7 +170,7 @@ int cmd_mktree(int ac, const char **av, const char *prefix) break; die("input format error: (blank line only valid in batch mode)"); } - mktree_line(sb.buf, sb.len, line_termination, allow_missing); + mktree_line(sb.buf, sb.len, nul_term_line, allow_missing); } if (is_batch_mode && got_eof && used < 1) { /* diff --git a/builtin/mv.c b/builtin/mv.c index d1d43168ae..2f43877bc9 100644 --- a/builtin/mv.c +++ b/builtin/mv.c @@ -24,8 +24,9 @@ static const char **internal_copy_pathspec(const char *prefix, int count, unsigned flags) { int i; - const char **result = xmalloc((count + 1) * sizeof(const char *)); - memcpy(result, pathspec, count * sizeof(const char *)); + const char **result; + ALLOC_ARRAY(result, count + 1); + COPY_ARRAY(result, pathspec, count); result[count] = NULL; for (i = 0; i < count; i++) { int length = strlen(result[i]); @@ -47,9 +48,9 @@ static const char **internal_copy_pathspec(const char *prefix, static const char *add_slash(const char *path) { - int len = strlen(path); + size_t len = strlen(path); if (path[len - 1] != '/') { - char *with_slash = xmalloc(len + 2); + char *with_slash = xmalloc(st_add(len, 2)); memcpy(with_slash, path, len); with_slash[len++] = '/'; with_slash[len] = 0; @@ -103,7 +104,7 @@ static int index_range_of_same_dir(const char *src, int length, int cmd_mv(int argc, const char **argv, const char *prefix) { - int i, gitmodules_modified = 0; + int i, flags, gitmodules_modified = 0; int verbose = 0, show_only = 0, force = 0, ignore_errors = 0; struct option builtin_mv_options[] = { OPT__VERBOSE(&verbose, N_("be verbose")), @@ -133,10 +134,13 @@ int cmd_mv(int argc, const char **argv, const char *prefix) modes = xcalloc(argc, sizeof(enum update_mode)); /* * Keep trailing slash, needed to let - * "git mv file no-such-dir/" error out. + * "git mv file no-such-dir/" error out, except in the case + * "git mv directory no-such-dir/". */ - dest_path = internal_copy_pathspec(prefix, argv + argc, 1, - KEEP_TRAILING_SLASH); + flags = KEEP_TRAILING_SLASH; + if (argc == 1 && is_directory(argv[0]) && !is_directory(argv[1])) + flags = 0; + dest_path = internal_copy_pathspec(prefix, argv + argc, 1, flags); submodule_gitfile = xcalloc(argc, sizeof(char *)); if (dest_path[0][0] == '\0') @@ -251,15 +255,18 @@ int cmd_mv(int argc, const char **argv, const char *prefix) int pos; if (show_only || verbose) printf(_("Renaming %s to %s\n"), src, dst); - if (!show_only && mode != INDEX) { - if (rename(src, dst) < 0 && !ignore_errors) - die_errno(_("renaming '%s' failed"), src); - if (submodule_gitfile[i]) { - if (submodule_gitfile[i] != SUBMODULE_WITH_GITDIR) - connect_work_tree_and_git_dir(dst, submodule_gitfile[i]); - if (!update_path_in_gitmodules(src, dst)) - gitmodules_modified = 1; - } + if (show_only) + continue; + if (mode != INDEX && rename(src, dst) < 0) { + if (ignore_errors) + continue; + die_errno(_("renaming '%s' failed"), src); + } + if (submodule_gitfile[i]) { + if (submodule_gitfile[i] != SUBMODULE_WITH_GITDIR) + connect_work_tree_and_git_dir(dst, submodule_gitfile[i]); + if (!update_path_in_gitmodules(src, dst)) + gitmodules_modified = 1; } if (mode == WORKING_DIRECTORY) diff --git a/builtin/name-rev.c b/builtin/name-rev.c index 248a3eb260..cd89d48b65 100644 --- a/builtin/name-rev.c +++ b/builtin/name-rev.c @@ -10,6 +10,7 @@ typedef struct rev_name { const char *tip_name; + unsigned long taggerdate; int generation; int distance; } rev_name; @@ -20,7 +21,8 @@ static long cutoff = LONG_MAX; #define MERGE_TRAVERSAL_WEIGHT 65535 static void name_rev(struct commit *commit, - const char *tip_name, int generation, int distance, + const char *tip_name, unsigned long taggerdate, + int generation, int distance, int deref) { struct rev_name *name = (struct rev_name *)commit->util; @@ -43,9 +45,12 @@ static void name_rev(struct commit *commit, name = xmalloc(sizeof(rev_name)); commit->util = name; goto copy_data; - } else if (name->distance > distance) { + } else if (name->taggerdate > taggerdate || + (name->taggerdate == taggerdate && + name->distance > distance)) { copy_data: name->tip_name = tip_name; + name->taggerdate = taggerdate; name->generation = generation; name->distance = distance; } else @@ -55,26 +60,22 @@ copy_data: parents; parents = parents->next, parent_number++) { if (parent_number > 1) { - int len = strlen(tip_name); - char *new_name = xmalloc(len + - 1 + decimal_length(generation) + /* ~<n> */ - 1 + 2 + /* ^NN */ - 1); - - if (len > 2 && !strcmp(tip_name + len - 2, "^0")) - len -= 2; + size_t len; + char *new_name; + + strip_suffix(tip_name, "^0", &len); if (generation > 0) - sprintf(new_name, "%.*s~%d^%d", len, tip_name, - generation, parent_number); + new_name = xstrfmt("%.*s~%d^%d", (int)len, tip_name, + generation, parent_number); else - sprintf(new_name, "%.*s^%d", len, tip_name, - parent_number); + new_name = xstrfmt("%.*s^%d", (int)len, tip_name, + parent_number); - name_rev(parents->item, new_name, 0, + name_rev(parents->item, new_name, taggerdate, 0, distance + MERGE_TRAVERSAL_WEIGHT, 0); } else { - name_rev(parents->item, tip_name, generation + 1, - distance + 1, 0); + name_rev(parents->item, tip_name, taggerdate, + generation + 1, distance + 1, 0); } } } @@ -144,6 +145,7 @@ static int name_ref(const char *path, const struct object_id *oid, int flags, vo struct name_ref_data *data = cb_data; int can_abbreviate_output = data->tags_only && data->name_only; int deref = 0; + unsigned long taggerdate = ULONG_MAX; if (data->tags_only && !starts_with(path, "refs/tags/")) return 0; @@ -166,14 +168,15 @@ static int name_ref(const char *path, const struct object_id *oid, int flags, vo struct tag *t = (struct tag *) o; if (!t->tagged) break; /* broken repository */ - o = parse_object(t->tagged->sha1); + o = parse_object(t->tagged->oid.hash); deref = 1; + taggerdate = t->date; } if (o && o->type == OBJ_COMMIT) { struct commit *commit = (struct commit *)o; path = name_ref_abbrev(path, can_abbreviate_output); - name_rev(commit, xstrdup(path), 0, 0, deref); + name_rev(commit, xstrdup(path), taggerdate, 0, 0, deref); } return 0; } @@ -192,12 +195,11 @@ static const char *get_exact_ref_match(const struct object *o) return NULL; if (!tip_table.sorted) { - qsort(tip_table.table, tip_table.nr, sizeof(*tip_table.table), - tipcmp); + QSORT(tip_table.table, tip_table.nr, tipcmp); tip_table.sorted = 1; } - found = sha1_pos(o->sha1, tip_table.table, tip_table.nr, + found = sha1_pos(o->oid.hash, tip_table.table, tip_table.nr, nth_tip_table_ent); if (0 <= found) return tip_table.table[found].refname; @@ -236,19 +238,19 @@ static void show_name(const struct object *obj, int always, int allow_undefined, int name_only) { const char *name; - const unsigned char *sha1 = obj->sha1; + const struct object_id *oid = &obj->oid; if (!name_only) - printf("%s ", caller_name ? caller_name : sha1_to_hex(sha1)); + printf("%s ", caller_name ? caller_name : oid_to_hex(oid)); name = get_rev_name(obj); if (name) printf("%s\n", name); else if (allow_undefined) printf("undefined\n"); else if (always) - printf("%s\n", find_unique_abbrev(sha1, DEFAULT_ABBREV)); + printf("%s\n", find_unique_abbrev(oid->hash, DEFAULT_ABBREV)); else - die("cannot describe '%s'", sha1_to_hex(sha1)); + die("cannot describe '%s'", oid_to_hex(oid)); } static char const * const name_rev_usage[] = { diff --git a/builtin/notes.c b/builtin/notes.c index 63f95fc554..5248a9bad8 100644 --- a/builtin/notes.c +++ b/builtin/notes.c @@ -19,6 +19,7 @@ #include "string-list.h" #include "notes-merge.h" #include "notes-utils.h" +#include "worktree.h" static const char * const git_notes_usage[] = { N_("git notes [--ref <notes-ref>] [list [<object>]]"), @@ -90,7 +91,7 @@ static const char * const git_notes_get_ref_usage[] = { }; static const char note_template[] = - "\nWrite/edit the notes for the following object:\n"; + N_("Write/edit the notes for the following object:"); struct note_data { int given; @@ -178,7 +179,8 @@ static void prepare_note_data(const unsigned char *object, struct note_data *d, copy_obj_to_fd(fd, old_note); strbuf_addch(&buf, '\n'); - strbuf_add_commented_lines(&buf, note_template, strlen(note_template)); + strbuf_add_commented_lines(&buf, "\n", strlen("\n")); + strbuf_add_commented_lines(&buf, _(note_template), strlen(_(note_template))); strbuf_addch(&buf, '\n'); write_or_die(fd, buf.buf, buf.len); @@ -189,9 +191,9 @@ static void prepare_note_data(const unsigned char *object, struct note_data *d, strbuf_reset(&d->buf); if (launch_editor(d->edit_path, &d->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(&d->buf, 1); + strbuf_stripspace(&d->buf, 1); } } @@ -200,7 +202,7 @@ static void write_note_data(struct note_data *d, unsigned char *sha1) if (write_sha1_file(d->buf.buf, d->buf.len, blob_type, sha1)) { error(_("unable to write note object")); if (d->edit_path) - error(_("The note contents have been left in %s"), + error(_("the note contents have been left in %s"), d->edit_path); exit(128); } @@ -214,7 +216,7 @@ static int parse_msg_arg(const struct option *opt, const char *arg, int unset) if (d->buf.len) strbuf_addch(&d->buf, '\n'); strbuf_addstr(&d->buf, arg); - stripspace(&d->buf, 0); + strbuf_stripspace(&d->buf, 0); d->given = 1; return 0; @@ -231,7 +233,7 @@ static int parse_file_arg(const struct option *opt, const char *arg, int unset) die_errno(_("cannot read '%s'"), arg); } else if (strbuf_read_file(&d->buf, arg, 1024) < 0) die_errno(_("could not open or read '%s'"), arg); - stripspace(&d->buf, 0); + strbuf_stripspace(&d->buf, 0); d->given = 1; return 0; @@ -249,14 +251,14 @@ static int parse_reuse_arg(const struct option *opt, const char *arg, int unset) strbuf_addch(&d->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))) { free(buf); - die(_("Failed to read object '%s'."), arg); + die(_("failed to read object '%s'."), arg); } if (type != OBJ_BLOB) { free(buf); - die(_("Cannot read note data from non-blob object '%s'."), arg); + die(_("cannot read note data from non-blob object '%s'."), arg); } strbuf_add(&d->buf, buf, len); free(buf); @@ -285,24 +287,24 @@ static int notes_copy_from_stdin(int force, const char *rewrite_cmd) if (!c) return 0; } else { - init_notes(NULL, NULL, NULL, 0); + init_notes(NULL, NULL, NULL, NOTES_INIT_WRITABLE); t = &default_notes_tree; } - while (strbuf_getline(&buf, stdin, '\n') != EOF) { + while (strbuf_getline_lf(&buf, stdin) != EOF) { unsigned char from_obj[20], to_obj[20]; struct strbuf **split; int err; 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); @@ -311,7 +313,7 @@ static 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; } @@ -328,15 +330,20 @@ static int notes_copy_from_stdin(int force, const char *rewrite_cmd) return ret; } -static struct notes_tree *init_notes_check(const char *subcommand) +static struct notes_tree *init_notes_check(const char *subcommand, + int flags) { struct notes_tree *t; - init_notes(NULL, NULL, NULL, 0); + const char *ref; + init_notes(NULL, NULL, NULL, flags); t = &default_notes_tree; - if (!starts_with(t->ref, "refs/notes/")) - die("Refusing to %s notes in %s (outside of refs/notes/)", - subcommand, t->ref); + ref = (flags & NOTES_INIT_WRITABLE) ? t->update_ref : t->ref; + if (!starts_with(ref, "refs/notes/")) + /* TRANSLATORS: the first %s will be replaced by a + git notes command: 'add', 'merge', 'remove', etc.*/ + die(_("refusing to %s notes in %s (outside of refs/notes/)"), + subcommand, ref); return t; } @@ -359,16 +366,16 @@ static int list(int argc, const char **argv, const char *prefix) usage_with_options(git_notes_list_usage, options); } - t = init_notes_check("list"); + t = init_notes_check("list", 0); 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); @@ -417,9 +424,9 @@ static int add(int argc, const char **argv, const char *prefix) 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"); + t = init_notes_check("add", NOTES_INIT_WRITABLE); note = get_note(t, object); if (note) { @@ -503,14 +510,14 @@ static int copy(int argc, const char **argv, const char *prefix) } 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"); + t = init_notes_check("copy", NOTES_INIT_WRITABLE); note = get_note(t, object); if (note) { @@ -527,7 +534,7 @@ static int copy(int argc, const char **argv, const char *prefix) from_note = get_note(t, from_obj); if (!from_note) { - retval = error(_("Missing notes on source object %s. Cannot " + retval = error(_("missing notes on source object %s. Cannot " "copy."), sha1_to_hex(from_obj)); goto out; } @@ -586,9 +593,9 @@ static int append_edit(int argc, const char **argv, const char *prefix) 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]); + t = init_notes_check(argv[0], NOTES_INIT_WRITABLE); note = get_note(t, object); prepare_note_data(object, &d, edit ? note : NULL); @@ -649,13 +656,13 @@ static int show(int argc, const char **argv, const char *prefix) 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"); + t = init_notes_check("show", 0); 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}; @@ -675,11 +682,11 @@ static int merge_abort(struct notes_merge_options *o) */ if (delete_ref("NOTES_MERGE_PARTIAL", NULL, 0)) - ret += error("Failed to delete ref NOTES_MERGE_PARTIAL"); + 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"); + ret += error(_("failed to delete ref NOTES_MERGE_REF")); if (notes_merge_abort(o)) - ret += error("Failed to remove 'git notes merge' worktree"); + ret += error(_("failed to remove 'git notes merge' worktree")); return ret; } @@ -699,14 +706,14 @@ static int merge_commit(struct notes_merge_options *o) */ if (get_sha1("NOTES_MERGE_PARTIAL", sha1)) - die("Failed to read ref NOTES_MERGE_PARTIAL"); + die(_("failed to read ref NOTES_MERGE_PARTIAL")); else if (!(partial = lookup_commit_reference(sha1))) - die("Could not find commit from NOTES_MERGE_PARTIAL."); + die(_("could not find commit from NOTES_MERGE_PARTIAL.")); else if (parse_commit(partial)) - die("Could not parse commit from NOTES_MERGE_PARTIAL."); + die(_("could not parse commit from NOTES_MERGE_PARTIAL.")); if (partial->parents) - hashcpy(parent_sha1, partial->parents->item->object.sha1); + hashcpy(parent_sha1, partial->parents->item->object.oid.hash); else hashclr(parent_sha1); @@ -716,10 +723,10 @@ static int merge_commit(struct notes_merge_options *o) o->local_ref = local_ref_to_free = resolve_refdup("NOTES_MERGE_REF", 0, sha1, NULL); if (!o->local_ref) - die("Failed to resolve NOTES_MERGE_REF"); + die(_("failed to resolve NOTES_MERGE_REF")); if (notes_merge_commit(o, t, partial, sha1)) - die("Failed to finalize notes merge"); + die(_("failed to finalize notes merge")); /* Reuse existing commit message in reflog message */ memset(&pretty_ctx, 0, sizeof(pretty_ctx)); @@ -737,6 +744,20 @@ static int merge_commit(struct notes_merge_options *o) return ret; } +static int git_config_get_notes_strategy(const char *key, + enum notes_merge_strategy *strategy) +{ + char *value; + + if (git_config_get_string(key, &value)) + return 1; + if (parse_notes_merge_strategy(value, strategy)) + git_die_config(key, _("unknown notes merge strategy %s"), value); + + free(value); + return 0; +} + static int merge(int argc, const char **argv, const char *prefix) { struct strbuf remote_ref = STRBUF_INIT, msg = STRBUF_INIT; @@ -770,15 +791,15 @@ static int merge(int argc, const char **argv, const char *prefix) 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"); + 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"); + 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"); + error(_("too many parameters")); usage_with_options(git_notes_merge_usage, options); } @@ -792,27 +813,31 @@ static int merge(int argc, const char **argv, const char *prefix) o.local_ref = default_notes_ref(); strbuf_addstr(&remote_ref, argv[0]); - expand_notes_ref(&remote_ref); + expand_loose_notes_ref(&remote_ref); o.remote_ref = remote_ref.buf; + t = init_notes_check("merge", NOTES_INIT_WRITABLE); + 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); + if (parse_notes_merge_strategy(strategy, &o.strategy)) { + error(_("unknown -s/--strategy: %s"), strategy); usage_with_options(git_notes_merge_usage, options); } - } + } else { + struct strbuf merge_key = STRBUF_INIT; + const char *short_ref = NULL; + + if (!skip_prefix(o.local_ref, "refs/notes/", &short_ref)) + die("BUG: local ref %s is outside of refs/notes/", + o.local_ref); + + strbuf_addf(&merge_key, "notes.%s.mergeStrategy", short_ref); - t = init_notes_check("merge"); + if (git_config_get_notes_strategy(merge_key.buf, &o.strategy)) + git_config_get_notes_strategy("notes.mergeStrategy", &o.strategy); + + strbuf_release(&merge_key); + } strbuf_addf(&msg, "notes: Merged notes from %s into %s", remote_ref.buf, default_notes_ref()); @@ -825,16 +850,21 @@ static int merge(int argc, const char **argv, const char *prefix) update_ref(msg.buf, default_notes_ref(), result_sha1, NULL, 0, UPDATE_REFS_DIE_ON_ERR); else { /* Merge has unresolved conflicts */ + const struct worktree *wt; /* Update .git/NOTES_MERGE_PARTIAL with partial merge result */ update_ref(msg.buf, "NOTES_MERGE_PARTIAL", result_sha1, NULL, 0, UPDATE_REFS_DIE_ON_ERR); /* Store ref-to-be-updated into .git/NOTES_MERGE_REF */ + wt = find_shared_symref("NOTES_MERGE_REF", default_notes_ref()); + if (wt) + die(_("a notes merge into %s is already in-progress at %s"), + default_notes_ref(), wt->path); if (create_symref("NOTES_MERGE_REF", default_notes_ref(), NULL)) - die("Failed to store link to current notes ref (%s)", + 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", + 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)); } @@ -878,7 +908,7 @@ static int remove_cmd(int argc, const char **argv, const char *prefix) argc = parse_options(argc, argv, prefix, options, git_notes_remove_usage, 0); - t = init_notes_check("remove"); + t = init_notes_check("remove", NOTES_INIT_WRITABLE); if (!argc && !from_stdin) { retval = remove_one_note(t, "HEAD", flag); @@ -907,8 +937,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__DRY_RUN(&show_only, "do not remove, show only"), - OPT__VERBOSE(&verbose, "report pruned notes"), + OPT__DRY_RUN(&show_only, N_("do not remove, show only")), + OPT__VERBOSE(&verbose, N_("report pruned notes")), OPT_END() }; @@ -920,7 +950,7 @@ static int prune(int argc, const char **argv, const char *prefix) usage_with_options(git_notes_prune_usage, options); } - t = init_notes_check("prune"); + t = init_notes_check("prune", NOTES_INIT_WRITABLE); prune_notes(t, (verbose ? NOTES_PRUNE_VERBOSE : 0) | (show_only ? NOTES_PRUNE_VERBOSE|NOTES_PRUNE_DRYRUN : 0) ); @@ -937,7 +967,7 @@ static int get_ref(int argc, const char **argv, const char *prefix) git_notes_get_ref_usage, 0); if (argc) { - error("too many parameters"); + error(_("too many parameters")); usage_with_options(git_notes_get_ref_usage, options); } @@ -986,7 +1016,7 @@ int cmd_notes(int argc, const char **argv, const char *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 80fe8c7dc1..8841f8b366 100644 --- a/builtin/pack-objects.c +++ b/builtin/pack-objects.c @@ -23,10 +23,11 @@ #include "reachable.h" #include "sha1-array.h" #include "argv-array.h" +#include "mru.h" static const char *pack_usage[] = { - N_("git pack-objects --stdout [options...] [< ref-list | < object-list]"), - N_("git pack-objects [options...] base-name [< ref-list | < object-list]"), + N_("git pack-objects --stdout [<options>...] [< <ref-list> | < <object-list>]"), + N_("git pack-objects [<options>...] <base-name> [< <ref-list> | < <object-list>]"), NULL }; @@ -44,7 +45,9 @@ static int non_empty; static int reuse_delta = 1, reuse_object = 1; static int keep_unreachable, unpack_unreachable, include_tag; static unsigned long unpack_unreachable_expiration; +static int pack_loose_unreachable; static int local; +static int have_non_local_packs; static int incremental; static int ignore_packed_keep; static int allow_ofs_delta; @@ -58,14 +61,13 @@ static int delta_search_threads; static int pack_to_stdout; static int num_preferred_base; static struct progress *progress_state; -static int pack_compression_level = Z_DEFAULT_COMPRESSION; -static int pack_compression_seen; static struct packed_git *reuse_packfile; static uint32_t reuse_packfile_objects; static off_t reuse_packfile_offset; -static int use_bitmap_index = 1; +static int use_bitmap_index_default = 1; +static int use_bitmap_index = -1; static int write_bitmap_index; static uint16_t write_bitmap_options; @@ -341,15 +343,15 @@ static unsigned long write_no_reuse_object(struct sha1file *f, struct object_ent } /* Return 0 if we will bust the pack-size limit */ -static unsigned long write_reuse_object(struct sha1file *f, struct object_entry *entry, - unsigned long limit, int usable_delta) +static off_t write_reuse_object(struct sha1file *f, struct object_entry *entry, + unsigned long limit, int usable_delta) { struct packed_git *p = entry->in_pack; struct pack_window *w_curs = NULL; struct revindex_entry *revidx; off_t offset; enum object_type type = entry->type; - unsigned long datalen; + off_t datalen; unsigned char header[10], dheader[10]; unsigned hdrlen; @@ -415,11 +417,12 @@ static unsigned long write_reuse_object(struct sha1file *f, struct object_entry } /* 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) +static off_t write_object(struct sha1file *f, + struct object_entry *entry, + off_t write_offset) { - unsigned long limit, len; + unsigned long limit; + off_t len; int usable_delta, to_reuse; if (!pack_to_stdout) @@ -491,7 +494,7 @@ static enum write_one_status write_one(struct sha1file *f, struct object_entry *e, off_t *offset) { - unsigned long size; + off_t size; int recursing; /* @@ -624,7 +627,7 @@ static struct object_entry **compute_write_order(void) { unsigned int i, wo_end, last_untagged; - struct object_entry **wo = xmalloc(to_pack.nr_objects * sizeof(*wo)); + struct object_entry **wo; struct object_entry *objects = to_pack.objects; for (i = 0; i < to_pack.nr_objects; i++) { @@ -657,6 +660,7 @@ static struct object_entry **compute_write_order(void) * Give the objects in the original recency order until * we see a tagged tip. */ + ALLOC_ARRAY(wo, to_pack.nr_objects); for (i = wo_end = 0; i < to_pack.nr_objects; i++) { if (objects[i].tagged) break; @@ -714,7 +718,7 @@ static off_t write_reused_pack(struct sha1file *f) if (!is_pack_valid(reuse_packfile)) die("packfile is invalid: %s", reuse_packfile->pack_name); - fd = git_open_noatime(reuse_packfile->pack_name); + fd = git_open(reuse_packfile->pack_name); if (fd < 0) die_errno("unable to open packfile for reuse: %s", reuse_packfile->pack_name); @@ -758,6 +762,10 @@ static off_t write_reused_pack(struct sha1file *f) return reuse_packfile_offset - sizeof(struct pack_header); } +static const char no_split_warning[] = N_( +"disabling bitmap writing, packs are split due to pack.packSizeLimit" +); + static void write_pack_file(void) { uint32_t i = 0, j; @@ -769,7 +777,7 @@ static void write_pack_file(void) if (progress > pack_to_stdout) progress_state = start_progress(_("Writing objects"), nr_result); - written_list = xmalloc(to_pack.nr_objects * sizeof(*written_list)); + ALLOC_ARRAY(written_list, to_pack.nr_objects); write_order = compute_write_order(); do { @@ -812,7 +820,10 @@ static void write_pack_file(void) fixup_pack_header_footer(fd, sha1, pack_tmp_name, nr_written, sha1, offset); close(fd); - write_bitmap_index = 0; + if (write_bitmap_index) { + warning(_(no_split_warning)); + write_bitmap_index = 0; + } } if (!pack_to_stdout) { @@ -827,8 +838,7 @@ static void write_pack_file(void) * to preserve this property. */ if (stat(pack_tmp_name, &st) < 0) { - warning("failed to stat %s: %s", - pack_tmp_name, strerror(errno)); + warning_errno("failed to stat %s", pack_tmp_name); } else if (!last_mtime) { last_mtime = st.st_mtime; } else { @@ -836,8 +846,7 @@ static void write_pack_file(void) utb.actime = st.st_atime; utb.modtime = --last_mtime; if (utime(pack_tmp_name, &utb) < 0) - warning("failed utime() on %s: %s", - pack_tmp_name, strerror(errno)); + warning_errno("failed utime() on %s", pack_tmp_name); } strbuf_addf(&tmpname, "%s-", base_name); @@ -936,29 +945,80 @@ static int have_duplicate_entry(const unsigned char *sha1, return 1; } +static int want_found_object(int exclude, struct packed_git *p) +{ + if (exclude) + return 1; + if (incremental) + return 0; + + /* + * When asked to do --local (do not include an object that appears in a + * pack we borrow from elsewhere) or --honor-pack-keep (do not include + * an object that appears in a pack marked with .keep), finding a pack + * that matches the criteria is sufficient for us to decide to omit it. + * However, even if this pack does not satisfy the criteria, we need to + * make sure no copy of this object appears in _any_ pack that makes us + * to omit the object, so we need to check all the packs. + * + * We can however first check whether these options can possible matter; + * if they do not matter we know we want the object in generated pack. + * Otherwise, we signal "-1" at the end to tell the caller that we do + * not know either way, and it needs to check more packs. + */ + if (!ignore_packed_keep && + (!local || !have_non_local_packs)) + return 1; + + if (local && !p->pack_local) + return 0; + if (ignore_packed_keep && p->pack_local && p->pack_keep) + return 0; + + /* we don't know yet; keep looking for more packs */ + return -1; +} + /* * Check whether we want the object in the pack (e.g., we do not want * objects found in non-local stores if the "--local" option was used). * - * As a side effect of this check, we will find the packed version of this - * object, if any. We therefore pass out the pack information to avoid having - * to look it up again later. + * If the caller already knows an existing pack it wants to take the object + * from, that is passed in *found_pack and *found_offset; otherwise this + * function finds if there is any pack that has the object and returns the pack + * and its offset in these variables. */ static int want_object_in_pack(const unsigned char *sha1, int exclude, struct packed_git **found_pack, off_t *found_offset) { - struct packed_git *p; + struct mru_entry *entry; + int want; if (!exclude && local && has_loose_object_nonlocal(sha1)) return 0; - *found_pack = NULL; - *found_offset = 0; + /* + * If we already know the pack object lives in, start checks from that + * pack - in the usual case when neither --local was given nor .keep files + * are present we will determine the answer right now. + */ + if (*found_pack) { + want = want_found_object(exclude, *found_pack); + if (want != -1) + return want; + } + + for (entry = packed_git_mru->head; entry; entry = entry->next) { + struct packed_git *p = entry->item; + off_t offset; + + if (p == *found_pack) + offset = *found_offset; + else + offset = find_pack_entry_one(sha1, p); - for (p = packed_git; p; p = p->next) { - off_t offset = find_pack_entry_one(sha1, p); if (offset) { if (!*found_pack) { if (!is_pack_valid(p)) @@ -966,14 +1026,11 @@ static int want_object_in_pack(const unsigned char *sha1, *found_offset = offset; *found_pack = p; } - if (exclude) - return 1; - if (incremental) - return 0; - if (local && !p->pack_local) - return 0; - if (ignore_packed_keep && p->pack_local && p->pack_keep) - return 0; + want = want_found_object(exclude, p); + if (!exclude && want > 0) + mru_mark(packed_git_mru, entry); + if (want != -1) + return want; } } @@ -1014,8 +1071,8 @@ static const char no_closure_warning[] = N_( static int add_object_entry(const unsigned char *sha1, enum object_type type, const char *name, int exclude) { - struct packed_git *found_pack; - off_t found_offset; + struct packed_git *found_pack = NULL; + off_t found_offset = 0; uint32_t index_pos; if (have_duplicate_entry(sha1, exclude, &index_pos)) @@ -1048,6 +1105,9 @@ static int add_object_entry_from_bitmap(const unsigned char *sha1, if (have_duplicate_entry(sha1, 0, &index_pos)) return 0; + if (!want_object_in_pack(sha1, 0, &pack, &offset)) + return 0; + create_object_entry(sha1, type, name_hash, 0, 0, index_pos, pack, offset); display_progress(progress_state, nr_result); @@ -1185,7 +1245,7 @@ static void add_pbase_object(struct tree_desc *tree, if (cmp < 0) return; if (name[cmplen] != '/') { - add_object_entry(entry.sha1, + add_object_entry(entry.oid->hash, object_type(entry.mode), fullname, 1); return; @@ -1196,7 +1256,7 @@ static void add_pbase_object(struct tree_desc *tree, const char *down = name+cmplen+1; int downlen = name_cmp_len(down); - tree = pbase_tree_get(entry.sha1); + tree = pbase_tree_get(entry.oid->hash); if (!tree) return; init_tree_desc(&sub, tree->tree_data, tree->tree_size); @@ -1469,6 +1529,83 @@ static int pack_offset_sort(const void *_a, const void *_b) (a->in_pack_offset > b->in_pack_offset); } +/* + * Drop an on-disk delta we were planning to reuse. Naively, this would + * just involve blanking out the "delta" field, but we have to deal + * with some extra book-keeping: + * + * 1. Removing ourselves from the delta_sibling linked list. + * + * 2. Updating our size/type to the non-delta representation. These were + * either not recorded initially (size) or overwritten with the delta type + * (type) when check_object() decided to reuse the delta. + */ +static void drop_reused_delta(struct object_entry *entry) +{ + struct object_entry **p = &entry->delta->delta_child; + struct object_info oi = OBJECT_INFO_INIT; + + while (*p) { + if (*p == entry) + *p = (*p)->delta_sibling; + else + p = &(*p)->delta_sibling; + } + entry->delta = NULL; + + oi.sizep = &entry->size; + oi.typep = &entry->type; + if (packed_object_info(entry->in_pack, entry->in_pack_offset, &oi) < 0) { + /* + * We failed to get the info from this pack for some reason; + * fall back to sha1_object_info, which may find another copy. + * And if that fails, the error will be recorded in entry->type + * and dealt with in prepare_pack(). + */ + entry->type = sha1_object_info(entry->idx.sha1, &entry->size); + } +} + +/* + * Follow the chain of deltas from this entry onward, throwing away any links + * that cause us to hit a cycle (as determined by the DFS state flags in + * the entries). + */ +static void break_delta_chains(struct object_entry *entry) +{ + /* If it's not a delta, it can't be part of a cycle. */ + if (!entry->delta) { + entry->dfs_state = DFS_DONE; + return; + } + + switch (entry->dfs_state) { + case DFS_NONE: + /* + * This is the first time we've seen the object. We mark it as + * part of the active potential cycle and recurse. + */ + entry->dfs_state = DFS_ACTIVE; + break_delta_chains(entry->delta); + entry->dfs_state = DFS_DONE; + break; + + case DFS_DONE: + /* object already examined, and not part of a cycle */ + break; + + case DFS_ACTIVE: + /* + * We found a cycle that needs broken. It would be correct to + * break any link in the chain, but it's convenient to + * break this one. + */ + drop_reused_delta(entry); + entry->dfs_state = DFS_DONE; + break; + } +} + static void get_object_details(void) { uint32_t i; @@ -1477,7 +1614,7 @@ static void get_object_details(void) sorted_by_offset = xcalloc(to_pack.nr_objects, sizeof(struct object_entry *)); for (i = 0; i < to_pack.nr_objects; i++) sorted_by_offset[i] = to_pack.objects + i; - qsort(sorted_by_offset, to_pack.nr_objects, sizeof(*sorted_by_offset), pack_offset_sort); + QSORT(sorted_by_offset, to_pack.nr_objects, pack_offset_sort); for (i = 0; i < to_pack.nr_objects; i++) { struct object_entry *entry = sorted_by_offset[i]; @@ -1486,6 +1623,13 @@ static void get_object_details(void) entry->no_try_delta = 1; } + /* + * This must happen in a second pass, since we rely on the delta + * information for the whole list being completed. + */ + for (i = 0; i < to_pack.nr_objects; i++) + break_delta_chains(&to_pack.objects[i]); + free(sorted_by_offset); } @@ -2097,6 +2241,35 @@ static void ll_find_deltas(struct object_entry **list, unsigned list_size, #define ll_find_deltas(l, s, w, d, p) find_deltas(l, &s, w, d, p) #endif +static void add_tag_chain(const struct object_id *oid) +{ + struct tag *tag; + + /* + * We catch duplicates already in add_object_entry(), but we'd + * prefer to do this extra check to avoid having to parse the + * tag at all if we already know that it's being packed (e.g., if + * it was included via bitmaps, we would not have parsed it + * previously). + */ + if (packlist_find(&to_pack, oid->hash, NULL)) + return; + + tag = lookup_tag(oid->hash); + while (1) { + if (!tag || parse_tag(tag) || !tag->tagged) + die("unable to pack objects reachable from tag %s", + oid_to_hex(oid)); + + add_object_entry(tag->object.oid.hash, OBJ_TAG, NULL, 0); + + if (tag->tagged->type != OBJ_TAG) + return; + + tag = (struct tag *)tag->tagged; + } +} + static int add_ref_tag(const char *path, const struct object_id *oid, int flag, void *cb_data) { struct object_id peeled; @@ -2104,7 +2277,7 @@ static int add_ref_tag(const char *path, const struct object_id *oid, int flag, if (starts_with(path, "refs/tags/") && /* is a tag? */ !peel_ref(path, peeled.hash) && /* peelable? */ packlist_find(&to_pack, peeled.hash, NULL)) /* object packed? */ - add_object_entry(oid->hash, OBJ_TAG, NULL, 0); + add_tag_chain(oid); return 0; } @@ -2129,7 +2302,7 @@ static void prepare_pack(int window, int depth) if (!to_pack.nr_objects || !window || !depth) return; - delta_list = xmalloc(to_pack.nr_objects * sizeof(*delta_list)); + ALLOC_ARRAY(delta_list, to_pack.nr_objects); nr_deltas = n = 0; for (i = 0; i < to_pack.nr_objects; i++) { @@ -2170,7 +2343,7 @@ static void prepare_pack(int window, int depth) if (progress) progress_state = start_progress(_("Compressing objects"), nr_deltas); - qsort(delta_list, n, sizeof(*delta_list), type_size_sort); + QSORT(delta_list, n, type_size_sort); ll_find_deltas(delta_list, n, window+1, depth, &nr_done); stop_progress(&progress_state); if (nr_done != nr_deltas) @@ -2193,16 +2366,6 @@ static int git_pack_config(const char *k, const char *v, void *cb) depth = git_config_int(k, v); return 0; } - if (!strcmp(k, "pack.compression")) { - int level = git_config_int(k, v); - if (level == -1) - level = Z_DEFAULT_COMPRESSION; - else if (level < 0 || level > Z_BEST_COMPRESSION) - die("bad pack compression level %d", level); - pack_compression_level = level; - pack_compression_seen = 1; - return 0; - } if (!strcmp(k, "pack.deltacachesize")) { max_delta_cache_size = git_config_int(k, v); return 0; @@ -2218,7 +2381,7 @@ static int git_pack_config(const char *k, const char *v, void *cb) write_bitmap_options &= ~BITMAP_OPT_HASH_CACHE; } if (!strcmp(k, "pack.usebitmaps")) { - use_bitmap_index = git_config_bool(k, v); + use_bitmap_index_default = git_config_bool(k, v); return 0; } if (!strcmp(k, "pack.threads")) { @@ -2277,33 +2440,23 @@ static void read_object_list_from_stdin(void) static void show_commit(struct commit *commit, void *data) { - add_object_entry(commit->object.sha1, OBJ_COMMIT, NULL, 0); + add_object_entry(commit->object.oid.hash, OBJ_COMMIT, NULL, 0); commit->object.flags |= OBJECT_ADDED; if (write_bitmap_index) index_commit_for_bitmap(commit); } -static void show_object(struct object *obj, - const struct name_path *path, const char *last, - void *data) +static void show_object(struct object *obj, const char *name, void *data) { - char *name = path_name(path, last); - add_preferred_base_object(name); - add_object_entry(obj->sha1, obj->type, name, 0); + add_object_entry(obj->oid.hash, obj->type, name, 0); obj->flags |= OBJECT_ADDED; - - /* - * We will have generated the hash from the name, - * but not saved a pointer to it - we can free it - */ - free((char *)name); } static void show_edge(struct commit *commit) { - add_preferred_base(commit->object.sha1); + add_preferred_base(commit->object.oid.hash); } struct in_pack_object { @@ -2319,7 +2472,7 @@ struct in_pack { static void mark_in_pack_object(struct object *object, struct packed_git *p, struct in_pack *in_pack) { - in_pack->array[in_pack->nr].offset = find_pack_entry_one(object->sha1, p); + in_pack->array[in_pack->nr].offset = find_pack_entry_one(object->oid.hash, p); in_pack->array[in_pack->nr].object = object; in_pack->nr++; } @@ -2338,7 +2491,7 @@ static int ofscmp(const void *a_, const void *b_) else if (a->offset > b->offset) return 1; else - return hashcmp(a->object->sha1, b->object->sha1); + return oidcmp(&a->object->oid, &b->object->oid); } static void add_objects_in_unpacked_packs(struct rev_info *revs) @@ -2372,16 +2525,41 @@ static void add_objects_in_unpacked_packs(struct rev_info *revs) } if (in_pack.nr) { - qsort(in_pack.array, in_pack.nr, sizeof(in_pack.array[0]), - ofscmp); + QSORT(in_pack.array, in_pack.nr, ofscmp); for (i = 0; i < in_pack.nr; i++) { struct object *o = in_pack.array[i].object; - add_object_entry(o->sha1, o->type, "", 0); + add_object_entry(o->oid.hash, o->type, "", 0); } } free(in_pack.array); } +static int add_loose_object(const unsigned char *sha1, const char *path, + void *data) +{ + enum object_type type = sha1_object_info(sha1, NULL); + + if (type < 0) { + warning("loose object at %s could not be examined", path); + return 0; + } + + add_object_entry(sha1, type, "", 0); + return 0; +} + +/* + * We actually don't even have to worry about reachability here. + * add_object_entry will weed out duplicates, so we just add every + * loose object we find. + */ +static void add_unreachable_loose_objects(void) +{ + for_each_loose_file_in_objdir(get_object_directory(), + add_loose_object, + NULL, NULL, NULL); +} + static int has_sha1_pack_kept_or_nonlocal(const unsigned char *sha1) { static struct packed_git *last_found = (void *)1; @@ -2451,13 +2629,13 @@ static void loosen_unused_packed_objects(struct rev_info *revs) } /* - * This tracks any options which a reader of the pack might - * not understand, and which would therefore prevent blind reuse - * of what we have on disk. + * This tracks any options which pack-reuse code expects to be on, or which a + * reader of the pack might not understand, and which would therefore prevent + * blind reuse of what we have on disk. */ static int pack_options_allow_reuse(void) { - return allow_ofs_delta; + return pack_to_stdout && allow_ofs_delta; } static int get_object_list_from_bitmap(struct rev_info *revs) @@ -2480,16 +2658,15 @@ static int get_object_list_from_bitmap(struct rev_info *revs) } static void record_recent_object(struct object *obj, - const struct name_path *path, - const char *last, + const char *name, void *data) { - sha1_array_append(&recent_objects, obj->sha1); + sha1_array_append(&recent_objects, obj->oid.hash); } static void record_recent_commit(struct commit *commit, void *data) { - sha1_array_append(&recent_objects, commit->object.sha1); + sha1_array_append(&recent_objects, commit->object.oid.hash); } static void get_object_list(int ac, const char **av) @@ -2552,6 +2729,8 @@ static void get_object_list(int ac, const char **av) if (keep_unreachable) add_objects_in_unpacked_packs(&revs); + if (pack_loose_unreachable) + add_unreachable_loose_objects(); if (unpack_unreachable) loosen_unused_packed_objects(&revs); @@ -2588,23 +2767,6 @@ static int option_parse_unpack_unreachable(const struct option *opt, return 0; } -static int option_parse_ulong(const struct option *opt, - const char *arg, int unset) -{ - if (unset) - die(_("option %s does not accept negative form"), - opt->long_name); - - if (!git_parse_ulong(arg, opt->value)) - die(_("unable to parse value '%s' for option %s"), - arg, opt->long_name); - return 0; -} - -#define OPT_ULONG(s, l, v, h) \ - { OPTION_CALLBACK, (s), (l), (v), "n", (h), \ - PARSE_OPT_NONEG, option_parse_ulong } - int cmd_pack_objects(int argc, const char **argv, const char *prefix) { int use_internal_rev_list = 0; @@ -2627,16 +2789,16 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) { OPTION_CALLBACK, 0, "index-version", NULL, N_("version[,offset]"), N_("write the pack index file in the specified idx format version"), 0, option_parse_index_version }, - OPT_ULONG(0, "max-pack-size", &pack_size_limit, - N_("maximum size of each output pack file")), + OPT_MAGNITUDE(0, "max-pack-size", &pack_size_limit, + N_("maximum size of each output pack file")), OPT_BOOL(0, "local", &local, N_("ignore borrowed objects from alternate object store")), OPT_BOOL(0, "incremental", &incremental, N_("ignore packed objects")), OPT_INTEGER(0, "window", &window, N_("limit pack window by objects")), - OPT_ULONG(0, "window-memory", &window_memory_limit, - N_("limit pack window by memory in addition to object limit")), + OPT_MAGNITUDE(0, "window-memory", &window_memory_limit, + N_("limit pack window by memory in addition to object limit")), OPT_INTEGER(0, "depth", &depth, N_("maximum length of delta chain allowed in the resulting pack")), OPT_BOOL(0, "reuse-delta", &reuse_delta, @@ -2669,6 +2831,8 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) N_("include tag objects that refer to objects to be packed")), OPT_BOOL(0, "keep-unreachable", &keep_unreachable, N_("keep unreachable objects")), + OPT_BOOL(0, "pack-loose-unreachable", &pack_loose_unreachable, + N_("pack loose unreachable objects")), { OPTION_CALLBACK, 0, "unpack-unreachable", NULL, N_("time"), N_("unpack unreachable objects newer than <time>"), PARSE_OPT_OPTARG, option_parse_unpack_unreachable }, @@ -2693,8 +2857,6 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) reset_pack_idx_option(&pack_idx_opts); git_config(git_pack_config, NULL); - if (!pack_compression_seen && core_compression_seen) - pack_compression_level = core_compression_level; progress = isatty(2); argc = parse_options(argc, argv, prefix, pack_objects_options, @@ -2764,7 +2926,23 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) if (!rev_list_all || !rev_list_reflog || !rev_list_index) unpack_unreachable_expiration = 0; - if (!use_internal_rev_list || !pack_to_stdout || is_repository_shallow()) + /* + * "soft" reasons not to use bitmaps - for on-disk repack by default we want + * + * - to produce good pack (with bitmap index not-yet-packed objects are + * packed in suboptimal order). + * + * - to use more robust pack-generation codepath (avoiding possible + * bugs in bitmap code and possible bitmap index corruption). + */ + if (!pack_to_stdout) + use_bitmap_index_default = 0; + + if (use_bitmap_index < 0) + use_bitmap_index = use_bitmap_index_default; + + /* "hard" reasons not to use bitmaps; these just won't work at all */ + if (!use_internal_rev_list || (!pack_to_stdout && write_bitmap_index) || is_repository_shallow()) use_bitmap_index = 0; if (pack_to_stdout || !rev_list_all) @@ -2774,6 +2952,28 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) progress = 2; prepare_packed_git(); + if (ignore_packed_keep) { + struct packed_git *p; + for (p = packed_git; p; p = p->next) + if (p->pack_local && p->pack_keep) + break; + if (!p) /* no keep-able packs found */ + ignore_packed_keep = 0; + } + if (local) { + /* + * unlike ignore_packed_keep above, we do not want to + * unset "local" based on looking at packs, as it + * also covers non-local objects + */ + struct packed_git *p; + for (p = packed_git; p; p = p->next) { + if (!p->pack_local) { + have_non_local_packs = 1; + break; + } + } + } if (progress) progress_state = start_progress(_("Counting objects"), 0); diff --git a/builtin/pack-redundant.c b/builtin/pack-redundant.c index d0532f66b1..72c815844d 100644 --- a/builtin/pack-redundant.c +++ b/builtin/pack-redundant.c @@ -53,7 +53,7 @@ static inline struct llist_item *llist_item_get(void) free_nodes = free_nodes->next; } else { int i = 1; - new = xmalloc(sizeof(struct llist_item) * BLKSIZE); + ALLOC_ARRAY(new, BLKSIZE); for (; i < BLKSIZE; i++) llist_item_put(&new[i]); } diff --git a/builtin/patch-id.c b/builtin/patch-id.c index ba34dac4d2..a84d0003a3 100644 --- a/builtin/patch-id.c +++ b/builtin/patch-id.c @@ -81,16 +81,13 @@ static int get_one_patchid(struct object_id *next_oid, struct object_id *result, while (strbuf_getwholeline(line_buf, stdin, '\n') != EOF) { char *line = line_buf->buf; - char *p = line; + const char *p = line; int len; - if (!memcmp(line, "diff-tree ", 10)) - p += 10; - else if (!memcmp(line, "commit ", 7)) - p += 7; - else if (!memcmp(line, "From ", 5)) - p += 5; - else if (!memcmp(line, "\\ ", 2) && 12 < strlen(line)) + if (!skip_prefix(line, "diff-tree ", &p) && + !skip_prefix(line, "commit ", &p) && + !skip_prefix(line, "From ", &p) && + starts_with(line, "\\ ") && 12 < strlen(line)) continue; if (!get_oid_hex(p, next_oid)) { @@ -99,14 +96,14 @@ static int get_one_patchid(struct object_id *next_oid, struct object_id *result, } /* Ignore commit comments */ - if (!patchlen && memcmp(line, "diff ", 5)) + if (!patchlen && !starts_with(line, "diff ")) continue; /* Parsing diff header? */ if (before == -1) { - if (!memcmp(line, "index ", 6)) + if (starts_with(line, "index ")) continue; - else if (!memcmp(line, "--- ", 4)) + else if (starts_with(line, "--- ")) before = after = 1; else if (!isalpha(line[0])) break; @@ -114,14 +111,14 @@ static int get_one_patchid(struct object_id *next_oid, struct object_id *result, /* Looking for a valid hunk header? */ if (before == 0 && after == 0) { - if (!memcmp(line, "@@ -", 4)) { + if (starts_with(line, "@@ -")) { /* Parse next hunk, but ignore line numbers. */ scan_hunk_header(line, &before, &after); continue; } /* Split at the end of the patch. */ - if (memcmp(line, "diff ", 5)) + if (!starts_with(line, "diff ")) break; /* Else we're parsing another header. */ @@ -165,7 +162,7 @@ static void generate_id_list(int stable) strbuf_release(&line_buf); } -static const char patch_id_usage[] = "git patch-id [--stable | --unstable] < patch"; +static const char patch_id_usage[] = "git patch-id [--stable | --unstable]"; static int git_patch_id_config(const char *var, const char *value, void *cb) { diff --git a/builtin/prune.c b/builtin/prune.c index 0c73246c72..8f4f052285 100644 --- a/builtin/prune.c +++ b/builtin/prune.c @@ -6,7 +6,6 @@ #include "reachable.h" #include "parse-options.h" #include "progress.h" -#include "dir.h" static const char * const prune_usage[] = { N_("git prune [-n] [-v] [--expire <time>] [--] [<head>...]"), @@ -76,95 +75,6 @@ static int prune_subdir(int nr, const char *path, void *data) return 0; } -static int prune_worktree(const char *id, struct strbuf *reason) -{ - struct stat st; - char *path; - int fd, len; - - if (!is_directory(git_path("worktrees/%s", id))) { - strbuf_addf(reason, _("Removing worktrees/%s: not a valid directory"), id); - return 1; - } - if (file_exists(git_path("worktrees/%s/locked", id))) - return 0; - if (stat(git_path("worktrees/%s/gitdir", id), &st)) { - strbuf_addf(reason, _("Removing worktrees/%s: gitdir file does not exist"), id); - return 1; - } - fd = open(git_path("worktrees/%s/gitdir", id), O_RDONLY); - if (fd < 0) { - strbuf_addf(reason, _("Removing worktrees/%s: unable to read gitdir file (%s)"), - id, strerror(errno)); - return 1; - } - len = st.st_size; - path = xmalloc(len + 1); - read_in_full(fd, path, len); - close(fd); - while (len && (path[len - 1] == '\n' || path[len - 1] == '\r')) - len--; - if (!len) { - strbuf_addf(reason, _("Removing worktrees/%s: invalid gitdir file"), id); - free(path); - return 1; - } - path[len] = '\0'; - if (!file_exists(path)) { - struct stat st_link; - free(path); - /* - * the repo is moved manually and has not been - * accessed since? - */ - if (!stat(git_path("worktrees/%s/link", id), &st_link) && - st_link.st_nlink > 1) - return 0; - if (st.st_mtime <= expire) { - strbuf_addf(reason, _("Removing worktrees/%s: gitdir file points to non-existent location"), id); - return 1; - } else { - return 0; - } - } - free(path); - return 0; -} - -static void prune_worktrees(void) -{ - struct strbuf reason = STRBUF_INIT; - struct strbuf path = STRBUF_INIT; - DIR *dir = opendir(git_path("worktrees")); - struct dirent *d; - int ret; - if (!dir) - return; - while ((d = readdir(dir)) != NULL) { - if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, "..")) - continue; - strbuf_reset(&reason); - if (!prune_worktree(d->d_name, &reason)) - continue; - if (show_only || verbose) - printf("%s\n", reason.buf); - if (show_only) - continue; - strbuf_reset(&path); - strbuf_addstr(&path, git_path("worktrees/%s", d->d_name)); - ret = remove_dir_recursively(&path, 0); - if (ret < 0 && errno == ENOTDIR) - ret = unlink(path.buf); - if (ret) - error(_("failed to remove: %s"), strerror(errno)); - } - closedir(dir); - if (!show_only) - rmdir(git_path("worktrees")); - strbuf_release(&reason); - strbuf_release(&path); -} - /* * Write errors (particularly out of space) can result in * failed temporary packs (and more rarely indexes and other @@ -191,12 +101,10 @@ int cmd_prune(int argc, const char **argv, const char *prefix) { struct rev_info revs; struct progress *progress = NULL; - int do_prune_worktrees = 0; const struct option options[] = { OPT__DRY_RUN(&show_only, N_("do not remove, show only")), OPT__VERBOSE(&verbose, N_("report pruned objects")), OPT_BOOL(0, "progress", &show_progress, N_("show progress")), - OPT_BOOL(0, "worktrees", &do_prune_worktrees, N_("prune .git/worktrees")), OPT_EXPIRY_DATE(0, "expire", &expire, N_("expire objects older than <time>")), OPT_END() @@ -211,12 +119,8 @@ int cmd_prune(int argc, const char **argv, const char *prefix) argc = parse_options(argc, argv, prefix, options, prune_usage, 0); - if (do_prune_worktrees) { - if (argc) - die(_("--worktrees does not take extra arguments")); - prune_worktrees(); - return 0; - } + if (repository_format_precious_objects) + die(_("cannot prune in a precious-objects repo")); while (argc--) { unsigned char sha1[20]; diff --git a/builtin/pull.c b/builtin/pull.c new file mode 100644 index 0000000000..3ecb881b0b --- /dev/null +++ b/builtin/pull.c @@ -0,0 +1,880 @@ +/* + * Builtin "git pull" + * + * Based on git-pull.sh by Junio C Hamano + * + * Fetch one or more remote refs and merge it/them into the current HEAD. + */ +#include "cache.h" +#include "builtin.h" +#include "parse-options.h" +#include "exec_cmd.h" +#include "run-command.h" +#include "sha1-array.h" +#include "remote.h" +#include "dir.h" +#include "refs.h" +#include "revision.h" +#include "tempfile.h" +#include "lockfile.h" +#include "wt-status.h" + +enum rebase_type { + REBASE_INVALID = -1, + REBASE_FALSE = 0, + REBASE_TRUE, + REBASE_PRESERVE, + REBASE_INTERACTIVE +}; + +/** + * Parses the value of --rebase. If value is a false value, returns + * REBASE_FALSE. If value is a true value, returns REBASE_TRUE. If value is + * "preserve", returns REBASE_PRESERVE. If value is a invalid value, dies with + * a fatal error if fatal is true, otherwise returns REBASE_INVALID. + */ +static enum rebase_type parse_config_rebase(const char *key, const char *value, + int fatal) +{ + int v = git_config_maybe_bool("pull.rebase", value); + + if (!v) + return REBASE_FALSE; + else if (v > 0) + return REBASE_TRUE; + else if (!strcmp(value, "preserve")) + return REBASE_PRESERVE; + else if (!strcmp(value, "interactive")) + return REBASE_INTERACTIVE; + + if (fatal) + die(_("Invalid value for %s: %s"), key, value); + else + error(_("Invalid value for %s: %s"), key, value); + + return REBASE_INVALID; +} + +/** + * Callback for --rebase, which parses arg with parse_config_rebase(). + */ +static int parse_opt_rebase(const struct option *opt, const char *arg, int unset) +{ + enum rebase_type *value = opt->value; + + if (arg) + *value = parse_config_rebase("--rebase", arg, 0); + else + *value = unset ? REBASE_FALSE : REBASE_TRUE; + return *value == REBASE_INVALID ? -1 : 0; +} + +static const char * const pull_usage[] = { + N_("git pull [<options>] [<repository> [<refspec>...]]"), + NULL +}; + +/* Shared options */ +static int opt_verbosity; +static char *opt_progress; + +/* Options passed to git-merge or git-rebase */ +static enum rebase_type opt_rebase = -1; +static char *opt_diffstat; +static char *opt_log; +static char *opt_squash; +static char *opt_commit; +static char *opt_edit; +static char *opt_ff; +static char *opt_verify_signatures; +static int opt_autostash = -1; +static int config_autostash; +static struct argv_array opt_strategies = ARGV_ARRAY_INIT; +static struct argv_array opt_strategy_opts = ARGV_ARRAY_INIT; +static char *opt_gpg_sign; +static int opt_allow_unrelated_histories; + +/* Options passed to git-fetch */ +static char *opt_all; +static char *opt_append; +static char *opt_upload_pack; +static int opt_force; +static char *opt_tags; +static char *opt_prune; +static char *opt_recurse_submodules; +static char *max_children; +static int opt_dry_run; +static char *opt_keep; +static char *opt_depth; +static char *opt_unshallow; +static char *opt_update_shallow; +static char *opt_refmap; + +static struct option pull_options[] = { + /* Shared options */ + OPT__VERBOSITY(&opt_verbosity), + OPT_PASSTHRU(0, "progress", &opt_progress, NULL, + N_("force progress reporting"), + PARSE_OPT_NOARG), + + /* Options passed to git-merge or git-rebase */ + OPT_GROUP(N_("Options related to merging")), + { OPTION_CALLBACK, 'r', "rebase", &opt_rebase, + "false|true|preserve|interactive", + N_("incorporate changes by rebasing rather than merging"), + PARSE_OPT_OPTARG, parse_opt_rebase }, + OPT_PASSTHRU('n', NULL, &opt_diffstat, NULL, + N_("do not show a diffstat at the end of the merge"), + PARSE_OPT_NOARG | PARSE_OPT_NONEG), + OPT_PASSTHRU(0, "stat", &opt_diffstat, NULL, + N_("show a diffstat at the end of the merge"), + PARSE_OPT_NOARG), + OPT_PASSTHRU(0, "summary", &opt_diffstat, NULL, + N_("(synonym to --stat)"), + PARSE_OPT_NOARG | PARSE_OPT_HIDDEN), + OPT_PASSTHRU(0, "log", &opt_log, N_("n"), + N_("add (at most <n>) entries from shortlog to merge commit message"), + PARSE_OPT_OPTARG), + OPT_PASSTHRU(0, "squash", &opt_squash, NULL, + N_("create a single commit instead of doing a merge"), + PARSE_OPT_NOARG), + OPT_PASSTHRU(0, "commit", &opt_commit, NULL, + N_("perform a commit if the merge succeeds (default)"), + PARSE_OPT_NOARG), + OPT_PASSTHRU(0, "edit", &opt_edit, NULL, + N_("edit message before committing"), + PARSE_OPT_NOARG), + OPT_PASSTHRU(0, "ff", &opt_ff, NULL, + N_("allow fast-forward"), + PARSE_OPT_NOARG), + OPT_PASSTHRU(0, "ff-only", &opt_ff, NULL, + N_("abort if fast-forward is not possible"), + PARSE_OPT_NOARG | PARSE_OPT_NONEG), + OPT_PASSTHRU(0, "verify-signatures", &opt_verify_signatures, NULL, + N_("verify that the named commit has a valid GPG signature"), + PARSE_OPT_NOARG), + OPT_BOOL(0, "autostash", &opt_autostash, + N_("automatically stash/stash pop before and after rebase")), + OPT_PASSTHRU_ARGV('s', "strategy", &opt_strategies, N_("strategy"), + N_("merge strategy to use"), + 0), + OPT_PASSTHRU_ARGV('X', "strategy-option", &opt_strategy_opts, + N_("option=value"), + N_("option for selected merge strategy"), + 0), + OPT_PASSTHRU('S', "gpg-sign", &opt_gpg_sign, N_("key-id"), + N_("GPG sign commit"), + PARSE_OPT_OPTARG), + OPT_SET_INT(0, "allow-unrelated-histories", + &opt_allow_unrelated_histories, + N_("allow merging unrelated histories"), 1), + + /* Options passed to git-fetch */ + OPT_GROUP(N_("Options related to fetching")), + OPT_PASSTHRU(0, "all", &opt_all, NULL, + N_("fetch from all remotes"), + PARSE_OPT_NOARG), + OPT_PASSTHRU('a', "append", &opt_append, NULL, + N_("append to .git/FETCH_HEAD instead of overwriting"), + PARSE_OPT_NOARG), + OPT_PASSTHRU(0, "upload-pack", &opt_upload_pack, N_("path"), + N_("path to upload pack on remote end"), + 0), + OPT__FORCE(&opt_force, N_("force overwrite of local branch")), + OPT_PASSTHRU('t', "tags", &opt_tags, NULL, + N_("fetch all tags and associated objects"), + PARSE_OPT_NOARG), + OPT_PASSTHRU('p', "prune", &opt_prune, NULL, + N_("prune remote-tracking branches no longer on remote"), + PARSE_OPT_NOARG), + OPT_PASSTHRU(0, "recurse-submodules", &opt_recurse_submodules, + N_("on-demand"), + N_("control recursive fetching of submodules"), + PARSE_OPT_OPTARG), + OPT_PASSTHRU('j', "jobs", &max_children, N_("n"), + N_("number of submodules pulled in parallel"), + PARSE_OPT_OPTARG), + OPT_BOOL(0, "dry-run", &opt_dry_run, + N_("dry run")), + OPT_PASSTHRU('k', "keep", &opt_keep, NULL, + N_("keep downloaded pack"), + PARSE_OPT_NOARG), + OPT_PASSTHRU(0, "depth", &opt_depth, N_("depth"), + N_("deepen history of shallow clone"), + 0), + OPT_PASSTHRU(0, "unshallow", &opt_unshallow, NULL, + N_("convert to a complete repository"), + PARSE_OPT_NONEG | PARSE_OPT_NOARG), + OPT_PASSTHRU(0, "update-shallow", &opt_update_shallow, NULL, + N_("accept refs that update .git/shallow"), + PARSE_OPT_NOARG), + OPT_PASSTHRU(0, "refmap", &opt_refmap, N_("refmap"), + N_("specify fetch refmap"), + PARSE_OPT_NONEG), + + OPT_END() +}; + +/** + * Pushes "-q" or "-v" switches into arr to match the opt_verbosity level. + */ +static void argv_push_verbosity(struct argv_array *arr) +{ + int verbosity; + + for (verbosity = opt_verbosity; verbosity > 0; verbosity--) + argv_array_push(arr, "-v"); + + for (verbosity = opt_verbosity; verbosity < 0; verbosity++) + argv_array_push(arr, "-q"); +} + +/** + * Pushes "-f" switches into arr to match the opt_force level. + */ +static void argv_push_force(struct argv_array *arr) +{ + int force = opt_force; + while (force-- > 0) + argv_array_push(arr, "-f"); +} + +/** + * Sets the GIT_REFLOG_ACTION environment variable to the concatenation of argv + */ +static void set_reflog_message(int argc, const char **argv) +{ + int i; + struct strbuf msg = STRBUF_INIT; + + for (i = 0; i < argc; i++) { + if (i) + strbuf_addch(&msg, ' '); + strbuf_addstr(&msg, argv[i]); + } + + setenv("GIT_REFLOG_ACTION", msg.buf, 0); + + strbuf_release(&msg); +} + +/** + * If pull.ff is unset, returns NULL. If pull.ff is "true", returns "--ff". If + * pull.ff is "false", returns "--no-ff". If pull.ff is "only", returns + * "--ff-only". Otherwise, if pull.ff is set to an invalid value, die with an + * error. + */ +static const char *config_get_ff(void) +{ + const char *value; + + if (git_config_get_value("pull.ff", &value)) + return NULL; + + switch (git_config_maybe_bool("pull.ff", value)) { + case 0: + return "--no-ff"; + case 1: + return "--ff"; + } + + if (!strcmp(value, "only")) + return "--ff-only"; + + die(_("Invalid value for pull.ff: %s"), value); +} + +/** + * Returns the default configured value for --rebase. It first looks for the + * value of "branch.$curr_branch.rebase", where $curr_branch is the current + * branch, and if HEAD is detached or the configuration key does not exist, + * looks for the value of "pull.rebase". If both configuration keys do not + * exist, returns REBASE_FALSE. + */ +static enum rebase_type config_get_rebase(void) +{ + struct branch *curr_branch = branch_get("HEAD"); + const char *value; + + if (curr_branch) { + char *key = xstrfmt("branch.%s.rebase", curr_branch->name); + + if (!git_config_get_value(key, &value)) { + enum rebase_type ret = parse_config_rebase(key, value, 1); + free(key); + return ret; + } + + free(key); + } + + if (!git_config_get_value("pull.rebase", &value)) + return parse_config_rebase("pull.rebase", value, 1); + + return REBASE_FALSE; +} + +/** + * Read config variables. + */ +static int git_pull_config(const char *var, const char *value, void *cb) +{ + if (!strcmp(var, "rebase.autostash")) { + config_autostash = git_config_bool(var, value); + return 0; + } + return git_default_config(var, value, cb); +} + +/** + * Appends merge candidates from FETCH_HEAD that are not marked not-for-merge + * into merge_heads. + */ +static void get_merge_heads(struct sha1_array *merge_heads) +{ + const char *filename = git_path("FETCH_HEAD"); + FILE *fp; + struct strbuf sb = STRBUF_INIT; + unsigned char sha1[GIT_SHA1_RAWSZ]; + + if (!(fp = fopen(filename, "r"))) + die_errno(_("could not open '%s' for reading"), filename); + while (strbuf_getline_lf(&sb, fp) != EOF) { + if (get_sha1_hex(sb.buf, sha1)) + continue; /* invalid line: does not start with SHA1 */ + if (starts_with(sb.buf + GIT_SHA1_HEXSZ, "\tnot-for-merge\t")) + continue; /* ref is not-for-merge */ + sha1_array_append(merge_heads, sha1); + } + fclose(fp); + strbuf_release(&sb); +} + +/** + * Used by die_no_merge_candidates() as a for_each_remote() callback to + * retrieve the name of the remote if the repository only has one remote. + */ +static int get_only_remote(struct remote *remote, void *cb_data) +{ + const char **remote_name = cb_data; + + if (*remote_name) + return -1; + + *remote_name = remote->name; + return 0; +} + +/** + * Dies with the appropriate reason for why there are no merge candidates: + * + * 1. We fetched from a specific remote, and a refspec was given, but it ended + * up not fetching anything. This is usually because the user provided a + * wildcard refspec which had no matches on the remote end. + * + * 2. We fetched from a non-default remote, but didn't specify a branch to + * merge. We can't use the configured one because it applies to the default + * remote, thus the user must specify the branches to merge. + * + * 3. We fetched from the branch's or repo's default remote, but: + * + * a. We are not on a branch, so there will never be a configured branch to + * merge with. + * + * b. We are on a branch, but there is no configured branch to merge with. + * + * 4. We fetched from the branch's or repo's default remote, but the configured + * branch to merge didn't get fetched. (Either it doesn't exist, or wasn't + * part of the configured fetch refspec.) + */ +static void NORETURN die_no_merge_candidates(const char *repo, const char **refspecs) +{ + struct branch *curr_branch = branch_get("HEAD"); + const char *remote = curr_branch ? curr_branch->remote_name : NULL; + + if (*refspecs) { + if (opt_rebase) + fprintf_ln(stderr, _("There is no candidate for rebasing against among the refs that you just fetched.")); + else + fprintf_ln(stderr, _("There are no candidates for merging among the refs that you just fetched.")); + fprintf_ln(stderr, _("Generally this means that you provided a wildcard refspec which had no\n" + "matches on the remote end.")); + } else if (repo && curr_branch && (!remote || strcmp(repo, remote))) { + fprintf_ln(stderr, _("You asked to pull from the remote '%s', but did not specify\n" + "a branch. Because this is not the default configured remote\n" + "for your current branch, you must specify a branch on the command line."), + repo); + } else if (!curr_branch) { + fprintf_ln(stderr, _("You are not currently on a branch.")); + if (opt_rebase) + fprintf_ln(stderr, _("Please specify which branch you want to rebase against.")); + else + fprintf_ln(stderr, _("Please specify which branch you want to merge with.")); + fprintf_ln(stderr, _("See git-pull(1) for details.")); + fprintf(stderr, "\n"); + fprintf_ln(stderr, " git pull %s %s", _("<remote>"), _("<branch>")); + fprintf(stderr, "\n"); + } else if (!curr_branch->merge_nr) { + const char *remote_name = NULL; + + if (for_each_remote(get_only_remote, &remote_name) || !remote_name) + remote_name = _("<remote>"); + + fprintf_ln(stderr, _("There is no tracking information for the current branch.")); + if (opt_rebase) + fprintf_ln(stderr, _("Please specify which branch you want to rebase against.")); + else + fprintf_ln(stderr, _("Please specify which branch you want to merge with.")); + fprintf_ln(stderr, _("See git-pull(1) for details.")); + fprintf(stderr, "\n"); + fprintf_ln(stderr, " git pull %s %s", _("<remote>"), _("<branch>")); + fprintf(stderr, "\n"); + fprintf_ln(stderr, _("If you wish to set tracking information for this branch you can do so with:")); + fprintf(stderr, "\n"); + fprintf_ln(stderr, " git branch --set-upstream-to=%s/%s %s\n", + remote_name, _("<branch>"), curr_branch->name); + } else + fprintf_ln(stderr, _("Your configuration specifies to merge with the ref '%s'\n" + "from the remote, but no such ref was fetched."), + *curr_branch->merge_name); + exit(1); +} + +/** + * Parses argv into [<repo> [<refspecs>...]], returning their values in `repo` + * as a string and `refspecs` as a null-terminated array of strings. If `repo` + * is not provided in argv, it is set to NULL. + */ +static void parse_repo_refspecs(int argc, const char **argv, const char **repo, + const char ***refspecs) +{ + if (argc > 0) { + *repo = *argv++; + argc--; + } else + *repo = NULL; + *refspecs = argv; +} + +/** + * Runs git-fetch, returning its exit status. `repo` and `refspecs` are the + * repository and refspecs to fetch, or NULL if they are not provided. + */ +static int run_fetch(const char *repo, const char **refspecs) +{ + struct argv_array args = ARGV_ARRAY_INIT; + int ret; + + argv_array_pushl(&args, "fetch", "--update-head-ok", NULL); + + /* Shared options */ + argv_push_verbosity(&args); + if (opt_progress) + argv_array_push(&args, opt_progress); + + /* Options passed to git-fetch */ + if (opt_all) + argv_array_push(&args, opt_all); + if (opt_append) + argv_array_push(&args, opt_append); + if (opt_upload_pack) + argv_array_push(&args, opt_upload_pack); + argv_push_force(&args); + if (opt_tags) + argv_array_push(&args, opt_tags); + if (opt_prune) + argv_array_push(&args, opt_prune); + if (opt_recurse_submodules) + argv_array_push(&args, opt_recurse_submodules); + if (max_children) + argv_array_push(&args, max_children); + if (opt_dry_run) + argv_array_push(&args, "--dry-run"); + if (opt_keep) + argv_array_push(&args, opt_keep); + if (opt_depth) + argv_array_push(&args, opt_depth); + if (opt_unshallow) + argv_array_push(&args, opt_unshallow); + if (opt_update_shallow) + argv_array_push(&args, opt_update_shallow); + if (opt_refmap) + argv_array_push(&args, opt_refmap); + + if (repo) { + argv_array_push(&args, repo); + argv_array_pushv(&args, refspecs); + } else if (*refspecs) + die("BUG: refspecs without repo?"); + ret = run_command_v_opt(args.argv, RUN_GIT_CMD); + argv_array_clear(&args); + return ret; +} + +/** + * "Pulls into void" by branching off merge_head. + */ +static int pull_into_void(const unsigned char *merge_head, + const unsigned char *curr_head) +{ + /* + * Two-way merge: we treat the index as based on an empty tree, + * and try to fast-forward to HEAD. This ensures we will not lose + * index/worktree changes that the user already made on the unborn + * branch. + */ + if (checkout_fast_forward(EMPTY_TREE_SHA1_BIN, merge_head, 0)) + return 1; + + if (update_ref("initial pull", "HEAD", merge_head, curr_head, 0, UPDATE_REFS_DIE_ON_ERR)) + return 1; + + return 0; +} + +/** + * Runs git-merge, returning its exit status. + */ +static int run_merge(void) +{ + int ret; + struct argv_array args = ARGV_ARRAY_INIT; + + argv_array_pushl(&args, "merge", NULL); + + /* Shared options */ + argv_push_verbosity(&args); + if (opt_progress) + argv_array_push(&args, opt_progress); + + /* Options passed to git-merge */ + if (opt_diffstat) + argv_array_push(&args, opt_diffstat); + if (opt_log) + argv_array_push(&args, opt_log); + if (opt_squash) + argv_array_push(&args, opt_squash); + if (opt_commit) + argv_array_push(&args, opt_commit); + if (opt_edit) + argv_array_push(&args, opt_edit); + if (opt_ff) + argv_array_push(&args, opt_ff); + if (opt_verify_signatures) + argv_array_push(&args, opt_verify_signatures); + argv_array_pushv(&args, opt_strategies.argv); + argv_array_pushv(&args, opt_strategy_opts.argv); + if (opt_gpg_sign) + argv_array_push(&args, opt_gpg_sign); + if (opt_allow_unrelated_histories > 0) + argv_array_push(&args, "--allow-unrelated-histories"); + + argv_array_push(&args, "FETCH_HEAD"); + ret = run_command_v_opt(args.argv, RUN_GIT_CMD); + argv_array_clear(&args); + return ret; +} + +/** + * Returns remote's upstream branch for the current branch. If remote is NULL, + * the current branch's configured default remote is used. Returns NULL if + * `remote` does not name a valid remote, HEAD does not point to a branch, + * remote is not the branch's configured remote or the branch does not have any + * configured upstream branch. + */ +static const char *get_upstream_branch(const char *remote) +{ + struct remote *rm; + struct branch *curr_branch; + const char *curr_branch_remote; + + rm = remote_get(remote); + if (!rm) + return NULL; + + curr_branch = branch_get("HEAD"); + if (!curr_branch) + return NULL; + + curr_branch_remote = remote_for_branch(curr_branch, NULL); + assert(curr_branch_remote); + + if (strcmp(curr_branch_remote, rm->name)) + return NULL; + + return branch_get_upstream(curr_branch, NULL); +} + +/** + * Derives the remote tracking branch from the remote and refspec. + * + * FIXME: The current implementation assumes the default mapping of + * refs/heads/<branch_name> to refs/remotes/<remote_name>/<branch_name>. + */ +static const char *get_tracking_branch(const char *remote, const char *refspec) +{ + struct refspec *spec; + const char *spec_src; + const char *merge_branch; + + spec = parse_fetch_refspec(1, &refspec); + spec_src = spec->src; + if (!*spec_src || !strcmp(spec_src, "HEAD")) + spec_src = "HEAD"; + else if (skip_prefix(spec_src, "heads/", &spec_src)) + ; + else if (skip_prefix(spec_src, "refs/heads/", &spec_src)) + ; + else if (starts_with(spec_src, "refs/") || + starts_with(spec_src, "tags/") || + starts_with(spec_src, "remotes/")) + spec_src = ""; + + if (*spec_src) { + if (!strcmp(remote, ".")) + merge_branch = mkpath("refs/heads/%s", spec_src); + else + merge_branch = mkpath("refs/remotes/%s/%s", remote, spec_src); + } else + merge_branch = NULL; + + free_refspec(1, spec); + return merge_branch; +} + +/** + * Given the repo and refspecs, sets fork_point to the point at which the + * current branch forked from its remote tracking branch. Returns 0 on success, + * -1 on failure. + */ +static int get_rebase_fork_point(unsigned char *fork_point, const char *repo, + const char *refspec) +{ + int ret; + struct branch *curr_branch; + const char *remote_branch; + struct child_process cp = CHILD_PROCESS_INIT; + struct strbuf sb = STRBUF_INIT; + + curr_branch = branch_get("HEAD"); + if (!curr_branch) + return -1; + + if (refspec) + remote_branch = get_tracking_branch(repo, refspec); + else + remote_branch = get_upstream_branch(repo); + + if (!remote_branch) + return -1; + + argv_array_pushl(&cp.args, "merge-base", "--fork-point", + remote_branch, curr_branch->name, NULL); + cp.no_stdin = 1; + cp.no_stderr = 1; + cp.git_cmd = 1; + + ret = capture_command(&cp, &sb, GIT_SHA1_HEXSZ); + if (ret) + goto cleanup; + + ret = get_sha1_hex(sb.buf, fork_point); + if (ret) + goto cleanup; + +cleanup: + strbuf_release(&sb); + return ret ? -1 : 0; +} + +/** + * Sets merge_base to the octopus merge base of curr_head, merge_head and + * fork_point. Returns 0 if a merge base is found, 1 otherwise. + */ +static int get_octopus_merge_base(unsigned char *merge_base, + const unsigned char *curr_head, + const unsigned char *merge_head, + const unsigned char *fork_point) +{ + struct commit_list *revs = NULL, *result; + + commit_list_insert(lookup_commit_reference(curr_head), &revs); + commit_list_insert(lookup_commit_reference(merge_head), &revs); + if (!is_null_sha1(fork_point)) + commit_list_insert(lookup_commit_reference(fork_point), &revs); + + result = reduce_heads(get_octopus_merge_bases(revs)); + free_commit_list(revs); + if (!result) + return 1; + + hashcpy(merge_base, result->item->object.oid.hash); + return 0; +} + +/** + * Given the current HEAD SHA1, the merge head returned from git-fetch and the + * fork point calculated by get_rebase_fork_point(), runs git-rebase with the + * appropriate arguments and returns its exit status. + */ +static int run_rebase(const unsigned char *curr_head, + const unsigned char *merge_head, + const unsigned char *fork_point) +{ + int ret; + unsigned char oct_merge_base[GIT_SHA1_RAWSZ]; + struct argv_array args = ARGV_ARRAY_INIT; + + if (!get_octopus_merge_base(oct_merge_base, curr_head, merge_head, fork_point)) + if (!is_null_sha1(fork_point) && !hashcmp(oct_merge_base, fork_point)) + fork_point = NULL; + + argv_array_push(&args, "rebase"); + + /* Shared options */ + argv_push_verbosity(&args); + + /* Options passed to git-rebase */ + if (opt_rebase == REBASE_PRESERVE) + argv_array_push(&args, "--preserve-merges"); + else if (opt_rebase == REBASE_INTERACTIVE) + argv_array_push(&args, "--interactive"); + if (opt_diffstat) + argv_array_push(&args, opt_diffstat); + argv_array_pushv(&args, opt_strategies.argv); + argv_array_pushv(&args, opt_strategy_opts.argv); + if (opt_gpg_sign) + argv_array_push(&args, opt_gpg_sign); + if (opt_autostash == 0) + argv_array_push(&args, "--no-autostash"); + else if (opt_autostash == 1) + argv_array_push(&args, "--autostash"); + if (opt_verify_signatures && + !strcmp(opt_verify_signatures, "--verify-signatures")) + warning(_("ignoring --verify-signatures for rebase")); + + argv_array_push(&args, "--onto"); + argv_array_push(&args, sha1_to_hex(merge_head)); + + if (fork_point && !is_null_sha1(fork_point)) + argv_array_push(&args, sha1_to_hex(fork_point)); + else + argv_array_push(&args, sha1_to_hex(merge_head)); + + ret = run_command_v_opt(args.argv, RUN_GIT_CMD); + argv_array_clear(&args); + return ret; +} + +int cmd_pull(int argc, const char **argv, const char *prefix) +{ + const char *repo, **refspecs; + struct sha1_array merge_heads = SHA1_ARRAY_INIT; + unsigned char orig_head[GIT_SHA1_RAWSZ], curr_head[GIT_SHA1_RAWSZ]; + unsigned char rebase_fork_point[GIT_SHA1_RAWSZ]; + + if (!getenv("GIT_REFLOG_ACTION")) + set_reflog_message(argc, argv); + + argc = parse_options(argc, argv, prefix, pull_options, pull_usage, 0); + + parse_repo_refspecs(argc, argv, &repo, &refspecs); + + if (!opt_ff) + opt_ff = xstrdup_or_null(config_get_ff()); + + if (opt_rebase < 0) + opt_rebase = config_get_rebase(); + + git_config(git_pull_config, NULL); + + if (read_cache_unmerged()) + die_resolve_conflict("pull"); + + if (file_exists(git_path("MERGE_HEAD"))) + die_conclude_merge(); + + if (get_sha1("HEAD", orig_head)) + hashclr(orig_head); + + if (!opt_rebase && opt_autostash != -1) + die(_("--[no-]autostash option is only valid with --rebase.")); + + if (opt_rebase) { + int autostash = config_autostash; + if (opt_autostash != -1) + autostash = opt_autostash; + + if (is_null_sha1(orig_head) && !is_cache_unborn()) + die(_("Updating an unborn branch with changes added to the index.")); + + if (!autostash) + require_clean_work_tree(N_("pull with rebase"), + _("please commit or stash them."), 1, 0); + + if (get_rebase_fork_point(rebase_fork_point, repo, *refspecs)) + hashclr(rebase_fork_point); + } + + if (run_fetch(repo, refspecs)) + return 1; + + if (opt_dry_run) + return 0; + + if (get_sha1("HEAD", curr_head)) + hashclr(curr_head); + + if (!is_null_sha1(orig_head) && !is_null_sha1(curr_head) && + hashcmp(orig_head, curr_head)) { + /* + * The fetch involved updating the current branch. + * + * The working tree and the index file are still based on + * orig_head commit, but we are merging into curr_head. + * Update the working tree to match curr_head. + */ + + warning(_("fetch updated the current branch head.\n" + "fast-forwarding your working tree from\n" + "commit %s."), sha1_to_hex(orig_head)); + + if (checkout_fast_forward(orig_head, curr_head, 0)) + die(_("Cannot fast-forward your working tree.\n" + "After making sure that you saved anything precious from\n" + "$ git diff %s\n" + "output, run\n" + "$ git reset --hard\n" + "to recover."), sha1_to_hex(orig_head)); + } + + get_merge_heads(&merge_heads); + + if (!merge_heads.nr) + die_no_merge_candidates(repo, refspecs); + + if (is_null_sha1(orig_head)) { + if (merge_heads.nr > 1) + die(_("Cannot merge multiple branches into empty head.")); + return pull_into_void(*merge_heads.sha1, curr_head); + } + if (opt_rebase && merge_heads.nr > 1) + die(_("Cannot rebase onto multiple branches.")); + + if (opt_rebase) { + struct commit_list *list = NULL; + struct commit *merge_head, *head; + + head = lookup_commit_reference(orig_head); + commit_list_insert(head, &list); + merge_head = lookup_commit_reference(merge_heads.sha1[0]); + if (is_descendant_of(merge_head, list)) { + /* we can fast-forward this without invoking rebase */ + opt_ff = "--ff-only"; + return run_merge(); + } + return run_rebase(curr_head, *merge_heads.sha1, rebase_fork_point); + } else { + return run_merge(); + } +} diff --git a/builtin/push.c b/builtin/push.c index 57c138bd7b..9307ad56a9 100644 --- a/builtin/push.c +++ b/builtin/push.c @@ -9,6 +9,8 @@ #include "transport.h" #include "parse-options.h" #include "submodule.h" +#include "submodule-config.h" +#include "send-pack.h" static const char * const push_usage[] = { N_("git push [<options>] [<repository> [<refspec>...]]"), @@ -20,6 +22,8 @@ static int deleterefs; static const char *receivepack; static int verbosity; static int progress = -1; +static int recurse_submodules = RECURSE_SUBMODULES_DEFAULT; +static enum transport_family family; static struct push_cas_option cas; @@ -190,46 +194,18 @@ static void setup_push_upstream(struct remote *remote, struct branch *branch, die_push_simple(branch, remote); } - strbuf_addf(&refspec, "%s:%s", branch->name, branch->merge[0]->src); + strbuf_addf(&refspec, "%s:%s", branch->refname, branch->merge[0]->src); add_refspec(refspec.buf); } static void setup_push_current(struct remote *remote, struct branch *branch) { + struct strbuf refspec = STRBUF_INIT; + if (!branch) die(_(message_detached_head_die), remote->name); - add_refspec(branch->name); -} - -static char warn_unspecified_push_default_msg[] = -N_("push.default is unset; its implicit value has changed in\n" - "Git 2.0 from 'matching' to 'simple'. To squelch this message\n" - "and maintain the traditional behavior, use:\n" - "\n" - " git config --global push.default matching\n" - "\n" - "To squelch this message and adopt the new behavior now, use:\n" - "\n" - " git config --global push.default simple\n" - "\n" - "When push.default is set to 'matching', git will push local branches\n" - "to the remote branches that already exist with the same name.\n" - "\n" - "Since Git 2.0, Git defaults to the more conservative 'simple'\n" - "behavior, which only pushes the current branch to the corresponding\n" - "remote branch that 'git pull' uses to update the current branch.\n" - "\n" - "See 'git help config' and search for 'push.default' for further information.\n" - "(the 'simple' mode was introduced in Git 1.7.11. Use the similar mode\n" - "'current' instead of 'simple' if you sometimes use older versions of Git)"); - -static void warn_unspecified_push_default_configuration(void) -{ - static int warn_once; - - if (warn_once++) - return; - warning("%s\n", _(warn_unspecified_push_default_msg)); + strbuf_addf(&refspec, "%s:%s", branch->refname, branch->refname); + add_refspec(refspec.buf); } static int is_workflow_triangular(struct remote *remote) @@ -250,9 +226,6 @@ static void setup_default_push_refspecs(struct remote *remote) break; case PUSH_DEFAULT_UNSPECIFIED: - warn_unspecified_push_default_configuration(); - /* fallthru */ - case PUSH_DEFAULT_SIMPLE: if (triangular) setup_push_current(remote, branch); @@ -343,6 +316,7 @@ static int push_with_options(struct transport *transport, int flags) unsigned int reject_reasons; transport_set_verbosity(transport, verbosity, progress); + transport->family = family; if (receivepack) transport_set_option(transport, @@ -382,7 +356,8 @@ static int push_with_options(struct transport *transport, int flags) return 1; } -static int do_push(const char *repo, int flags) +static int do_push(const char *repo, int flags, + const struct string_list *push_options) { int i, errs; struct remote *remote = pushremote_get(repo); @@ -405,6 +380,9 @@ static int do_push(const char *repo, int flags) if (remote->mirror) flags |= (TRANSPORT_PUSH_MIRROR|TRANSPORT_PUSH_FORCE); + if (push_options->nr) + flags |= TRANSPORT_PUSH_OPTIONS; + if ((flags & TRANSPORT_PUSH_ALL) && refspec) { if (!strcmp(*refspec, "refs/tags/*")) return error(_("--all and --tags are incompatible")); @@ -435,13 +413,16 @@ static int do_push(const char *repo, int flags) for (i = 0; i < url_nr; i++) { struct transport *transport = transport_get(remote, url[i]); + if (flags & TRANSPORT_PUSH_OPTIONS) + transport->push_options = push_options; if (push_with_options(transport, flags)) errs++; } } else { struct transport *transport = transport_get(remote, NULL); - + if (flags & TRANSPORT_PUSH_OPTIONS) + transport->push_options = push_options; if (push_with_options(transport, flags)) errs++; } @@ -451,26 +432,36 @@ static int do_push(const char *repo, int flags) static int option_parse_recurse_submodules(const struct option *opt, const char *arg, int unset) { - int *flags = opt->value; + int *recurse_submodules = opt->value; - if (*flags & (TRANSPORT_RECURSE_SUBMODULES_CHECK | - TRANSPORT_RECURSE_SUBMODULES_ON_DEMAND)) - die("%s can only be used once.", opt->long_name); - - if (arg) { - if (!strcmp(arg, "check")) - *flags |= TRANSPORT_RECURSE_SUBMODULES_CHECK; - else if (!strcmp(arg, "on-demand")) - *flags |= TRANSPORT_RECURSE_SUBMODULES_ON_DEMAND; - else - die("bad %s argument: %s", opt->long_name, arg); - } else - die("option %s needs an argument (check|on-demand)", - opt->long_name); + if (unset) + *recurse_submodules = RECURSE_SUBMODULES_OFF; + else if (arg) + *recurse_submodules = parse_push_recurse_submodules_arg(opt->long_name, arg); + else + die("%s missing parameter", opt->long_name); return 0; } +static void set_push_cert_flags(int *flags, int v) +{ + switch (v) { + case SEND_PACK_PUSH_CERT_NEVER: + *flags &= ~(TRANSPORT_PUSH_CERT_ALWAYS | TRANSPORT_PUSH_CERT_IF_ASKED); + break; + case SEND_PACK_PUSH_CERT_ALWAYS: + *flags |= TRANSPORT_PUSH_CERT_ALWAYS; + *flags &= ~TRANSPORT_PUSH_CERT_IF_ASKED; + break; + case SEND_PACK_PUSH_CERT_IF_ASKED: + *flags |= TRANSPORT_PUSH_CERT_IF_ASKED; + *flags &= ~TRANSPORT_PUSH_CERT_ALWAYS; + break; + } +} + + static int git_push_config(const char *k, const char *v, void *cb) { int *flags = cb; @@ -486,6 +477,27 @@ static int git_push_config(const char *k, const char *v, void *cb) else *flags &= ~TRANSPORT_PUSH_FOLLOW_TAGS; return 0; + } else if (!strcmp(k, "push.gpgsign")) { + const char *value; + if (!git_config_get_value("push.gpgsign", &value)) { + switch (git_config_maybe_bool("push.gpgsign", value)) { + case 0: + set_push_cert_flags(flags, SEND_PACK_PUSH_CERT_NEVER); + break; + case 1: + set_push_cert_flags(flags, SEND_PACK_PUSH_CERT_ALWAYS); + break; + default: + if (value && !strcasecmp(value, "if-asked")) + set_push_cert_flags(flags, SEND_PACK_PUSH_CERT_IF_ASKED); + else + return error("Invalid value for '%s'", k); + } + } + } else if (!strcmp(k, "push.recursesubmodules")) { + const char *value; + if (!git_config_get_value("push.recursesubmodules", &value)) + recurse_submodules = parse_push_recurse_submodules_arg(k, value); } return git_default_config(k, v, NULL); @@ -495,15 +507,19 @@ int cmd_push(int argc, const char **argv, const char *prefix) { int flags = 0; int tags = 0; + int push_cert = -1; int rc; const char *repo = NULL; /* default repository */ + static struct string_list push_options = STRING_LIST_INIT_DUP; + static struct string_list_item *item; + struct option options[] = { OPT__VERBOSITY(&verbosity), OPT_STRING( 0 , "repo", &repo, N_("repository"), N_("repository")), OPT_BIT( 0 , "all", &flags, N_("push all refs"), TRANSPORT_PUSH_ALL), OPT_BIT( 0 , "mirror", &flags, N_("mirror all refs"), (TRANSPORT_PUSH_MIRROR|TRANSPORT_PUSH_FORCE)), - OPT_BOOL( 0, "delete", &deleterefs, N_("delete refs")), + OPT_BOOL('d', "delete", &deleterefs, N_("delete refs")), OPT_BOOL( 0 , "tags", &tags, N_("push tags (can't be used with --all or --mirror)")), OPT_BIT('n' , "dry-run", &flags, N_("dry run"), TRANSPORT_PUSH_DRY_RUN), OPT_BIT( 0, "porcelain", &flags, N_("machine-readable output"), TRANSPORT_PUSH_PORCELAIN), @@ -512,7 +528,7 @@ int cmd_push(int argc, const char **argv, const char *prefix) 0, CAS_OPT_NAME, &cas, N_("refname>:<expect"), N_("require old value of ref to be at this value"), PARSE_OPT_OPTARG, parseopt_push_cas_option }, - { OPTION_CALLBACK, 0, "recurse-submodules", &flags, "check|on-demand", + { OPTION_CALLBACK, 0, "recurse-submodules", &recurse_submodules, "check|on-demand|no", N_("control recursive pushing of submodules"), PARSE_OPT_OPTARG, option_parse_recurse_submodules }, OPT_BOOL( 0 , "thin", &thin, N_("use thin pack")), @@ -526,20 +542,33 @@ int cmd_push(int argc, const char **argv, const char *prefix) OPT_BIT(0, "no-verify", &flags, N_("bypass pre-push hook"), TRANSPORT_PUSH_NO_HOOK), OPT_BIT(0, "follow-tags", &flags, N_("push missing but relevant tags"), TRANSPORT_PUSH_FOLLOW_TAGS), - OPT_BIT(0, "signed", &flags, N_("GPG sign the push"), TRANSPORT_PUSH_CERT), + { OPTION_CALLBACK, + 0, "signed", &push_cert, "yes|no|if-asked", N_("GPG sign the push"), + PARSE_OPT_OPTARG, option_parse_push_signed }, OPT_BIT(0, "atomic", &flags, N_("request atomic transaction on remote side"), TRANSPORT_PUSH_ATOMIC), + OPT_STRING_LIST('o', "push-option", &push_options, N_("server-specific"), N_("option to transmit")), + OPT_SET_INT('4', "ipv4", &family, N_("use IPv4 addresses only"), + TRANSPORT_FAMILY_IPV4), + OPT_SET_INT('6', "ipv6", &family, N_("use IPv6 addresses only"), + TRANSPORT_FAMILY_IPV6), OPT_END() }; packet_trace_identity("push"); git_config(git_push_config, &flags); argc = parse_options(argc, argv, prefix, options, push_usage, 0); + set_push_cert_flags(&flags, push_cert); if (deleterefs && (tags || (flags & (TRANSPORT_PUSH_ALL | TRANSPORT_PUSH_MIRROR)))) die(_("--delete is incompatible with --all, --mirror and --tags")); if (deleterefs && argc < 2) die(_("--delete doesn't make sense without any refs")); + if (recurse_submodules == RECURSE_SUBMODULES_CHECK) + flags |= TRANSPORT_RECURSE_SUBMODULES_CHECK; + else if (recurse_submodules == RECURSE_SUBMODULES_ON_DEMAND) + flags |= TRANSPORT_RECURSE_SUBMODULES_ON_DEMAND; + if (tags) add_refspec("refs/tags/*"); @@ -548,7 +577,11 @@ int cmd_push(int argc, const char **argv, const char *prefix) set_refspecs(argv + 1, argc - 1, repo); } - rc = do_push(repo, flags); + for_each_string_list_item(item, &push_options) + if (strchr(item->string, '\n')) + die(_("push options must not have new line characters")); + + rc = do_push(repo, flags, &push_options); if (rc == -1) usage_with_options(push_usage, options); else diff --git a/builtin/read-tree.c b/builtin/read-tree.c index 43b47f72f1..9bd1fd755e 100644 --- a/builtin/read-tree.c +++ b/builtin/read-tree.c @@ -34,7 +34,7 @@ static int list_tree(unsigned char *sha1) } static const char * const read_tree_usage[] = { - N_("git read-tree [[-m [--trivial] [--aggressive] | --reset | --prefix=<prefix>] [-u [--exclude-per-directory=<gitignore>] | -i]] [--no-sparse-checkout] [--index-output=<file>] (--empty | <tree-ish1> [<tree-ish2> [<tree-ish3>]])"), + N_("git read-tree [(-m [--trivial] [--aggressive] | --reset | --prefix=<prefix>) [-u [--exclude-per-directory=<gitignore>] | -i]] [--no-sparse-checkout] [--index-output=<file>] (--empty | <tree-ish1> [<tree-ish2> [<tree-ish3>]])"), NULL }; @@ -78,7 +78,7 @@ static void debug_stage(const char *label, const struct cache_entry *ce, else printf("%06o #%d %s %.8s\n", ce->ce_mode, ce_stage(ce), ce->name, - sha1_to_hex(ce->sha1)); + oid_to_hex(&ce->oid)); } static int debug_merge(const struct cache_entry * const *stages, @@ -90,7 +90,7 @@ static int debug_merge(const struct cache_entry * const *stages, debug_stage("index", stages[0], o); for (i = 1; i <= o->merge_size; i++) { char buf[24]; - sprintf(buf, "ent#%d", i); + xsnprintf(buf, sizeof(buf), "ent#%d", i); debug_stage(buf, stages[i], o); } return 0; diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c index 94d0571776..e6b3879a5b 100644 --- a/builtin/receive-pack.c +++ b/builtin/receive-pack.c @@ -19,8 +19,13 @@ #include "tag.h" #include "gpg-interface.h" #include "sigchain.h" +#include "fsck.h" +#include "tmp-objdir.h" -static const char receive_pack_usage[] = "git receive-pack <git-dir>"; +static const char * const receive_pack_usage[] = { + N_("git receive-pack <git-dir>"), + NULL +}; enum deny_action { DENY_UNCONFIGURED, @@ -36,18 +41,22 @@ static enum deny_action deny_current_branch = DENY_UNCONFIGURED; static enum deny_action deny_delete_current = DENY_UNCONFIGURED; static int receive_fsck_objects = -1; static int transfer_fsck_objects = -1; +static struct strbuf fsck_msg_types = STRBUF_INIT; static int receive_unpack_limit = -1; static int transfer_unpack_limit = -1; static int advertise_atomic_push = 1; +static int advertise_push_options; static int unpack_limit = 100; +static off_t max_input_size; static int report_status; static int use_sideband; static int use_atomic; +static int use_push_options; static int quiet; static int prefer_ofs_delta = 1; static int auto_update_server_info; static int auto_gc = 1; -static int fix_thin = 1; +static int reject_thin; static int stateless_rpc; static const char *service_dir; static const char *head_name; @@ -71,6 +80,15 @@ static long nonce_stamp_slop; static unsigned long nonce_stamp_slop_limit; static struct ref_transaction *transaction; +static enum { + KEEPALIVE_NEVER = 0, + KEEPALIVE_AFTER_NUL, + KEEPALIVE_ALWAYS +} use_keepalive; +static int keepalive_in_sec = 5; + +static struct tmp_objdir *tmp_objdir; + static enum deny_action parse_deny_action(const char *var, const char *value) { if (value) { @@ -115,6 +133,26 @@ static int receive_pack_config(const char *var, const char *value, void *cb) return 0; } + if (strcmp(var, "receive.fsck.skiplist") == 0) { + const char *path; + + if (git_config_pathname(&path, var, value)) + return 1; + strbuf_addf(&fsck_msg_types, "%cskiplist=%s", + fsck_msg_types.len ? ',' : '=', path); + free((char *)path); + return 0; + } + + if (skip_prefix(var, "receive.fsck.", &var)) { + if (is_valid_msg_type(var, value)) + strbuf_addf(&fsck_msg_types, "%c%s=%s", + fsck_msg_types.len ? ',' : '=', var, value); + else + warning("Skipping unknown msg id '%s'", var); + return 0; + } + if (strcmp(var, "receive.fsckobjects") == 0) { receive_fsck_objects = git_config_bool(var, value); return 0; @@ -168,16 +206,28 @@ static int receive_pack_config(const char *var, const char *value, void *cb) return 0; } + if (strcmp(var, "receive.advertisepushoptions") == 0) { + advertise_push_options = git_config_bool(var, value); + return 0; + } + + if (strcmp(var, "receive.keepalive") == 0) { + keepalive_in_sec = git_config_int(var, value); + return 0; + } + + if (strcmp(var, "receive.maxinputsize") == 0) { + max_input_size = git_config_int64(var, value); + return 0; + } + return git_default_config(var, value, cb); } static void show_ref(const char *path, const unsigned char *sha1) { - if (ref_is_hidden(path)) - return; - if (sent_capabilities) { - packet_write(1, "%s %s\n", sha1_to_hex(sha1), path); + packet_write_fmt(1, "%s %s\n", sha1_to_hex(sha1), path); } else { struct strbuf cap = STRBUF_INIT; @@ -189,17 +239,24 @@ static void show_ref(const char *path, const unsigned char *sha1) strbuf_addstr(&cap, " ofs-delta"); if (push_cert_nonce) strbuf_addf(&cap, " push-cert=%s", push_cert_nonce); + if (advertise_push_options) + strbuf_addstr(&cap, " push-options"); strbuf_addf(&cap, " agent=%s", git_user_agent_sanitized()); - packet_write(1, "%s %s%c%s\n", + packet_write_fmt(1, "%s %s%c%s\n", sha1_to_hex(sha1), path, 0, cap.buf); strbuf_release(&cap); sent_capabilities = 1; } } -static int show_ref_cb(const char *path, const struct object_id *oid, int flag, void *unused) +static int show_ref_cb(const char *path_full, const struct object_id *oid, + int flag, void *unused) { - path = strip_namespace(path); + const char *path = strip_namespace(path_full); + + if (ref_is_hidden(path, path_full)) + return 0; + /* * Advertise refs outside our current namespace as ".have" * refs, so that the client can use them to minimize data @@ -214,15 +271,16 @@ static int show_ref_cb(const char *path, const struct object_id *oid, int flag, return 0; } -static void show_one_alternate_sha1(const unsigned char sha1[20], void *unused) +static int show_one_alternate_sha1(const unsigned char sha1[20], void *unused) { show_ref(".have", sha1); + return 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); + sha1_array_append(sa, ref->old_oid.hash); } static void write_head_info(void) @@ -258,10 +316,10 @@ static void rp_warning(const char *err, ...) __attribute__((format (printf, 1, 2 static void report_message(const char *prefix, const char *err, va_list params) { - int sz = strlen(prefix); + int sz; char msg[4096]; - strncpy(msg, prefix, sz); + sz = xsnprintf(msg, sizeof(msg), "%s", prefix); sz += vsnprintf(msg + sz, sizeof(msg) - sz, err, params); if (sz > (sizeof(msg) - 1)) sz = sizeof(msg) - 1; @@ -292,10 +350,60 @@ static void rp_error(const char *err, ...) static int copy_to_sideband(int in, int out, void *arg) { char data[128]; + int keepalive_active = 0; + + if (keepalive_in_sec <= 0) + use_keepalive = KEEPALIVE_NEVER; + if (use_keepalive == KEEPALIVE_ALWAYS) + keepalive_active = 1; + while (1) { - ssize_t sz = xread(in, data, sizeof(data)); + ssize_t sz; + + if (keepalive_active) { + struct pollfd pfd; + int ret; + + pfd.fd = in; + pfd.events = POLLIN; + ret = poll(&pfd, 1, 1000 * keepalive_in_sec); + + if (ret < 0) { + if (errno == EINTR) + continue; + else + break; + } else if (ret == 0) { + /* no data; send a keepalive packet */ + static const char buf[] = "0005\1"; + write_or_die(1, buf, sizeof(buf) - 1); + continue; + } /* else there is actual data to read */ + } + + sz = xread(in, data, sizeof(data)); if (sz <= 0) break; + + if (use_keepalive == KEEPALIVE_AFTER_NUL && !keepalive_active) { + const char *p = memchr(data, '\0', sz); + if (p) { + /* + * The NUL tells us to start sending keepalives. Make + * sure we send any other data we read along + * with it. + */ + keepalive_active = 1; + send_sideband(1, 2, data, p - data, use_sideband); + send_sideband(1, 2, p + 1, sz - (p - data + 1), use_sideband); + continue; + } + } + + /* + * Either we're not looking for a NUL signal, or we didn't see + * it yet; just pass along the data. + */ send_sideband(1, 2, data, sz, use_sideband); } close(in); @@ -523,8 +631,16 @@ static void prepare_push_cert_sha1(struct child_process *proc) } } +struct receive_hook_feed_state { + struct command *cmd; + int skip_broken; + struct strbuf buf; + const struct string_list *push_options; +}; + typedef int (*feed_fn)(void *, const char **, size_t *); -static int run_and_feed_hook(const char *hook_name, feed_fn feed, void *feed_state) +static int run_and_feed_hook(const char *hook_name, feed_fn feed, + struct receive_hook_feed_state *feed_state) { struct child_process proc = CHILD_PROCESS_INIT; struct async muxer; @@ -540,6 +656,19 @@ static int run_and_feed_hook(const char *hook_name, feed_fn feed, void *feed_sta proc.argv = argv; proc.in = -1; proc.stdout_to_stderr = 1; + if (feed_state->push_options) { + int i; + for (i = 0; i < feed_state->push_options->nr; i++) + argv_array_pushf(&proc.env_array, + "GIT_PUSH_OPTION_%d=%s", i, + feed_state->push_options->items[i].string); + argv_array_pushf(&proc.env_array, "GIT_PUSH_OPTION_COUNT=%d", + feed_state->push_options->nr); + } else + argv_array_pushf(&proc.env_array, "GIT_PUSH_OPTION_COUNT"); + + if (tmp_objdir) + argv_array_pushv(&proc.env_array, tmp_objdir_env(tmp_objdir)); if (use_sideband) { memset(&muxer, 0, sizeof(muxer)); @@ -579,12 +708,6 @@ static int run_and_feed_hook(const char *hook_name, feed_fn feed, void *feed_sta return finish_command(&proc); } -struct receive_hook_feed_state { - struct command *cmd; - int skip_broken; - struct strbuf buf; -}; - static int feed_receive_hook(void *state_, const char **bufp, size_t *sizep) { struct receive_hook_feed_state *state = state_; @@ -607,8 +730,10 @@ static int feed_receive_hook(void *state_, const char **bufp, size_t *sizep) return 0; } -static int run_receive_hook(struct command *commands, const char *hook_name, - int skip_broken) +static int run_receive_hook(struct command *commands, + const char *hook_name, + int skip_broken, + const struct string_list *push_options) { struct receive_hook_feed_state state; int status; @@ -619,6 +744,7 @@ static int run_receive_hook(struct command *commands, const char *hook_name, if (feed_receive_hook(&state, NULL, NULL)) return 0; state.cmd = commands; + state.push_options = push_options; status = run_and_feed_hook(hook_name, feed_receive_hook, &state); strbuf_release(&state.buf); return status; @@ -643,6 +769,7 @@ static int run_update_hook(struct command *cmd) proc.stdout_to_stderr = 1; proc.err = use_sideband ? -1 : 0; proc.argv = argv; + proc.env = tmp_objdir_env(tmp_objdir); code = start_command(&proc); if (code) @@ -662,47 +789,39 @@ static int is_ref_checked_out(const char *ref) return !strcmp(head_name, ref); } -static char *refuse_unconfigured_deny_msg[] = { - "By default, updating the current branch in a non-bare repository", - "is denied, because it will make the index and work tree inconsistent", - "with what you pushed, and will require 'git reset --hard' to match", - "the work tree to HEAD.", - "", - "You can set 'receive.denyCurrentBranch' configuration variable to", - "'ignore' or 'warn' in the remote repository to allow pushing into", - "its current branch; however, this is not recommended unless you", - "arranged to update its work tree to match what you pushed in some", - "other way.", - "", - "To squelch this message and still keep the default behaviour, set", - "'receive.denyCurrentBranch' configuration variable to 'refuse'." -}; +static char *refuse_unconfigured_deny_msg = + N_("By default, updating the current branch in a non-bare repository\n" + "is denied, because it will make the index and work tree inconsistent\n" + "with what you pushed, and will require 'git reset --hard' to match\n" + "the work tree to HEAD.\n" + "\n" + "You can set 'receive.denyCurrentBranch' configuration variable to\n" + "'ignore' or 'warn' in the remote repository to allow pushing into\n" + "its current branch; however, this is not recommended unless you\n" + "arranged to update its work tree to match what you pushed in some\n" + "other way.\n" + "\n" + "To squelch this message and still keep the default behaviour, set\n" + "'receive.denyCurrentBranch' configuration variable to 'refuse'."); static void refuse_unconfigured_deny(void) { - int i; - for (i = 0; i < ARRAY_SIZE(refuse_unconfigured_deny_msg); i++) - rp_error("%s", refuse_unconfigured_deny_msg[i]); + rp_error("%s", _(refuse_unconfigured_deny_msg)); } -static char *refuse_unconfigured_deny_delete_current_msg[] = { - "By default, deleting the current branch is denied, because the next", - "'git clone' won't result in any file checked out, causing confusion.", - "", - "You can set 'receive.denyDeleteCurrent' configuration variable to", - "'warn' or 'ignore' in the remote repository to allow deleting the", - "current branch, with or without a warning message.", - "", - "To squelch this message, you can set it to 'refuse'." -}; +static char *refuse_unconfigured_deny_delete_current_msg = + N_("By default, deleting the current branch is denied, because the next\n" + "'git clone' won't result in any file checked out, causing confusion.\n" + "\n" + "You can set 'receive.denyDeleteCurrent' configuration variable to\n" + "'warn' or 'ignore' in the remote repository to allow deleting the\n" + "current branch, with or without a warning message.\n" + "\n" + "To squelch this message, you can set it to 'refuse'."); static void refuse_unconfigured_deny_delete_current(void) { - int i; - for (i = 0; - i < ARRAY_SIZE(refuse_unconfigured_deny_delete_current_msg); - i++) - rp_error("%s", refuse_unconfigured_deny_delete_current_msg[i]); + rp_error("%s", _(refuse_unconfigured_deny_delete_current_msg)); } static int command_singleton_iterator(void *cb_data, unsigned char sha1[20]); @@ -710,7 +829,7 @@ static int update_shallow_ref(struct command *cmd, struct shallow_info *si) { static struct lock_file shallow_lock; struct sha1_array extra = SHA1_ARRAY_INIT; - const char *alt_file; + struct check_connected_options opt = CHECK_CONNECTED_INIT; uint32_t mask = 1 << (cmd->index % 32); int i; @@ -722,9 +841,9 @@ static int update_shallow_ref(struct command *cmd, struct shallow_info *si) !delayed_reachability_test(si, i)) sha1_array_append(&extra, si->shallow->sha1[i]); - setup_alternate_shallow(&shallow_lock, &alt_file, &extra); - if (check_shallow_connected(command_singleton_iterator, - 0, cmd, alt_file)) { + opt.env = tmp_objdir_env(tmp_objdir); + setup_alternate_shallow(&shallow_lock, &opt.shallow_file, &extra); + if (check_connected(command_singleton_iterator, cmd, &opt)) { rollback_lock_file(&shallow_lock); sha1_array_clear(&extra); return -1; @@ -911,7 +1030,7 @@ static const char *update(struct command *cmd, struct shallow_info *si) return "deletion prohibited"; } - if (!strcmp(namespaced_name, head_name)) { + if (head_name && !strcmp(namespaced_name, head_name)) { switch (deny_delete_current) { case DENY_IGNORE: break; @@ -1007,7 +1126,6 @@ static void run_update_post_hook(struct command *commands) { struct command *cmd; int argc; - const char **argv; struct child_process proc = CHILD_PROCESS_INIT; const char *hook; @@ -1020,21 +1138,16 @@ static void run_update_post_hook(struct command *commands) if (!argc || !hook) return; - argv = xmalloc(sizeof(*argv) * (2 + argc)); - argv[0] = hook; - - for (argc = 1, cmd = commands; cmd; cmd = cmd->next) { + argv_array_push(&proc.args, hook); + for (cmd = commands; cmd; cmd = cmd->next) { if (cmd->error_string || cmd->did_not_exist) continue; - argv[argc] = xstrdup(cmd->ref_name); - argc++; + argv_array_push(&proc.args, cmd->ref_name); } - argv[argc] = NULL; proc.no_stdin = 1; proc.stdout_to_stderr = 1; proc.err = use_sideband ? -1 : 0; - proc.argv = argv; if (!start_command(&proc)) { if (use_sideband) @@ -1049,8 +1162,7 @@ static void check_aliased_update(struct command *cmd, struct string_list *list) 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]; + unsigned char sha1[GIT_SHA1_RAWSZ]; int flag; strbuf_addf(&buf, "%s%s", get_git_namespace(), cmd->ref_name); @@ -1060,13 +1172,13 @@ static void check_aliased_update(struct command *cmd, struct string_list *list) 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; } + dst_name = strip_namespace(dst_name); if ((item = string_list_lookup(list, dst_name)) == NULL) return; @@ -1081,14 +1193,14 @@ static void check_aliased_update(struct command *cmd, struct string_list *list) dst_cmd->skip_update = 1; - strcpy(cmd_oldh, find_unique_abbrev(cmd->old_sha1, DEFAULT_ABBREV)); - strcpy(cmd_newh, find_unique_abbrev(cmd->new_sha1, DEFAULT_ABBREV)); - strcpy(dst_oldh, find_unique_abbrev(dst_cmd->old_sha1, DEFAULT_ABBREV)); - strcpy(dst_newh, find_unique_abbrev(dst_cmd->new_sha1, DEFAULT_ABBREV)); rp_error("refusing inconsistent update between symref '%s' (%s..%s) and" " its target '%s' (%s..%s)", - cmd->ref_name, cmd_oldh, cmd_newh, - dst_cmd->ref_name, dst_oldh, dst_newh); + cmd->ref_name, + find_unique_abbrev(cmd->old_sha1, DEFAULT_ABBREV), + find_unique_abbrev(cmd->new_sha1, DEFAULT_ABBREV), + dst_cmd->ref_name, + find_unique_abbrev(dst_cmd->old_sha1, DEFAULT_ABBREV), + find_unique_abbrev(dst_cmd->new_sha1, DEFAULT_ABBREV)); cmd->error_string = dst_cmd->error_string = "inconsistent aliased update"; @@ -1133,12 +1245,17 @@ static void set_connectivity_errors(struct command *commands, for (cmd = commands; cmd; cmd = cmd->next) { struct command *singleton = cmd; + struct check_connected_options opt = CHECK_CONNECTED_INIT; + if (shallow_update && si->shallow_ref[cmd->index]) /* to be checked in update_shallow_ref() */ continue; - if (!check_everything_connected(command_singleton_iterator, - 0, &singleton)) + + opt.env = tmp_objdir_env(tmp_objdir); + if (!check_connected(command_singleton_iterator, &singleton, + &opt)) continue; + cmd->error_string = "missing necessary objects"; } } @@ -1170,16 +1287,29 @@ static int iterate_receive_command_list(void *cb_data, unsigned char sha1[20]) static void reject_updates_to_hidden(struct command *commands) { + struct strbuf refname_full = STRBUF_INIT; + size_t prefix_len; struct command *cmd; + strbuf_addstr(&refname_full, get_git_namespace()); + prefix_len = refname_full.len; + for (cmd = commands; cmd; cmd = cmd->next) { - if (cmd->error_string || !ref_is_hidden(cmd->ref_name)) + if (cmd->error_string) + continue; + + strbuf_setlen(&refname_full, prefix_len); + strbuf_addstr(&refname_full, cmd->ref_name); + + if (!ref_is_hidden(cmd->ref_name, refname_full.buf)) continue; if (is_null_sha1(cmd->new_sha1)) cmd->error_string = "deny deleting a hidden ref"; else cmd->error_string = "deny updating a hidden ref"; } + + strbuf_release(&refname_full); } static int should_process_cmd(struct command *cmd) @@ -1279,11 +1409,15 @@ cleanup: static void execute_commands(struct command *commands, const char *unpacker_error, - struct shallow_info *si) + struct shallow_info *si, + const struct string_list *push_options) { + struct check_connected_options opt = CHECK_CONNECTED_INIT; struct command *cmd; unsigned char sha1[20]; struct iterate_data data; + struct async muxer; + int err_fd = 0; if (unpacker_error) { for (cmd = commands; cmd; cmd = cmd->next) @@ -1291,14 +1425,29 @@ static void execute_commands(struct command *commands, return; } + if (use_sideband) { + memset(&muxer, 0, sizeof(muxer)); + muxer.proc = copy_to_sideband; + muxer.in = -1; + if (!start_async(&muxer)) + err_fd = muxer.in; + /* ...else, continue without relaying sideband */ + } + data.cmds = commands; data.si = si; - if (check_everything_connected(iterate_receive_command_list, 0, &data)) + opt.err_fd = err_fd; + opt.progress = err_fd && !quiet; + opt.env = tmp_objdir_env(tmp_objdir); + if (check_connected(iterate_receive_command_list, &data, &opt)) set_connectivity_errors(commands, si); + if (use_sideband) + finish_async(&muxer); + reject_updates_to_hidden(commands); - if (run_receive_hook(commands, "pre-receive", 0)) { + if (run_receive_hook(commands, "pre-receive", 0, push_options)) { for (cmd = commands; cmd; cmd = cmd->next) { if (!cmd->error_string) cmd->error_string = "pre-receive hook declined"; @@ -1306,6 +1455,19 @@ static void execute_commands(struct command *commands, return; } + /* + * Now we'll start writing out refs, which means the objects need + * to be in their final positions so that other processes can see them. + */ + if (tmp_objdir_migrate(tmp_objdir) < 0) { + for (cmd = commands; cmd; cmd = cmd->next) { + if (!cmd->error_string) + cmd->error_string = "unable to migrate objects to permanent storage"; + } + return; + } + tmp_objdir = NULL; + check_aliased_updates(commands); free(head_name_to_free); @@ -1338,11 +1500,9 @@ static struct command **queue_command(struct command **tail, refname = line + 82; reflen = linelen - 82; - cmd = xcalloc(1, sizeof(struct command) + reflen + 1); + FLEX_ALLOC_MEM(cmd, ref_name, refname, reflen); hashcpy(cmd->old_sha1, old_sha1); hashcpy(cmd->new_sha1, new_sha1); - memcpy(cmd->ref_name, refname, reflen); - cmd->ref_name[reflen] = '\0'; *tail = cmd; return &cmd->next; } @@ -1402,6 +1562,9 @@ static struct command *read_head_info(struct sha1_array *shallow) if (advertise_atomic_push && parse_feature_request(feature_list, "atomic")) use_atomic = 1; + if (advertise_push_options + && parse_feature_request(feature_list, "push-options")) + use_push_options = 1; } if (!strcmp(line, "push-cert")) { @@ -1434,6 +1597,21 @@ static struct command *read_head_info(struct sha1_array *shallow) return commands; } +static void read_push_options(struct string_list *options) +{ + while (1) { + char *line; + int len; + + line = packet_read_line(0, &len); + + if (!line) + break; + + string_list_append(options, line); + } +} + static const char *parse_pack_header(struct pack_header *hdr) { switch (read_pack_header(0, hdr)) { @@ -1485,12 +1663,28 @@ static const char *unpack(int err_fd, struct shallow_info *si) argv_array_push(&child.args, alt_shallow_file); } + tmp_objdir = tmp_objdir_create(); + if (!tmp_objdir) + return "unable to create temporary object directory"; + child.env = tmp_objdir_env(tmp_objdir); + + /* + * Normally we just pass the tmp_objdir environment to the child + * processes that do the heavy lifting, but we may need to see these + * objects ourselves to set up shallow information. + */ + tmp_objdir_add_as_alternate(tmp_objdir); + if (ntohl(hdr.hdr_entries) < unpack_limit) { argv_array_pushl(&child.args, "unpack-objects", hdr_arg, NULL); if (quiet) argv_array_push(&child.args, "-q"); if (fsck_objects) - argv_array_push(&child.args, "--strict"); + argv_array_pushf(&child.args, "--strict%s", + fsck_msg_types.buf); + if (max_input_size) + argv_array_pushf(&child.args, "--max-input-size=%"PRIuMAX, + (uintmax_t)max_input_size); child.no_stdout = 1; child.err = err_fd; child.git_cmd = 1; @@ -1498,19 +1692,30 @@ static const char *unpack(int err_fd, struct shallow_info *si) if (status) return "unpack-objects abnormal exit"; } else { - int s; - char keep_arg[256]; - - s = sprintf(keep_arg, "--keep=receive-pack %"PRIuMAX" on ", (uintmax_t) getpid()); - if (gethostname(keep_arg + s, sizeof(keep_arg) - s)) - strcpy(keep_arg + s, "localhost"); + char hostname[256]; argv_array_pushl(&child.args, "index-pack", - "--stdin", hdr_arg, keep_arg, NULL); + "--stdin", hdr_arg, NULL); + + if (gethostname(hostname, sizeof(hostname))) + xsnprintf(hostname, sizeof(hostname), "localhost"); + argv_array_pushf(&child.args, + "--keep=receive-pack %"PRIuMAX" on %s", + (uintmax_t)getpid(), + hostname); + + if (!quiet && err_fd) + argv_array_push(&child.args, "--show-resolving-progress"); + if (use_sideband) + argv_array_push(&child.args, "--report-end-of-input"); if (fsck_objects) - argv_array_push(&child.args, "--strict"); - if (fix_thin) + argv_array_pushf(&child.args, "--strict%s", + fsck_msg_types.buf); + if (!reject_thin) argv_array_push(&child.args, "--fix-thin"); + if (max_input_size) + argv_array_pushf(&child.args, "--max-input-size=%"PRIuMAX, + (uintmax_t)max_input_size); child.out = -1; child.err = err_fd; child.git_cmd = 1; @@ -1535,6 +1740,7 @@ static const char *unpack_with_sideband(struct shallow_info *si) if (!use_sideband) return unpack(0, si); + use_keepalive = KEEPALIVE_AFTER_NUL; memset(&muxer, 0, sizeof(muxer)); muxer.proc = copy_to_sideband; muxer.in = -1; @@ -1552,8 +1758,7 @@ static void prepare_shallow_update(struct command *commands, { int i, j, k, bitmap_size = (si->ref->nr + 31) / 32; - si->used_shallow = xmalloc(sizeof(*si->used_shallow) * - si->shallow->nr); + ALLOC_ARRAY(si->used_shallow, si->shallow->nr); assign_shallow_commits_to_refs(si, si->used_shallow, NULL); si->need_reachability_test = @@ -1573,7 +1778,7 @@ static void prepare_shallow_update(struct command *commands, continue; si->need_reachability_test[i]++; for (k = 0; k < 32; k++) - if (si->used_shallow[i][j] & (1 << k)) + if (si->used_shallow[i][j] & (1U << k)) si->shallow_ref[j * 32 + k]++; } @@ -1619,7 +1824,7 @@ static void update_shallow_info(struct command *commands, return; } - ref_status = xmalloc(sizeof(*ref_status) * ref->nr); + ALLOC_ARRAY(ref_status, ref->nr); assign_shallow_commits_to_refs(si, NULL, ref_status); for (cmd = commands; cmd; cmd = cmd->next) { if (is_null_sha1(cmd->new_sha1)) @@ -1669,45 +1874,29 @@ static int delete_only(struct command *commands) int cmd_receive_pack(int argc, const char **argv, const char *prefix) { int advertise_refs = 0; - int i; struct command *commands; struct sha1_array shallow = SHA1_ARRAY_INIT; struct sha1_array ref = SHA1_ARRAY_INIT; struct shallow_info si; - packet_trace_identity("receive-pack"); + struct option options[] = { + OPT__QUIET(&quiet, N_("quiet")), + OPT_HIDDEN_BOOL(0, "stateless-rpc", &stateless_rpc, NULL), + OPT_HIDDEN_BOOL(0, "advertise-refs", &advertise_refs, NULL), + OPT_HIDDEN_BOOL(0, "reject-thin-pack-for-testing", &reject_thin, NULL), + OPT_END() + }; - argv++; - for (i = 1; i < argc; i++) { - const char *arg = *argv++; + packet_trace_identity("receive-pack"); - if (*arg == '-') { - if (!strcmp(arg, "--quiet")) { - quiet = 1; - continue; - } + argc = parse_options(argc, argv, prefix, options, receive_pack_usage, 0); - if (!strcmp(arg, "--advertise-refs")) { - advertise_refs = 1; - continue; - } - if (!strcmp(arg, "--stateless-rpc")) { - stateless_rpc = 1; - continue; - } - if (!strcmp(arg, "--reject-thin-pack-for-testing")) { - fix_thin = 0; - continue; - } + if (argc > 1) + usage_msg_opt(_("Too many arguments."), receive_pack_usage, options); + if (argc == 0) + usage_msg_opt(_("You must specify a directory."), receive_pack_usage, options); - usage(receive_pack_usage); - } - if (service_dir) - usage(receive_pack_usage); - service_dir = arg; - } - if (!service_dir) - usage(receive_pack_usage); + service_dir = argv[0]; setup_path(); @@ -1731,6 +1920,10 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix) if ((commands = read_head_info(&shallow)) != NULL) { const char *unpack_status = NULL; + struct string_list push_options = STRING_LIST_INIT_DUP; + + if (use_push_options) + read_push_options(&push_options); prepare_shallow_info(&si, &shallow); if (!si.nr_ours && !si.nr_theirs) @@ -1739,19 +1932,36 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix) unpack_status = unpack_with_sideband(&si); update_shallow_info(commands, &si, &ref); } - execute_commands(commands, unpack_status, &si); + use_keepalive = KEEPALIVE_ALWAYS; + execute_commands(commands, unpack_status, &si, + &push_options); if (pack_lockfile) unlink_or_warn(pack_lockfile); if (report_status) report(commands, unpack_status); - run_receive_hook(commands, "post-receive", 1); + run_receive_hook(commands, "post-receive", 1, + &push_options); run_update_post_hook(commands); + if (push_options.nr) + string_list_clear(&push_options, 0); if (auto_gc) { const char *argv_gc_auto[] = { "gc", "--auto", "--quiet", NULL, }; - int opt = RUN_GIT_CMD | RUN_COMMAND_STDOUT_TO_STDERR; - run_command_v_opt(argv_gc_auto, opt); + struct child_process proc = CHILD_PROCESS_INIT; + + proc.no_stdin = 1; + proc.stdout_to_stderr = 1; + proc.err = use_sideband ? -1 : 0; + proc.git_cmd = 1; + proc.argv = argv_gc_auto; + + close_all_packs(); + if (!start_command(&proc)) { + if (use_sideband) + copy_to_sideband(proc.err, -1, NULL); + finish_command(&proc); + } } if (auto_update_server_info) update_server_info(0); diff --git a/builtin/reflog.c b/builtin/reflog.c index c2eb8ff840..7a7136e53e 100644 --- a/builtin/reflog.c +++ b/builtin/reflog.c @@ -13,6 +13,8 @@ static const char reflog_expire_usage[] = "git reflog expire [--expire=<time>] [--expire-unreachable=<time>] [--rewrite] [--updateref] [--stale-fix] [--dry-run | -n] [--verbose] [--all] <refs>..."; static const char reflog_delete_usage[] = "git reflog delete [--rewrite] [--updateref] [--dry-run | -n] [--verbose] <refs>..."; +static const char reflog_exists_usage[] = +"git reflog exists <ref>"; static unsigned long default_reflog_expire; static unsigned long default_reflog_expire_unreachable; @@ -82,8 +84,8 @@ static int tree_is_complete(const unsigned char *sha1) init_tree_desc(&desc, tree->buffer, tree->size); complete = 1; while (tree_entry(&desc, &entry)) { - if (!has_sha1_file(entry.sha1) || - (S_ISDIR(entry.mode) && !tree_is_complete(entry.sha1))) { + if (!has_sha1_file(entry.oid->hash) || + (S_ISDIR(entry.mode) && !tree_is_complete(entry.oid->hash))) { tree->object.flags |= INCOMPLETE; complete = 0; } @@ -124,7 +126,7 @@ static int commit_is_complete(struct commit *commit) struct commit_list *parent; c = (struct commit *)study.objects[--study.nr].item; - if (!c->object.parsed && !parse_object(c->object.sha1)) + if (!c->object.parsed && !parse_object(c->object.oid.hash)) c->object.flags |= INCOMPLETE; if (c->object.flags & INCOMPLETE) { @@ -150,7 +152,7 @@ static int commit_is_complete(struct commit *commit) for (i = 0; i < found.nr; i++) { struct commit *c = (struct commit *)found.objects[i].item; - if (!tree_is_complete(c->tree->object.sha1)) { + if (!tree_is_complete(c->tree->object.oid.hash)) { is_incomplete = 1; c->object.flags |= INCOMPLETE; } @@ -216,7 +218,6 @@ static int keep_entry(struct commit **it, unsigned char *sha1) */ static void mark_reachable(struct expire_reflog_policy_cb *cb) { - struct commit *commit; struct commit_list *pending; unsigned long expire_limit = cb->mark_limit; struct commit_list *leftover = NULL; @@ -226,11 +227,8 @@ static void mark_reachable(struct expire_reflog_policy_cb *cb) pending = cb->mark_list; while (pending) { - struct commit_list *entry = pending; struct commit_list *parent; - pending = entry->next; - commit = entry->item; - free(entry); + struct commit *commit = pop_commit(&pending); if (commit->object.flags & REACHABLE) continue; if (parse_commit(commit)) @@ -384,11 +382,9 @@ static int collect_reflog(const char *ref, const struct object_id *oid, int unus { struct collected_reflog *e; struct collect_reflog_cb *cb = cb_data; - size_t namelen = strlen(ref); - e = xmalloc(sizeof(*e) + namelen + 1); + FLEX_ALLOC_STR(e, reflog, ref); hashcpy(e->sha1, oid->hash); - memcpy(e->reflog, ref, namelen + 1); ALLOC_GROW(cb->e, cb->nr + 1, cb->alloc); cb->e[cb->nr++] = e; return 0; @@ -398,7 +394,6 @@ static struct reflog_expire_cfg { struct reflog_expire_cfg *next; unsigned long expire_total; unsigned long expire_unreachable; - size_t len; char pattern[FLEX_ARRAY]; } *reflog_expire_cfg, **reflog_expire_cfg_tail; @@ -410,13 +405,11 @@ static struct reflog_expire_cfg *find_cfg_ent(const char *pattern, size_t len) reflog_expire_cfg_tail = &reflog_expire_cfg; for (ent = reflog_expire_cfg; ent; ent = ent->next) - if (ent->len == len && - !memcmp(ent->pattern, pattern, len)) + if (!strncmp(ent->pattern, pattern, len) && + ent->pattern[len] == '\0') return ent; - ent = xcalloc(1, (sizeof(*ent) + len)); - memcpy(ent->pattern, pattern, len); - ent->len = len; + FLEX_ALLOC_MEM(ent, pattern, pattern, len); *reflog_expire_cfg_tail = ent; reflog_expire_cfg_tail = &(ent->next); return ent; @@ -427,7 +420,7 @@ static int parse_expire_cfg_value(const char *var, const char *value, unsigned l if (!value) return config_error_nonbool(var); if (parse_expiry_date(value, expire)) - return error(_("%s' for '%s' is not a valid timestamp"), + return error(_("'%s' for '%s' is not a valid timestamp"), value, var); return 0; } @@ -699,12 +692,38 @@ static int cmd_reflog_delete(int argc, const char **argv, const char *prefix) return status; } +static int cmd_reflog_exists(int argc, const char **argv, const char *prefix) +{ + int i, start = 0; + + for (i = 1; i < argc; i++) { + const char *arg = argv[i]; + if (!strcmp(arg, "--")) { + i++; + break; + } + else if (arg[0] == '-') + usage(reflog_exists_usage); + else + break; + } + + start = i; + + if (argc - start != 1) + usage(reflog_exists_usage); + + if (check_refname_format(argv[start], REFNAME_ALLOW_ONELEVEL)) + die("invalid ref format: %s", argv[start]); + return !reflog_exists(argv[start]); +} + /* * main "reflog" */ static const char reflog_usage[] = -"git reflog [ show | expire | delete ]"; +"git reflog [ show | expire | delete | exists ]"; int cmd_reflog(int argc, const char **argv, const char *prefix) { @@ -724,5 +743,8 @@ 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); + if (!strcmp(argv[1], "exists")) + return cmd_reflog_exists(argc - 1, argv + 1, prefix); + return cmd_log_reflog(argc, argv, prefix); } diff --git a/builtin/remote-ext.c b/builtin/remote-ext.c index 3b8c22cc75..11b48bfb41 100644 --- a/builtin/remote-ext.c +++ b/builtin/remote-ext.c @@ -1,6 +1,7 @@ #include "builtin.h" #include "transport.h" #include "run-command.h" +#include "pkt-line.h" /* * URL syntax: @@ -113,65 +114,24 @@ static char *strip_escapes(const char *str, const char *service, } } -/* Should be enough... */ -#define MAXARGUMENTS 256 - -static const char **parse_argv(const char *arg, const char *service) +static void parse_argv(struct argv_array *out, 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); + char *expanded = strip_escapes(arg, service, &arg); if (expanded) - temparray[arguments++] = expanded; + argv_array_push(out, expanded); + free(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; + if (!vhost) + packet_write_fmt(stdin_fd, "%s %s%c", serv, repo, 0); 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); + packet_write_fmt(stdin_fd, "%s %s%chost=%s%c", serv, repo, 0, + vhost, 0); } static int run_child(const char *arg, const char *service) @@ -182,7 +142,7 @@ static int run_child(const char *arg, const char *service) child.in = -1; child.out = -1; child.err = 0; - child.argv = parse_argv(arg, service); + parse_argv(&child.args, arg, service); if (start_command(&child) < 0) die("Can't run specified command"); @@ -208,7 +168,7 @@ static int command_loop(const char *child) size_t i; if (!fgets(buffer, MAXCOMMAND - 1, stdin)) { if (ferror(stdin)) - die("Comammand input error"); + die("Command input error"); exit(0); } /* Strip end of line characters. */ diff --git a/builtin/remote.c b/builtin/remote.c index f4a6ec9f13..e52cf3925b 100644 --- a/builtin/remote.c +++ b/builtin/remote.c @@ -18,6 +18,7 @@ static const char * const builtin_remote_usage[] = { N_("git remote prune [-n | --dry-run] <name>"), N_("git remote [-v | --verbose] update [-p | --prune] [(<group> | <remote>)...]"), N_("git remote set-branches [--add] <name> <branch>..."), + N_("git remote get-url [--push] [--all] <name>"), N_("git remote set-url [--push] <name> <newurl> [<oldurl>]"), N_("git remote set-url --add <name> <newurl>"), N_("git remote set-url --delete <name> <url>"), @@ -65,6 +66,11 @@ static const char * const builtin_remote_update_usage[] = { NULL }; +static const char * const builtin_remote_geturl_usage[] = { + N_("git remote get-url [--push] [--all] <name>"), + NULL +}; + static const char * const builtin_remote_seturl_usage[] = { N_("git remote set-url [--push] <name> <newurl> [<oldurl>]"), N_("git remote set-url --add <name> <newurl>"), @@ -102,8 +108,8 @@ enum { #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) +static void add_branch(const char *key, const char *branchname, + const char *remotename, int mirror, struct strbuf *tmp) { strbuf_reset(tmp); strbuf_addch(tmp, '+'); @@ -113,7 +119,7 @@ static int add_branch(const char *key, const char *branchname, else strbuf_addf(tmp, "refs/heads/%s:refs/remotes/%s/%s", branchname, remotename, branchname); - return git_config_set_multivar(key, tmp->buf, "^$", 0); + git_config_set_multivar(key, tmp->buf, "^$", 0); } static const char mirror_advice[] = @@ -180,10 +186,7 @@ static int add(int argc, const char **argv) url = argv[1]; remote = remote_get(name); - if (remote && (remote->url_nr > 1 || - (strcmp(name, remote->url[0]) && - strcmp(url, remote->url[0])) || - remote->fetch_refspec_nr)) + if (remote_is_configured(remote)) die(_("remote %s already exists."), name); strbuf_addf(&buf2, "refs/heads/test:refs/remotes/%s/test", name); @@ -191,8 +194,7 @@ static int add(int argc, const char **argv) die(_("'%s' is not a valid remote name"), name); strbuf_addf(&buf, "remote.%s.url", name); - if (git_config_set(buf.buf, url)) - return 1; + git_config_set(buf.buf, url); if (!mirror || mirror & MIRROR_FETCH) { strbuf_reset(&buf); @@ -200,25 +202,22 @@ static int add(int argc, const char **argv) 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; + add_branch(buf.buf, track.items[i].string, + name, mirror, &buf2); } } if (mirror & MIRROR_PUSH) { strbuf_reset(&buf); strbuf_addf(&buf, "remote.%s.mirror", name); - if (git_config_set(buf.buf, "true")) - return 1; + git_config_set(buf.buf, "true"); } if (fetch_tags != TAGS_DEFAULT) { strbuf_reset(&buf); strbuf_addf(&buf, "remote.%s.tagopt", name); - if (git_config_set(buf.buf, - fetch_tags == TAGS_SET ? "--tags" : "--no-tags")) - return 1; + git_config_set(buf.buf, + fetch_tags == TAGS_SET ? "--tags" : "--no-tags"); } if (fetch && fetch_remote(name)) @@ -245,10 +244,10 @@ static int add(int argc, const char **argv) struct branch_info { char *remote_name; struct string_list merge; - int rebase; + enum { NO_REBASE, NORMAL_REBASE, INTERACTIVE_REBASE } rebase; }; -static struct string_list branch_list; +static struct string_list branch_list = STRING_LIST_INIT_NODUP; static const char *abbrev_ref(const char *name, const char *prefix) { @@ -305,7 +304,9 @@ static int config_read_branches(const char *key, const char *value, void *cb) if (v >= 0) info->rebase = v; else if (!strcmp(value, "preserve")) - info->rebase = 1; + info->rebase = NORMAL_REBASE; + else if (!strcmp(value, "interactive")) + info->rebase = INTERACTIVE_REBASE; } } return 0; @@ -395,7 +396,7 @@ static int get_push_ref_states(const struct ref *remote_refs, if (!ref->peer_ref) continue; - hashcpy(ref->new_sha1, ref->peer_ref->new_sha1); + oidcpy(&ref->new_oid, &ref->peer_ref->new_oid); item = string_list_append(&states->push, abbrev_branch(ref->peer_ref->name)); @@ -404,14 +405,14 @@ static int get_push_ref_states(const struct ref *remote_refs, info->forced = ref->force; info->dest = xstrdup(abbrev_branch(ref->name)); - if (is_null_sha1(ref->new_sha1)) { + if (is_null_oid(&ref->new_oid)) { info->status = PUSH_STATUS_DELETE; - } else if (!hashcmp(ref->old_sha1, ref->new_sha1)) + } else if (!oidcmp(&ref->old_oid, &ref->new_oid)) info->status = PUSH_STATUS_UPTODATE; - else if (is_null_sha1(ref->old_sha1)) + else if (is_null_oid(&ref->old_oid)) info->status = PUSH_STATUS_CREATE; - else if (has_sha1_file(ref->old_sha1) && - ref_newer(ref->new_sha1, ref->old_sha1)) + else if (has_object_file(&ref->old_oid) && + ref_newer(&ref->new_oid, &ref->old_oid)) info->status = PUSH_STATUS_FASTFORWARD; else info->status = PUSH_STATUS_OUTOFDATE; @@ -538,10 +539,6 @@ static int add_branch_for_removal(const char *refname, return 0; } - /* make sure that symrefs are deleted */ - if (flags & REF_ISSYMREF) - return unlink(git_path("%s", refname)); - string_list_append(branches->branches, refname); return 0; @@ -581,31 +578,23 @@ static int migrate_file(struct remote *remote) { struct strbuf buf = STRBUF_INIT; int i; - const char *path = NULL; strbuf_addf(&buf, "remote.%s.url", remote->name); for (i = 0; i < remote->url_nr; i++) - if (git_config_set_multivar(buf.buf, remote->url[i], "^$", 0)) - return error(_("Could not append '%s' to '%s'"), - remote->url[i], buf.buf); + git_config_set_multivar(buf.buf, remote->url[i], "^$", 0); strbuf_reset(&buf); strbuf_addf(&buf, "remote.%s.push", remote->name); for (i = 0; i < remote->push_refspec_nr; i++) - if (git_config_set_multivar(buf.buf, remote->push_refspec[i], "^$", 0)) - return error(_("Could not append '%s' to '%s'"), - remote->push_refspec[i], buf.buf); + git_config_set_multivar(buf.buf, remote->push_refspec[i], "^$", 0); strbuf_reset(&buf); strbuf_addf(&buf, "remote.%s.fetch", remote->name); for (i = 0; i < remote->fetch_refspec_nr; i++) - if (git_config_set_multivar(buf.buf, remote->fetch_refspec[i], "^$", 0)) - return error(_("Could not append '%s' to '%s'"), - remote->fetch_refspec[i], buf.buf); + git_config_set_multivar(buf.buf, remote->fetch_refspec[i], "^$", 0); if (remote->origin == REMOTE_REMOTES) - path = git_path("remotes/%s", remote->name); + unlink_or_warn(git_path("remotes/%s", remote->name)); else if (remote->origin == REMOTE_BRANCHES) - path = git_path("branches/%s", remote->name); - if (path) - unlink_or_warn(path); + unlink_or_warn(git_path("branches/%s", remote->name)); + return 0; } @@ -629,14 +618,14 @@ static int mv(int argc, const char **argv) rename.remote_branches = &remote_branches; oldremote = remote_get(rename.old); - if (!oldremote) + if (!remote_is_configured(oldremote)) die(_("No such remote: %s"), rename.old); if (!strcmp(rename.old, rename.new) && oldremote->origin != REMOTE_CONFIG) return migrate_file(oldremote); newremote = remote_get(rename.new); - if (newremote && (newremote->url_nr > 1 || newremote->fetch_refspec_nr)) + if (remote_is_configured(newremote)) die(_("remote %s already exists."), rename.new); strbuf_addf(&buf, "refs/heads/test:refs/remotes/%s/test", rename.new); @@ -652,8 +641,7 @@ static int mv(int argc, const char **argv) strbuf_reset(&buf); strbuf_addf(&buf, "remote.%s.fetch", rename.new); - if (git_config_set_multivar(buf.buf, NULL, NULL, 1)) - return error(_("Could not remove config section '%s'"), buf.buf); + git_config_set_multivar(buf.buf, NULL, NULL, 1); strbuf_addf(&old_remote_context, ":refs/remotes/%s/", rename.old); for (i = 0; i < oldremote->fetch_refspec_nr; i++) { char *ptr; @@ -673,8 +661,7 @@ static int mv(int argc, const char **argv) "\tPlease update the configuration manually if necessary."), buf2.buf); - if (git_config_set_multivar(buf.buf, buf2.buf, "^$", 0)) - return error(_("Could not append '%s'"), buf.buf); + git_config_set_multivar(buf.buf, buf2.buf, "^$", 0); } read_branches(); @@ -684,9 +671,7 @@ static int mv(int argc, const char **argv) if (info->remote_name && !strcmp(info->remote_name, rename.old)) { strbuf_reset(&buf); strbuf_addf(&buf, "branch.%s.remote", item->string); - if (git_config_set(buf.buf, rename.new)) { - return error(_("Could not set '%s'"), buf.buf); - } + git_config_set(buf.buf, rename.new); } } @@ -746,26 +731,6 @@ static int mv(int argc, const char **argv) return 0; } -static int remove_branches(struct string_list *branches) -{ - struct strbuf err = STRBUF_INIT; - int i, result = 0; - - if (repack_without_refs(branches, &err)) - result |= error("%s", err.buf); - strbuf_release(&err); - - for (i = 0; i < branches->nr; i++) { - struct string_list_item *item = branches->items + i; - const char *refname = item->string; - - if (delete_ref(refname, NULL, 0)) - result |= error(_("Could not remove branch %s"), refname); - } - - return result; -} - static int rm(int argc, const char **argv) { struct option options[] = { @@ -788,7 +753,7 @@ static int rm(int argc, const char **argv) usage_with_options(builtin_remote_rm_usage, options); remote = remote_get(argv[1]); - if (!remote) + if (!remote_is_configured(remote)) die(_("No such remote: %s"), argv[1]); known_remotes.to_delete = remote; @@ -804,10 +769,7 @@ static int rm(int argc, const char **argv) strbuf_reset(&buf); strbuf_addf(&buf, "branch.%s.%s", item->string, *k); - if (git_config_set(buf.buf, NULL)) { - strbuf_release(&buf); - return -1; - } + git_config_set(buf.buf, NULL); } } } @@ -822,7 +784,7 @@ static int rm(int argc, const char **argv) strbuf_release(&buf); if (!result) - result = remove_branches(&branches); + result = delete_refs(&branches, REF_NODEREF); string_list_clear(&branches, 0); if (skipped.nr) { @@ -986,7 +948,7 @@ static int show_local_info_item(struct string_list_item *item, void *cb_data) struct show_info *show_info = cb_data; struct branch_info *branch_info = item->util; struct string_list *merge = &branch_info->merge; - const char *also; + int width = show_info->width + 4; int i; if (branch_info->rebase && branch_info->merge.nr > 1) { @@ -997,17 +959,18 @@ static int show_local_info_item(struct string_list_item *item, void *cb_data) printf(" %-*s ", show_info->width, item->string); if (branch_info->rebase) { - printf_ln(_("rebases onto remote %s"), merge->items[0].string); + printf_ln(branch_info->rebase == INTERACTIVE_REBASE + ? _("rebases interactively onto remote %s") + : _("rebases onto remote %s"), merge->items[0].string); return 0; } else if (show_info->any_rebase) { printf_ln(_(" merges with remote %s"), merge->items[0].string); - also = _(" and with remote"); + width++; } else { printf_ln(_("merges with remote %s"), merge->items[0].string); - also = _(" and with remote"); } for (i = 1; i < merge->nr; i++) - printf(" %-*s %s %s\n", show_info->width, "", also, + printf(_("%-*s and with remote %s\n"), width, "", merge->items[i].string); return 0; @@ -1186,13 +1149,15 @@ static int show(int argc, const char **argv) url_nr = states.remote->url_nr; } for (i = 0; i < url_nr; i++) + /* TRANSLATORS: the colon ':' should align with + the one in " Fetch URL: %s" translation */ printf_ln(_(" Push URL: %s"), url[i]); if (!i) - printf_ln(_(" Push URL: %s"), "(no URL)"); + printf_ln(_(" Push URL: %s"), _("(no URL)")); if (no_query) - printf_ln(_(" HEAD branch: %s"), "(not queried)"); + printf_ln(_(" HEAD branch: %s"), _("(not queried)")); else if (!states.heads.nr) - printf_ln(_(" HEAD branch: %s"), "(unknown)"); + printf_ln(_(" HEAD branch: %s"), _("(unknown)")); else if (states.heads.nr == 1) printf_ln(_(" HEAD branch: %s"), states.heads.items[0].string); else { @@ -1232,8 +1197,7 @@ static int show(int argc, const char **argv) info.width = info.width2 = 0; for_each_string_list(&states.push, add_push_to_show_info, &info); - qsort(info.list->items, info.list->nr, - sizeof(*info.list->items), cmp_string_with_push); + QSORT(info.list->items, info.list->nr, cmp_string_with_push); if (info.list->nr) printf_ln(Q_(" Local ref configured for 'git push'%s:", " Local refs configured for 'git push'%s:", @@ -1334,19 +1298,12 @@ static int prune_remote(const char *remote, int dry_run) string_list_append(&refs_to_prune, item->util); string_list_sort(&refs_to_prune); - if (!dry_run) { - struct strbuf err = STRBUF_INIT; - if (repack_without_refs(&refs_to_prune, &err)) - result |= error("%s", err.buf); - strbuf_release(&err); - } + if (!dry_run) + result |= delete_refs(&refs_to_prune, 0); for_each_string_list_item(item, &states.stale) { const char *refname = item->util; - if (!dry_run) - result |= delete_ref(refname, NULL, 0); - if (dry_run) printf_ln(_(" * [would prune] %s"), abbrev_ref(refname, "refs/remotes/")); @@ -1433,24 +1390,20 @@ static int update(int argc, const char **argv) static int remove_all_fetch_refspecs(const char *remote, const char *key) { - return git_config_set_multivar(key, NULL, NULL, 1); + return git_config_set_multivar_gently(key, NULL, NULL, 1); } -static int add_branches(struct remote *remote, const char **branches, - const char *key) +static void add_branches(struct remote *remote, const char **branches, + const char *key) { const char *remotename = remote->name; int mirror = remote->mirror; struct strbuf refspec = STRBUF_INIT; for (; *branches; branches++) - if (add_branch(key, *branches, remotename, mirror, &refspec)) { - strbuf_release(&refspec); - return 1; - } + add_branch(key, *branches, remotename, mirror, &refspec); strbuf_release(&refspec); - return 0; } static int set_remote_branches(const char *remotename, const char **branches, @@ -1461,18 +1414,15 @@ static int set_remote_branches(const char *remotename, const char **branches, strbuf_addf(&key, "remote.%s.fetch", remotename); - if (!remote_is_configured(remotename)) - die(_("No such remote '%s'"), remotename); remote = remote_get(remotename); + if (!remote_is_configured(remote)) + die(_("No such remote '%s'"), remotename); if (!add_mode && remove_all_fetch_refspecs(remotename, key.buf)) { strbuf_release(&key); return 1; } - if (add_branches(remote, branches, key.buf)) { - strbuf_release(&key); - return 1; - } + add_branches(remote, branches, key.buf); strbuf_release(&key); return 0; @@ -1497,6 +1447,57 @@ static int set_branches(int argc, const char **argv) return set_remote_branches(argv[0], argv + 1, add_mode); } +static int get_url(int argc, const char **argv) +{ + int i, push_mode = 0, all_mode = 0; + const char *remotename = NULL; + struct remote *remote; + const char **url; + int url_nr; + struct option options[] = { + OPT_BOOL('\0', "push", &push_mode, + N_("query push URLs rather than fetch URLs")), + OPT_BOOL('\0', "all", &all_mode, + N_("return all URLs")), + OPT_END() + }; + argc = parse_options(argc, argv, NULL, options, builtin_remote_geturl_usage, 0); + + if (argc != 1) + usage_with_options(builtin_remote_geturl_usage, options); + + remotename = argv[0]; + + remote = remote_get(remotename); + if (!remote_is_configured(remote)) + die(_("No such remote '%s'"), remotename); + + url_nr = 0; + if (push_mode) { + url = remote->pushurl; + url_nr = remote->pushurl_nr; + } + /* else fetch mode */ + + /* Use the fetch URL when no push URLs were found or requested. */ + if (!url_nr) { + url = remote->url; + url_nr = remote->url_nr; + } + + if (!url_nr) + die(_("no URLs configured for remote '%s'"), remotename); + + if (all_mode) { + for (i = 0; i < url_nr; i++) + printf_ln("%s", url[i]); + } else { + printf_ln("%s", *url); + } + + return 0; +} + static int set_url(int argc, const char **argv) { int i, push_mode = 0, add_mode = 0, delete_mode = 0; @@ -1535,9 +1536,9 @@ static int set_url(int argc, const char **argv) if (delete_mode) oldurl = newurl; - if (!remote_is_configured(remotename)) - die(_("No such remote '%s'"), remotename); remote = remote_get(remotename); + if (!remote_is_configured(remote)) + die(_("No such remote '%s'"), remotename); if (push_mode) { strbuf_addf(&name_buf, "remote.%s.pushurl", remotename); @@ -1553,10 +1554,11 @@ static int set_url(int argc, const char **argv) if ((!oldurl && !delete_mode) || add_mode) { if (add_mode) git_config_set_multivar(name_buf.buf, newurl, - "^$", 0); + "^$", 0); else git_config_set(name_buf.buf, newurl); strbuf_release(&name_buf); + return 0; } @@ -1606,6 +1608,8 @@ int cmd_remote(int argc, const char **argv, const char *prefix) result = set_head(argc, argv); else if (!strcmp(argv[0], "set-branches")) result = set_branches(argc, argv); + else if (!strcmp(argv[0], "get-url")) + result = get_url(argc, argv); else if (!strcmp(argv[0], "set-url")) result = set_url(argc, argv); else if (!strcmp(argv[0], "show")) diff --git a/builtin/repack.c b/builtin/repack.c index af7340c7ba..677bc7c81a 100644 --- a/builtin/repack.c +++ b/builtin/repack.c @@ -18,6 +18,12 @@ static const char *const git_repack_usage[] = { NULL }; +static const char incremental_bitmap_conflict_error[] = N_( +"Incremental repacks are incompatible with bitmap indexes. Use\n" +"--no-write-bitmap-index or disable the pack.writebitmaps configuration." +); + + static int repack_config(const char *var, const char *value, void *cb) { if (!strcmp(var, "repack.usedeltabaseoffset")) { @@ -146,6 +152,7 @@ int cmd_repack(int argc, const char **argv, const char *prefix) int pack_everything = 0; int delete_redundant = 0; const char *unpack_unreachable = NULL; + int keep_unreachable = 0; const char *window = NULL, *window_memory = NULL; const char *depth = NULL; const char *max_pack_size = NULL; @@ -175,6 +182,8 @@ int cmd_repack(int argc, const char **argv, const char *prefix) N_("write bitmap index")), OPT_STRING(0, "unpack-unreachable", &unpack_unreachable, N_("approxidate"), N_("with -A, do not loosen objects older than this")), + OPT_BOOL('k', "keep-unreachable", &keep_unreachable, + N_("with -a, repack unreachable objects")), OPT_STRING(0, "window", &window, N_("n"), N_("size of the window used for delta compression")), OPT_STRING(0, "window-memory", &window_memory, N_("bytes"), @@ -193,9 +202,19 @@ int cmd_repack(int argc, const char **argv, const char *prefix) argc = parse_options(argc, argv, prefix, builtin_repack_options, git_repack_usage, 0); + if (delete_redundant && repository_format_precious_objects) + die(_("cannot delete packs in a precious-objects repo")); + + if (keep_unreachable && + (unpack_unreachable || (pack_everything & LOOSEN_UNREACHABLE))) + die(_("--keep-unreachable and -A are incompatible")); + if (pack_kept_objects < 0) pack_kept_objects = write_bitmaps; + if (write_bitmaps && !(pack_everything & ALL_INTO_ONE)) + die(_(incremental_bitmap_conflict_error)); + packdir = mkpathdup("%s/pack", get_object_directory()); packtmp = mkpathdup("%s/.tmp-%d-pack", packdir, (int)getpid()); @@ -236,6 +255,9 @@ int cmd_repack(int argc, const char **argv, const char *prefix) } else if (pack_everything & LOOSEN_UNREACHABLE) { argv_array_push(&cmd.args, "--unpack-unreachable"); + } else if (keep_unreachable) { + argv_array_push(&cmd.args, "--keep-unreachable"); + argv_array_push(&cmd.args, "--pack-loose-unreachable"); } else { argv_array_push(&cmd.env_array, "GIT_REF_PARANOIA=1"); } @@ -263,7 +285,7 @@ int cmd_repack(int argc, const char **argv, const char *prefix) return ret; out = xfdopen(cmd.out, "r"); - while (strbuf_getline(&line, out, '\n') != EOF) { + while (strbuf_getline_lf(&line, out) != EOF) { if (line.len != 40) die("repack: Expecting 40 character sha1 lines only from pack-objects."); string_list_append(&names, line.buf); @@ -285,8 +307,7 @@ int cmd_repack(int argc, const char **argv, const char *prefix) failed = 0; for_each_string_list_item(item, &names) { for (ext = 0; ext < ARRAY_SIZE(exts); ext++) { - const char *fname_old; - char *fname; + char *fname, *fname_old; fname = mkpathdup("%s/pack-%s%s", packdir, item->string, exts[ext].name); if (!file_exists(fname)) { @@ -294,7 +315,7 @@ int cmd_repack(int argc, const char **argv, const char *prefix) continue; } - fname_old = mkpath("%s/old-%s%s", packdir, + fname_old = mkpathdup("%s/old-%s%s", packdir, item->string, exts[ext].name); if (file_exists(fname_old)) if (unlink(fname_old)) @@ -302,10 +323,12 @@ int cmd_repack(int argc, const char **argv, const char *prefix) if (!failed && rename(fname, fname_old)) { free(fname); + free(fname_old); failed = 1; break; } else { string_list_append(&rollback, fname); + free(fname_old); } } if (failed) @@ -314,13 +337,13 @@ int cmd_repack(int argc, const char **argv, const char *prefix) if (failed) { struct string_list rollback_failure = STRING_LIST_INIT_DUP; for_each_string_list_item(item, &rollback) { - const char *fname_old; - char *fname; + char *fname, *fname_old; fname = mkpathdup("%s/%s", packdir, item->string); - fname_old = mkpath("%s/old-%s", packdir, item->string); + fname_old = mkpathdup("%s/old-%s", packdir, item->string); if (rename(fname_old, fname)) string_list_append(&rollback_failure, fname); free(fname); + free(fname_old); } if (rollback_failure.nr) { @@ -368,13 +391,14 @@ int cmd_repack(int argc, const char **argv, const char *prefix) /* Remove the "old-" files */ for_each_string_list_item(item, &names) { for (ext = 0; ext < ARRAY_SIZE(exts); ext++) { - const char *fname; - fname = mkpath("%s/old-%s%s", - packdir, - item->string, - exts[ext].name); + char *fname; + fname = mkpathdup("%s/old-%s%s", + packdir, + item->string, + exts[ext].name); if (remove_path(fname)) - warning(_("removing '%s' failed"), fname); + warning(_("failed to remove '%s'"), fname); + free(fname); } } diff --git a/builtin/replace.c b/builtin/replace.c index 0d52e7fa1d..b58c714cb8 100644 --- a/builtin/replace.c +++ b/builtin/replace.c @@ -104,9 +104,9 @@ static int for_each_replace_name(const char **argv, each_replace_name_fn fn) continue; } full_hex = sha1_to_hex(sha1); - snprintf(ref, sizeof(ref), "refs/replace/%s", full_hex); + snprintf(ref, sizeof(ref), "%s%s", git_replace_ref_base, full_hex); /* read_ref() may reuse the buffer */ - full_hex = ref + strlen("refs/replace/"); + full_hex = ref + strlen(git_replace_ref_base); if (read_ref(ref, sha1)) { error("replace ref '%s' not found.", full_hex); had_error = 1; @@ -134,7 +134,7 @@ static void check_ref_valid(unsigned char object[20], int force) { if (snprintf(ref, ref_size, - "refs/replace/%s", + "%s%s", git_replace_ref_base, sha1_to_hex(object)) > ref_size - 1) die("replace ref name too long: %.*s...", 50, ref); if (check_refname_format(ref, 0)) @@ -358,10 +358,10 @@ static void check_one_mergetag(struct commit *commit, /* iterate over new parents */ for (i = 1; i < mergetag_data->argc; i++) { - unsigned char sha1[20]; - if (get_sha1(mergetag_data->argv[i], sha1) < 0) + struct object_id oid; + if (get_sha1(mergetag_data->argv[i], oid.hash) < 0) die(_("Not a valid object name: '%s'"), mergetag_data->argv[i]); - if (!hashcmp(tag->tagged->sha1, sha1)) + if (!oidcmp(&tag->tagged->oid, &oid)) return; /* found */ } @@ -440,6 +440,7 @@ int cmd_replace(int argc, const char **argv, const char *prefix) }; check_replace_refs = 0; + git_config(git_default_config, NULL); argc = parse_options(argc, argv, prefix, options, git_replace_usage, 0); diff --git a/builtin/rerere.c b/builtin/rerere.c index 7afadd2ead..1bf72423bf 100644 --- a/builtin/rerere.c +++ b/builtin/rerere.c @@ -29,9 +29,10 @@ static int diff_two(const char *file1, const char *label1, xdemitconf_t xecfg; xdemitcb_t ecb; mmfile_t minus, plus; + int ret; if (read_mmfile(&minus, file1) || read_mmfile(&plus, file2)) - return 1; + return -1; printf("--- a/%s\n+++ b/%s\n", label1, label2); fflush(stdout); @@ -40,17 +41,17 @@ static int diff_two(const char *file1, const char *label1, memset(&xecfg, 0, sizeof(xecfg)); xecfg.ctxlen = 3; ecb.outf = outf; - xdi_diff(&minus, &plus, &xpp, &xecfg, &ecb); + ret = xdi_diff(&minus, &plus, &xpp, &xecfg, &ecb); free(minus.ptr); free(plus.ptr); - return 0; + return ret; } int cmd_rerere(int argc, const char **argv, const char *prefix) { struct string_list merge_rr = STRING_LIST_INIT_DUP; - int i, fd, autoupdate = -1, flags = 0; + int i, autoupdate = -1, flags = 0; struct option options[] = { OPT_SET_INT(0, "rerere-autoupdate", &autoupdate, @@ -79,18 +80,16 @@ int cmd_rerere(int argc, const char **argv, const char *prefix) return rerere_forget(&pathspec); } - fd = setup_rerere(&merge_rr, flags); - if (fd < 0) - return 0; - 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")) + else if (!strcmp(argv[0], "status")) { + if (setup_rerere(&merge_rr, flags | RERERE_READONLY) < 0) + return 0; for (i = 0; i < merge_rr.nr; i++) printf("%s\n", merge_rr.items[i].string); - else if (!strcmp(argv[0], "remaining")) { + } 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) @@ -100,13 +99,16 @@ int cmd_rerere(int argc, const char **argv, const char *prefix) * string_list_clear() */ merge_rr.items[i].util = NULL; } - } else if (!strcmp(argv[0], "diff")) + } else if (!strcmp(argv[0], "diff")) { + if (setup_rerere(&merge_rr, flags | RERERE_READONLY) < 0) + return 0; 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); + const struct rerere_id *id = merge_rr.items[i].util; + if (diff_two(rerere_path(id, "preimage"), path, path, path)) + die("unable to generate diff for %s", rerere_path(id, NULL)); } - else + } else usage_with_options(rerere_usage, options); string_list_clear(&merge_rr, 1); diff --git a/builtin/reset.c b/builtin/reset.c index 4c08ddc1ca..c04ac076dc 100644 --- a/builtin/reset.c +++ b/builtin/reset.c @@ -24,7 +24,7 @@ static const char * const git_reset_usage[] = { N_("git reset [--mixed | --soft | --hard | --merge | --keep] [-q] [<commit>]"), - N_("git reset [-q] <tree-ish> [--] <paths>..."), + N_("git reset [-q] [<tree-ish>] [--] <paths>..."), N_("git reset --patch [<tree-ish>] [--] [<paths>...]"), NULL }; @@ -36,10 +36,10 @@ static const char *reset_type_names[] = { static inline int is_merge(void) { - return !access(git_path("MERGE_HEAD"), F_OK); + return !access(git_path_merge_head(), F_OK); } -static int reset_index(const unsigned char *sha1, int reset_type, int quiet) +static int reset_index(const struct object_id *oid, int reset_type, int quiet) { int nr = 1; struct tree_desc desc[2]; @@ -69,22 +69,22 @@ static int reset_index(const unsigned char *sha1, int reset_type, int quiet) read_cache_unmerged(); if (reset_type == KEEP) { - unsigned char head_sha1[20]; - if (get_sha1("HEAD", head_sha1)) + struct object_id head_oid; + if (get_oid("HEAD", &head_oid)) return error(_("You do not have a valid HEAD.")); - if (!fill_tree_descriptor(desc, head_sha1)) + if (!fill_tree_descriptor(desc, head_oid.hash)) 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)); + if (!fill_tree_descriptor(desc + nr - 1, oid->hash)) + return error(_("Failed to find tree of %s."), oid_to_hex(oid)); if (unpack_trees(nr, desc, &opts)) return -1; if (reset_type == MIXED || reset_type == HARD) { - tree = parse_tree_indirect(sha1); + tree = parse_tree_indirect(oid->hash); prime_cache_tree(&the_index, tree); } @@ -96,14 +96,14 @@ static void print_new_head_line(struct commit *commit) const char *hex, *body; const char *msg; - hex = find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV); + hex = find_unique_abbrev(commit->object.oid.hash, DEFAULT_ABBREV); printf(_("HEAD is now at %s"), hex); msg = logmsg_reencode(commit, NULL, get_log_output_encoding()); body = strstr(msg, "\n\n"); if (body) { const char *eol; size_t len; - body += 2; + body = skip_blank_lines(body + 2); eol = strchr(body, '\n'); len = eol ? eol - body : strlen(body); printf(" %.*s\n", (int) len, body); @@ -121,7 +121,7 @@ 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; - int is_missing = !(one->mode && !is_null_sha1(one->sha1)); + int is_missing = !(one->mode && !is_null_oid(&one->oid)); struct cache_entry *ce; if (is_missing && !intent_to_add) { @@ -129,7 +129,7 @@ static void update_index_from_diff(struct diff_queue_struct *q, continue; } - ce = make_cache_entry(one->mode, one->sha1, one->path, + ce = make_cache_entry(one->mode, one->oid.hash, one->path, 0, 0); if (!ce) die(_("make_cache_entry failed for path '%s'"), @@ -143,7 +143,7 @@ static void update_index_from_diff(struct diff_queue_struct *q, } static int read_from_tree(const struct pathspec *pathspec, - unsigned char *tree_sha1, + struct object_id *tree_oid, int intent_to_add) { struct diff_options opt; @@ -154,11 +154,11 @@ static int read_from_tree(const struct pathspec *pathspec, opt.format_callback = update_index_from_diff; opt.format_callback_data = &intent_to_add; - if (do_diff_cache(tree_sha1, &opt)) + if (do_diff_cache(tree_oid->hash, &opt)) return 1; diffcore_std(&opt); diff_flush(&opt); - free_pathspec(&opt.pathspec); + clear_pathspec(&opt.pathspec); return 0; } @@ -191,7 +191,7 @@ static void parse_args(struct pathspec *pathspec, const char **rev_ret) { const char *rev = "HEAD"; - unsigned char unused[20]; + struct object_id unused; /* * Possible arguments are: * @@ -216,8 +216,8 @@ static void parse_args(struct pathspec *pathspec, * has to be unambiguous. If there is a single argument, it * can not be a tree */ - else if ((!argv[1] && !get_sha1_committish(argv[0], unused)) || - (argv[1] && !get_sha1_treeish(argv[0], unused))) { + else if ((!argv[1] && !get_sha1_committish(argv[0], unused.hash)) || + (argv[1] && !get_sha1_treeish(argv[0], unused.hash))) { /* * Ok, argv[0] looks like a commit/tree; it should not * be a filename. @@ -241,24 +241,24 @@ static void parse_args(struct pathspec *pathspec, prefix, argv); } -static int reset_refs(const char *rev, const unsigned char *sha1) +static int reset_refs(const char *rev, const struct object_id *oid) { int update_ref_status; struct strbuf msg = STRBUF_INIT; - unsigned char *orig = NULL, sha1_orig[20], - *old_orig = NULL, sha1_old_orig[20]; + struct object_id *orig = NULL, oid_orig, + *old_orig = NULL, oid_old_orig; - if (!get_sha1("ORIG_HEAD", sha1_old_orig)) - old_orig = sha1_old_orig; - if (!get_sha1("HEAD", sha1_orig)) { - orig = sha1_orig; + if (!get_oid("ORIG_HEAD", &oid_old_orig)) + old_orig = &oid_old_orig; + if (!get_oid("HEAD", &oid_orig)) { + orig = &oid_orig; set_reflog_message(&msg, "updating ORIG_HEAD", NULL); - update_ref(msg.buf, "ORIG_HEAD", orig, old_orig, 0, + update_ref_oid(msg.buf, "ORIG_HEAD", orig, old_orig, 0, UPDATE_REFS_MSG_ON_ERR); } else if (old_orig) - delete_ref("ORIG_HEAD", old_orig, 0); + delete_ref("ORIG_HEAD", old_orig->hash, 0); set_reflog_message(&msg, "updating HEAD", rev); - update_ref_status = update_ref(msg.buf, "HEAD", sha1, orig, 0, + update_ref_status = update_ref_oid(msg.buf, "HEAD", oid, orig, 0, UPDATE_REFS_MSG_ON_ERR); strbuf_release(&msg); return update_ref_status; @@ -269,7 +269,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix) int reset_type = NONE, update_ref_status = 0, quiet = 0; int patch_mode = 0, unborn; const char *rev; - unsigned char sha1[20]; + struct object_id oid; struct pathspec pathspec; int intent_to_add = 0; const struct option options[] = { @@ -295,26 +295,26 @@ int cmd_reset(int argc, const char **argv, const char *prefix) PARSE_OPT_KEEP_DASHDASH); parse_args(&pathspec, argv, prefix, patch_mode, &rev); - unborn = !strcmp(rev, "HEAD") && get_sha1("HEAD", sha1); + unborn = !strcmp(rev, "HEAD") && get_sha1("HEAD", oid.hash); if (unborn) { /* reset on unborn branch: treat as reset to empty tree */ - hashcpy(sha1, EMPTY_TREE_SHA1_BIN); + hashcpy(oid.hash, EMPTY_TREE_SHA1_BIN); } else if (!pathspec.nr) { struct commit *commit; - if (get_sha1_committish(rev, sha1)) + if (get_sha1_committish(rev, oid.hash)) die(_("Failed to resolve '%s' as a valid revision."), rev); - commit = lookup_commit_reference(sha1); + commit = lookup_commit_reference(oid.hash); if (!commit) die(_("Could not parse object '%s'."), rev); - hashcpy(sha1, commit->object.sha1); + oidcpy(&oid, &commit->object.oid); } else { struct tree *tree; - if (get_sha1_treeish(rev, sha1)) + if (get_sha1_treeish(rev, oid.hash)) die(_("Failed to resolve '%s' as a valid tree."), rev); - tree = parse_tree_indirect(sha1); + tree = parse_tree_indirect(oid.hash); if (!tree) die(_("Could not parse object '%s'."), rev); - hashcpy(sha1, tree->object.sha1); + oidcpy(&oid, &tree->object.oid); } if (patch_mode) { @@ -357,15 +357,15 @@ int cmd_reset(int argc, const char **argv, const char *prefix) hold_locked_index(lock, 1); if (reset_type == MIXED) { int flags = quiet ? REFRESH_QUIET : REFRESH_IN_PORCELAIN; - if (read_from_tree(&pathspec, sha1, intent_to_add)) + if (read_from_tree(&pathspec, &oid, intent_to_add)) return 1; if (get_git_work_tree()) refresh_index(&the_index, flags, NULL, NULL, _("Unstaged changes after reset:")); } else { - int err = reset_index(sha1, reset_type, quiet); + int err = reset_index(&oid, reset_type, quiet); if (reset_type == KEEP && !err) - err = reset_index(sha1, MIXED, quiet); + err = reset_index(&oid, MIXED, quiet); if (err) die(_("Could not reset index file to revision '%s'."), rev); } @@ -377,10 +377,10 @@ int cmd_reset(int argc, const char **argv, const char *prefix) if (!pathspec.nr && !unborn) { /* Any resets without paths update HEAD to the head being * switched to, saving the previous head in ORIG_HEAD before. */ - update_ref_status = reset_refs(rev, sha1); + update_ref_status = reset_refs(rev, &oid); if (reset_type == HARD && !update_ref_status && !quiet) - print_new_head_line(lookup_commit_reference(sha1)); + print_new_head_line(lookup_commit_reference(oid.hash)); } if (!pathspec.nr) remove_branch_state(); diff --git a/builtin/rev-list.c b/builtin/rev-list.c index ff84a825ff..c43decda70 100644 --- a/builtin/rev-list.c +++ b/builtin/rev-list.c @@ -9,6 +9,7 @@ #include "log-tree.h" #include "graph.h" #include "bisect.h" +#include "progress.h" static const char rev_list_usage[] = "git rev-list [OPTION] <commit-id>... [ -- paths... ]\n" @@ -42,18 +43,24 @@ static const char rev_list_usage[] = " --abbrev=<n> | --no-abbrev\n" " --abbrev-commit\n" " --left-right\n" +" --count\n" " special purpose:\n" " --bisect\n" " --bisect-vars\n" " --bisect-all" ; +static struct progress *progress; +static unsigned progress_counter; + static void finish_commit(struct commit *commit, void *data); static void show_commit(struct commit *commit, void *data) { struct rev_list_info *info = data; struct rev_info *revs = info->revs; + display_progress(progress, ++progress_counter); + if (info->flags & REV_LIST_QUIET) { finish_commit(commit, data); return; @@ -80,14 +87,14 @@ static void show_commit(struct commit *commit, void *data) 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), + fputs(find_unique_abbrev(commit->object.oid.hash, revs->abbrev), stdout); else - fputs(sha1_to_hex(commit->object.sha1), stdout); + fputs(oid_to_hex(&commit->object.oid), stdout); if (revs->print_parents) { struct commit_list *parents = commit->parents; while (parents) { - printf(" %s", sha1_to_hex(parents->item->object.sha1)); + printf(" %s", oid_to_hex(&parents->item->object.oid)); parents = parents->next; } } @@ -96,7 +103,7 @@ static void show_commit(struct commit *commit, void *data) children = lookup_decoration(&revs->children, &commit->object); while (children) { - printf(" %s", sha1_to_hex(children->item->object.sha1)); + printf(" %s", oid_to_hex(&children->item->object.oid)); children = children->next; } } @@ -115,48 +122,40 @@ static void show_commit(struct commit *commit, void *data) ctx.fmt = revs->commit_format; ctx.output_encoding = get_log_output_encoding(); pretty_print_commit(&ctx, commit, &buf); - if (revs->graph) { - if (buf.len) { - if (revs->commit_format != CMIT_FMT_ONELINE) - graph_show_oneline(revs->graph); - - graph_show_commit_msg(revs->graph, &buf); - - /* - * Add a newline after the commit message. - * - * Usually, this newline produces a blank - * padding line between entries, in which case - * we need to add graph padding on this line. - * - * However, the commit message may not end in a - * newline. In this case the newline simply - * ends the last line of the commit message, - * and we don't need any graph output. (This - * always happens with CMIT_FMT_ONELINE, and it - * happens with CMIT_FMT_USERFORMAT when the - * format doesn't explicitly end in a newline.) - */ - if (buf.len && buf.buf[buf.len - 1] == '\n') - graph_show_padding(revs->graph); - putchar('\n'); - } else { - /* - * If the message buffer is empty, just show - * the rest of the graph output for this - * commit. - */ - if (graph_show_remainder(revs->graph)) - putchar('\n'); - if (revs->commit_format == CMIT_FMT_ONELINE) - putchar('\n'); - } + if (buf.len) { + if (revs->commit_format != CMIT_FMT_ONELINE) + graph_show_oneline(revs->graph); + + graph_show_commit_msg(revs->graph, stdout, &buf); + + /* + * Add a newline after the commit message. + * + * Usually, this newline produces a blank + * padding line between entries, in which case + * we need to add graph padding on this line. + * + * However, the commit message may not end in a + * newline. In this case the newline simply + * ends the last line of the commit message, + * and we don't need any graph output. (This + * always happens with CMIT_FMT_ONELINE, and it + * happens with CMIT_FMT_USERFORMAT when the + * format doesn't explicitly end in a newline.) + */ + if (buf.len && buf.buf[buf.len - 1] == '\n') + graph_show_padding(revs->graph); + putchar(info->hdr_termination); } else { - if (revs->commit_format != CMIT_FMT_USERFORMAT || - buf.len) { - fwrite(buf.buf, 1, buf.len, stdout); - putchar(info->hdr_termination); - } + /* + * If the message buffer is empty, just show + * the rest of the graph output for this + * commit. + */ + if (graph_show_remainder(revs->graph)) + putchar('\n'); + if (revs->commit_format == CMIT_FMT_ONELINE) + putchar('\n'); } strbuf_release(&buf); } else { @@ -176,31 +175,28 @@ static void finish_commit(struct commit *commit, void *data) free_commit_buffer(commit); } -static void finish_object(struct object *obj, - const struct name_path *path, const char *name, - void *cb_data) +static void finish_object(struct object *obj, const char *name, void *cb_data) { struct rev_list_info *info = cb_data; - if (obj->type == OBJ_BLOB && !has_sha1_file(obj->sha1)) - die("missing blob object '%s'", sha1_to_hex(obj->sha1)); + if (obj->type == OBJ_BLOB && !has_object_file(&obj->oid)) + die("missing blob object '%s'", oid_to_hex(&obj->oid)); if (info->revs->verify_objects && !obj->parsed && obj->type != OBJ_COMMIT) - parse_object(obj->sha1); + parse_object(obj->oid.hash); } -static void show_object(struct object *obj, - const struct name_path *path, const char *component, - void *cb_data) +static void show_object(struct object *obj, const char *name, void *cb_data) { struct rev_list_info *info = cb_data; - finish_object(obj, path, component, cb_data); + finish_object(obj, name, cb_data); + display_progress(progress, ++progress_counter); if (info->flags & REV_LIST_QUIET) return; - show_object_with_name(stdout, obj, path, component); + show_object_with_name(stdout, obj, name); } static void show_edge(struct commit *commit) { - printf("-%s\n", sha1_to_hex(commit->object.sha1)); + printf("-%s\n", oid_to_hex(&commit->object.oid)); } static void print_var_str(const char *var, const char *val) @@ -216,7 +212,7 @@ static void print_var_int(const char *var, int val) static int show_bisect_vars(struct rev_list_info *info, int reaches, int all) { int cnt, flags = info->flags; - char hex[41] = ""; + char hex[GIT_SHA1_HEXSZ + 1] = ""; struct commit_list *tried; struct rev_info *revs = info->revs; @@ -241,7 +237,7 @@ static int show_bisect_vars(struct rev_list_info *info, int reaches, int all) cnt = reaches; if (revs->commits) - strcpy(hex, sha1_to_hex(revs->commits->item->object.sha1)); + sha1_to_hex_r(hex, revs->commits->item->object.oid.hash); if (flags & BISECT_SHOW_ALL) { traverse_commit_list(revs, show_commit, show_object, info); @@ -279,6 +275,7 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) int bisect_show_vars = 0; int bisect_find_all = 0; int use_bitmap_index = 0; + const char *show_progress = NULL; git_config(git_default_config, NULL); init_revisions(&revs, prefix); @@ -328,6 +325,10 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) test_bitmap_walk(&revs); return 0; } + if (skip_prefix(arg, "--progress=", &arg)) { + show_progress = arg; + continue; + } usage(rev_list_usage); } @@ -349,21 +350,31 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) revs.diff) usage(rev_list_usage); + if (revs.show_notes) + die(_("rev-list does not support display of notes")); + save_commit_buffer = (revs.verbose_header || revs.grep_filter.pattern_list || revs.grep_filter.header_list); if (bisect_list) revs.limited = 1; - if (use_bitmap_index) { + if (show_progress) + progress = start_progress_delay(show_progress, 0, 0, 2); + + if (use_bitmap_index && !revs.prune) { if (revs.count && !revs.left_right && !revs.cherry_mark) { uint32_t commit_count; + int max_count = revs.max_count; if (!prepare_bitmap_walk(&revs)) { count_bitmap_commit_list(&commit_count, NULL, NULL, NULL); + if (max_count >= 0 && max_count < commit_count) + commit_count = max_count; printf("%d\n", commit_count); return 0; } - } else if (revs.tag_objects && revs.tree_objects && revs.blob_objects) { + } else if (revs.max_count < 0 && + revs.tag_objects && revs.tree_objects && revs.blob_objects) { if (!prepare_bitmap_walk(&revs)) { traverse_bitmap_commit_list(&show_object_fast); return 0; @@ -388,6 +399,8 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) traverse_commit_list(&revs, show_commit, show_object, &info); + stop_progress(&progress); + if (revs.count) { if (revs.left_right && revs.cherry_mark) printf("%d\t%d\t%d\n", revs.count_left, revs.count_right, revs.count_same); diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c index b6232390a6..ff13e59e1d 100644 --- a/builtin/rev-parse.c +++ b/builtin/rev-parse.c @@ -281,11 +281,8 @@ static int try_difference(const char *arg) b = lookup_commit_reference(end); exclude = get_merge_bases(a, b); while (exclude) { - struct commit_list *n = exclude->next; - show_rev(REVERSED, - exclude->item->object.sha1,NULL); - free(exclude); - exclude = n; + struct commit *commit = pop_commit(&exclude); + show_rev(REVERSED, commit->object.oid.hash, NULL); } } *dotdot = '.'; @@ -301,14 +298,30 @@ static int try_parent_shorthands(const char *arg) unsigned char sha1[20]; struct commit *commit; struct commit_list *parents; - int parents_only; - - if ((dotdot = strstr(arg, "^!"))) - parents_only = 0; - else if ((dotdot = strstr(arg, "^@"))) - parents_only = 1; - - if (!dotdot || dotdot[2]) + int parent_number; + int include_rev = 0; + int include_parents = 0; + int exclude_parent = 0; + + if ((dotdot = strstr(arg, "^!"))) { + include_rev = 1; + if (dotdot[2]) + return 0; + } else if ((dotdot = strstr(arg, "^@"))) { + include_parents = 1; + if (dotdot[2]) + return 0; + } else if ((dotdot = strstr(arg, "^-"))) { + include_rev = 1; + exclude_parent = 1; + + if (dotdot[2]) { + char *end; + exclude_parent = strtoul(dotdot + 2, &end, 10); + if (*end != '\0' || !exclude_parent) + return 0; + } + } else return 0; *dotdot = 0; @@ -317,12 +330,29 @@ static int try_parent_shorthands(const char *arg) return 0; } - if (!parents_only) - show_rev(NORMAL, sha1, arg); commit = lookup_commit_reference(sha1); - for (parents = commit->parents; parents; parents = parents->next) - show_rev(parents_only ? NORMAL : REVERSED, - parents->item->object.sha1, arg); + if (exclude_parent && + exclude_parent > commit_list_count(commit->parents)) { + *dotdot = '^'; + return 0; + } + + if (include_rev) + show_rev(NORMAL, sha1, arg); + for (parents = commit->parents, parent_number = 1; + parents; + parents = parents->next, parent_number++) { + char *name = NULL; + + if (exclude_parent && parent_number != exclude_parent) + continue; + + if (symbolic) + name = xstrfmt("%s^%d", arg, parent_number); + show_rev(include_parents ? NORMAL : REVERSED, + parents->item->object.oid.hash, name); + free(name); + } *dotdot = '^'; return 1; @@ -371,6 +401,7 @@ static int cmd_parseopt(int argc, const char **argv, const char *prefix) N_("output in stuck long form")), OPT_END(), }; + static const char * const flag_chars = "*=?!"; struct strbuf sb = STRBUF_INIT, parsed = STRBUF_INIT; const char **usage = NULL; @@ -385,7 +416,7 @@ static int cmd_parseopt(int argc, const char **argv, const char *prefix) /* get the usage up to the first line with a -- on it */ for (;;) { - if (strbuf_getline(&sb, stdin, '\n') == EOF) + if (strbuf_getline(&sb, stdin) == EOF) die("premature end of input"); ALLOC_GROW(usage, unb + 1, usz); if (!strcmp("--", sb.buf)) { @@ -398,9 +429,9 @@ static int cmd_parseopt(int argc, const char **argv, const char *prefix) } /* parse: (<short>|<short>,<long>|<long>)[*=?!]*<arghint>? SP+ <help> */ - while (strbuf_getline(&sb, stdin, '\n') != EOF) { + while (strbuf_getline(&sb, stdin) != EOF) { const char *s; - const char *end; + const char *help; struct option *o; if (!sb.len) @@ -410,54 +441,56 @@ static int cmd_parseopt(int argc, const char **argv, const char *prefix) memset(opts + onb, 0, sizeof(opts[onb])); o = &opts[onb++]; - s = strchr(sb.buf, ' '); - if (!s || *sb.buf == ' ') { + help = strchr(sb.buf, ' '); + if (!help || *sb.buf == ' ') { o->type = OPTION_GROUP; o->help = xstrdup(skipspaces(sb.buf)); continue; } o->type = OPTION_CALLBACK; - o->help = xstrdup(skipspaces(s)); + o->help = xstrdup(skipspaces(help)); o->value = &parsed; o->flags = PARSE_OPT_NOARG; o->callback = &parseopt_dump; - /* Possible argument name hint */ - end = s; - while (s > sb.buf && strchr("*=?!", s[-1]) == NULL) - --s; - if (s != sb.buf && s != end) - o->argh = xmemdupz(s, end - s); - if (s == sb.buf) - s = end; - - while (s > sb.buf && strchr("*=?!", s[-1])) { - switch (*--s) { + /* name(s) */ + s = strpbrk(sb.buf, flag_chars); + if (s == NULL) + s = help; + + if (s - sb.buf == 1) /* short option only */ + o->short_name = *sb.buf; + else if (sb.buf[1] != ',') /* long option only */ + o->long_name = xmemdupz(sb.buf, s - sb.buf); + else { + o->short_name = *sb.buf; + o->long_name = xmemdupz(sb.buf + 2, s - sb.buf - 2); + } + + /* flags */ + while (s < help) { + switch (*s++) { case '=': o->flags &= ~PARSE_OPT_NOARG; - break; + continue; case '?': o->flags &= ~PARSE_OPT_NOARG; o->flags |= PARSE_OPT_OPTARG; - break; + continue; case '!': o->flags |= PARSE_OPT_NONEG; - break; + continue; case '*': o->flags |= PARSE_OPT_HIDDEN; - break; + continue; } + s--; + break; } - if (s - sb.buf == 1) /* short option only */ - o->short_name = *sb.buf; - else if (sb.buf[1] != ',') /* long option only */ - o->long_name = xmemdupz(sb.buf, s - sb.buf); - else { - o->short_name = *sb.buf; - o->long_name = xmemdupz(sb.buf + 2, s - sb.buf - 2); - } + if (s < help) + o->argh = xmemdupz(s, help - s); } strbuf_release(&sb); @@ -469,7 +502,7 @@ static int cmd_parseopt(int argc, const char **argv, const char *prefix) (stop_at_non_option ? PARSE_OPT_STOP_AT_NON_OPTION : 0) | PARSE_OPT_SHELL_EVAL); - strbuf_addf(&parsed, " --"); + strbuf_addstr(&parsed, " --"); sq_quote_argv(&parsed, argv, 0); puts(parsed.buf); return 0; @@ -505,6 +538,7 @@ N_("git rev-parse --parseopt [<options>] -- [<args>...]\n" int cmd_rev_parse(int argc, const char **argv, const char *prefix) { int i, as_is = 0, verify = 0, quiet = 0, revs_count = 0, type = 0; + int did_repo_setup = 0; int has_dashdash = 0; int output_prefix = 0; unsigned char sha1[20]; @@ -528,11 +562,40 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) } } - prefix = setup_git_directory(); - git_config(git_default_config, NULL); + /* No options; just report on whether we're in a git repo or not. */ + if (argc == 1) { + setup_git_directory(); + git_config(git_default_config, NULL); + return 0; + } + for (i = 1; i < argc; i++) { const char *arg = argv[i]; + if (!strcmp(arg, "--local-env-vars")) { + int i; + for (i = 0; local_repo_env[i]; i++) + printf("%s\n", local_repo_env[i]); + continue; + } + if (!strcmp(arg, "--resolve-git-dir")) { + const char *gitdir = argv[++i]; + if (!gitdir) + die("--resolve-git-dir requires an argument"); + gitdir = resolve_gitdir(gitdir); + if (!gitdir) + die("not a gitdir '%s'", argv[i]); + puts(gitdir); + continue; + } + + /* The rest of the options require a git repository. */ + if (!did_repo_setup) { + prefix = setup_git_directory(); + git_config(git_default_config, NULL); + did_repo_setup = 1; + } + if (!strcmp(arg, "--git-path")) { if (!argv[i + 1]) die("--git-path requires an argument"); @@ -613,8 +676,9 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) filter &= ~(DO_FLAGS|DO_NOREV); verify = 1; abbrev = DEFAULT_ABBREV; - if (arg[7] == '=') - abbrev = strtoul(arg + 8, NULL, 10); + if (!arg[7]) + continue; + abbrev = strtoul(arg + 8, NULL, 10); if (abbrev < MINIMUM_ABBREV) abbrev = MINIMUM_ABBREV; else if (40 <= abbrev) @@ -706,12 +770,6 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) add_ref_exclusion(&ref_excludes, arg + 10); continue; } - if (!strcmp(arg, "--local-env-vars")) { - int i; - for (i = 0; local_repo_env[i]; i++) - printf("%s\n", local_repo_env[i]); - continue; - } if (!strcmp(arg, "--show-toplevel")) { const char *work_tree = get_git_work_tree(); if (work_tree) @@ -763,17 +821,8 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) continue; } if (!strcmp(arg, "--git-common-dir")) { - puts(get_git_common_dir()); - continue; - } - if (!strcmp(arg, "--resolve-git-dir")) { - const char *gitdir = argv[++i]; - if (!gitdir) - die("--resolve-git-dir requires an argument"); - gitdir = resolve_gitdir(gitdir); - if (!gitdir) - die("not a gitdir '%s'", argv[i]); - puts(gitdir); + const char *pfx = prefix ? prefix : ""; + puts(prefix_filename(pfx, strlen(pfx), get_git_common_dir())); continue; } if (!strcmp(arg, "--is-inside-git-dir")) { diff --git a/builtin/revert.c b/builtin/revert.c index 56a2c36669..4ca5b51544 100644 --- a/builtin/revert.c +++ b/builtin/revert.c @@ -71,12 +71,12 @@ static void verify_opt_compatible(const char *me, const char *base_opt, ...) die(_("%s: %s cannot be used with %s"), me, this_opt, base_opt); } -static void parse_args(int argc, const char **argv, struct replay_opts *opts) +static int run_sequencer(int argc, const char **argv, struct replay_opts *opts) { const char * const * usage_str = revert_or_cherry_pick_usage(opts); const char *me = action_name(opts); int cmd = 0; - struct option options[] = { + struct option base_options[] = { OPT_CMDMODE(0, "quit", &cmd, N_("end revert or cherry-pick sequence"), 'q'), OPT_CMDMODE(0, "continue", &cmd, N_("resume revert or cherry-pick sequence"), 'c'), OPT_CMDMODE(0, "abort", &cmd, N_("cancel revert or cherry-pick sequence"), 'a'), @@ -91,13 +91,9 @@ static void parse_args(int argc, const char **argv, struct replay_opts *opts) N_("option for merge strategy"), option_parse_x), { OPTION_STRING, 'S', "gpg-sign", &opts->gpg_sign, N_("key-id"), N_("GPG sign commit"), PARSE_OPT_OPTARG, NULL, (intptr_t) "" }, - OPT_END(), - OPT_END(), - OPT_END(), - OPT_END(), - OPT_END(), - OPT_END(), + OPT_END() }; + struct option *options = base_options; if (opts->action == REPLAY_PICK) { struct option cp_extra[] = { @@ -108,8 +104,7 @@ static void parse_args(int argc, const char **argv, struct replay_opts *opts) OPT_BOOL(0, "keep-redundant-commits", &opts->keep_redundant_commits, N_("keep redundant, empty commits")), OPT_END(), }; - if (parse_options_concat(options, ARRAY_SIZE(options), cp_extra)) - die(_("program error")); + options = parse_options_concat(options, cp_extra); } argc = parse_options(argc, argv, NULL, options, usage_str, @@ -120,25 +115,15 @@ static void parse_args(int argc, const char **argv, struct replay_opts *opts) if (opts->keep_redundant_commits) opts->allow_empty = 1; - /* Set the subcommand */ - if (cmd == 'q') - opts->subcommand = REPLAY_REMOVE_STATE; - else if (cmd == 'c') - opts->subcommand = REPLAY_CONTINUE; - else if (cmd == 'a') - opts->subcommand = REPLAY_ROLLBACK; - else - opts->subcommand = REPLAY_NONE; - /* Check for incompatible command line arguments */ - if (opts->subcommand != REPLAY_NONE) { + if (cmd) { char *this_operation; - if (opts->subcommand == REPLAY_REMOVE_STATE) + if (cmd == 'q') this_operation = "--quit"; - else if (opts->subcommand == REPLAY_CONTINUE) + else if (cmd == 'c') this_operation = "--continue"; else { - assert(opts->subcommand == REPLAY_ROLLBACK); + assert(cmd == 'a'); this_operation = "--abort"; } @@ -161,7 +146,7 @@ static void parse_args(int argc, const char **argv, struct replay_opts *opts) "--edit", opts->edit, NULL); - if (opts->subcommand != REPLAY_NONE) { + if (cmd) { opts->revs = NULL; } else { struct setup_revision_opt s_r_opt; @@ -179,20 +164,30 @@ static void parse_args(int argc, const char **argv, struct replay_opts *opts) if (argc > 1) usage_with_options(usage_str, options); + + /* These option values will be free()d */ + opts->gpg_sign = xstrdup_or_null(opts->gpg_sign); + opts->strategy = xstrdup_or_null(opts->strategy); + + if (cmd == 'q') + return sequencer_remove_state(opts); + if (cmd == 'c') + return sequencer_continue(opts); + if (cmd == 'a') + return sequencer_rollback(opts); + return sequencer_pick_revisions(opts); } int cmd_revert(int argc, const char **argv, const char *prefix) { - struct replay_opts opts; + struct replay_opts opts = REPLAY_OPTS_INIT; int res; - memset(&opts, 0, sizeof(opts)); if (isatty(0)) opts.edit = 1; opts.action = REPLAY_REVERT; git_config(git_default_config, NULL); - parse_args(argc, argv, &opts); - res = sequencer_pick_revisions(&opts); + res = run_sequencer(argc, argv, &opts); if (res < 0) die(_("revert failed")); return res; @@ -200,14 +195,12 @@ int cmd_revert(int argc, const char **argv, const char *prefix) int cmd_cherry_pick(int argc, const char **argv, const char *prefix) { - struct replay_opts opts; + struct replay_opts opts = REPLAY_OPTS_INIT; int res; - memset(&opts, 0, sizeof(opts)); opts.action = REPLAY_PICK; git_config(git_default_config, NULL); - parse_args(argc, argv, &opts); - res = sequencer_pick_revisions(&opts); + res = run_sequencer(argc, argv, &opts); if (res < 0) die(_("cherry-pick failed")); return res; diff --git a/builtin/rm.c b/builtin/rm.c index 80b972f92f..3f3e24eb36 100644 --- a/builtin/rm.c +++ b/builtin/rm.c @@ -107,7 +107,7 @@ static int check_submodules_use_gitfiles(void) return errs; } -static int check_local_mod(unsigned char *head, int index_only) +static int check_local_mod(struct object_id *head, int index_only) { /* * Items in list are already sorted in the cache order, @@ -123,13 +123,13 @@ static int check_local_mod(unsigned char *head, int index_only) struct string_list files_submodule = STRING_LIST_INIT_NODUP; struct string_list files_local = STRING_LIST_INIT_NODUP; - no_head = is_null_sha1(head); + no_head = is_null_oid(head); for (i = 0; i < list.nr; i++) { struct stat st; int pos; const struct cache_entry *ce; const char *name = list.entry[i].name; - unsigned char sha1[20]; + struct object_id oid; unsigned mode; int local_changes = 0; int staged_changes = 0; @@ -152,7 +152,7 @@ static int check_local_mod(unsigned char *head, int index_only) if (lstat(ce->name, &st) < 0) { if (errno != ENOENT && errno != ENOTDIR) - warning("'%s': %s", ce->name, strerror(errno)); + warning_errno(_("failed to stat '%s'"), ce->name); /* It already vanished from the working tree */ continue; } @@ -197,9 +197,9 @@ static int check_local_mod(unsigned char *head, int index_only) * way as changed from the HEAD. */ if (no_head - || get_tree_entry(head, name, sha1, &mode) + || get_tree_entry(head->hash, name, oid.hash, &mode) || ce->ce_mode != create_ce_mode(mode) - || hashcmp(ce->sha1, sha1)) + || oidcmp(&ce->oid, &oid)) staged_changes = 1; /* @@ -211,7 +211,7 @@ static int check_local_mod(unsigned char *head, int index_only) * "intent to add" entry. */ if (local_changes && staged_changes) { - if (!index_only || !(ce->ce_flags & CE_INTENT_TO_ADD)) + if (!index_only || !ce_intent_to_add(ce)) string_list_append(&files_staged, name); } else if (!index_only) { @@ -314,7 +314,7 @@ int cmd_rm(int argc, const char **argv, const char *prefix) list.entry[list.nr].is_submodule = S_ISGITLINK(ce->ce_mode); if (list.entry[list.nr++].is_submodule && !is_staging_gitmodules_ok()) - die (_("Please, stage your changes to .gitmodules or stash them to proceed")); + die (_("Please stage your changes to .gitmodules or stash them to proceed")); } if (pathspec.nr) { @@ -351,10 +351,10 @@ int cmd_rm(int argc, const char **argv, const char *prefix) * report no changes unless forced. */ if (!force) { - unsigned char sha1[20]; - if (get_sha1("HEAD", sha1)) - hashclr(sha1); - if (check_local_mod(sha1, index_only)) + struct object_id oid; + if (get_oid("HEAD", &oid)) + oidclr(&oid); + if (check_local_mod(&oid, index_only)) exit(1); } else if (!index_only) { if (check_submodules_use_gitfiles()) @@ -387,6 +387,7 @@ int cmd_rm(int argc, const char **argv, const char *prefix) */ if (!index_only) { int removed = 0, gitmodules_modified = 0; + struct strbuf buf = STRBUF_INIT; for (i = 0; i < list.nr; i++) { const char *path = list.entry[i].name; if (list.entry[i].is_submodule) { @@ -398,7 +399,7 @@ int cmd_rm(int argc, const char **argv, const char *prefix) continue; } } else { - struct strbuf buf = STRBUF_INIT; + strbuf_reset(&buf); strbuf_addstr(&buf, path); if (!remove_dir_recursively(&buf, 0)) { removed = 1; @@ -410,7 +411,6 @@ int cmd_rm(int argc, const char **argv, const char *prefix) /* Submodule was removed by user */ if (!remove_path_from_gitmodules(path)) gitmodules_modified = 1; - strbuf_release(&buf); /* Fallthrough and let remove_path() fail. */ } } @@ -421,6 +421,7 @@ int cmd_rm(int argc, const char **argv, const char *prefix) if (!removed) die_errno("git rm: '%s'", path); } + strbuf_release(&buf); if (gitmodules_modified) stage_updated_gitmodules(); } diff --git a/builtin/send-pack.c b/builtin/send-pack.c index b961e5ae78..1ff5a67538 100644 --- a/builtin/send-pack.c +++ b/builtin/send-pack.c @@ -11,10 +11,16 @@ #include "transport.h" #include "version.h" #include "sha1-array.h" +#include "gpg-interface.h" +#include "gettext.h" -static const char send_pack_usage[] = -"git send-pack [--all | --mirror] [--dry-run] [--force] [--receive-pack=<git-receive-pack>] [--verbose] [--thin] [--atomic] [<host>:]<directory> [<ref>...]\n" -" --all and explicit <ref> specification are mutually exclusive."; +static const char * const send_pack_usage[] = { + N_("git send-pack [--all | --mirror] [--dry-run] [--force] " + "[--receive-pack=<git-receive-pack>] [--verbose] [--thin] [--atomic] " + "[<host>:]<directory> [<ref>...]\n" + " --all and explicit <ref> specification are mutually exclusive."), + NULL, +}; static struct send_pack_args args; @@ -91,6 +97,31 @@ static void print_helper_status(struct ref *ref) strbuf_release(&buf); } +static int send_pack_config(const char *k, const char *v, void *cb) +{ + git_gpg_config(k, v, NULL); + + if (!strcmp(k, "push.gpgsign")) { + const char *value; + if (!git_config_get_value("push.gpgsign", &value)) { + switch (git_config_maybe_bool("push.gpgsign", value)) { + case 0: + args.push_cert = SEND_PACK_PUSH_CERT_NEVER; + break; + case 1: + args.push_cert = SEND_PACK_PUSH_CERT_ALWAYS; + break; + default: + if (value && !strcasecmp(value, "if-asked")) + args.push_cert = SEND_PACK_PUSH_CERT_IF_ASKED; + else + return error("Invalid value for '%s'", k); + } + } + } + return 0; +} + int cmd_send_pack(int argc, const char **argv, const char *prefix) { int i, nr_refspecs = 0; @@ -106,114 +137,68 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix) int ret; int helper_status = 0; int send_all = 0; + int verbose = 0; const char *receivepack = "git-receive-pack"; + unsigned dry_run = 0; + unsigned send_mirror = 0; + unsigned force_update = 0; + unsigned quiet = 0; + int push_cert = 0; + unsigned use_thin_pack = 0; + unsigned atomic = 0; + unsigned stateless_rpc = 0; int flags; unsigned int reject_reasons; int progress = -1; int from_stdin = 0; struct push_cas_option cas = {0}; - argv++; - for (i = 1; i < argc; i++, argv++) { - const char *arg = *argv; - - if (*arg == '-') { - if (starts_with(arg, "--receive-pack=")) { - receivepack = arg + 15; - continue; - } - if (starts_with(arg, "--exec=")) { - receivepack = arg + 7; - continue; - } - if (starts_with(arg, "--remote=")) { - remote_name = arg + 9; - continue; - } - if (!strcmp(arg, "--all")) { - send_all = 1; - continue; - } - if (!strcmp(arg, "--dry-run")) { - args.dry_run = 1; - continue; - } - if (!strcmp(arg, "--mirror")) { - args.send_mirror = 1; - continue; - } - if (!strcmp(arg, "--force")) { - args.force_update = 1; - continue; - } - if (!strcmp(arg, "--quiet")) { - args.quiet = 1; - continue; - } - if (!strcmp(arg, "--verbose")) { - args.verbose = 1; - continue; - } - if (!strcmp(arg, "--signed")) { - args.push_cert = 1; - continue; - } - if (!strcmp(arg, "--progress")) { - progress = 1; - continue; - } - if (!strcmp(arg, "--no-progress")) { - progress = 0; - continue; - } - if (!strcmp(arg, "--thin")) { - args.use_thin_pack = 1; - continue; - } - if (!strcmp(arg, "--atomic")) { - args.atomic = 1; - continue; - } - if (!strcmp(arg, "--stateless-rpc")) { - args.stateless_rpc = 1; - continue; - } - if (!strcmp(arg, "--stdin")) { - from_stdin = 1; - continue; - } - if (!strcmp(arg, "--helper-status")) { - helper_status = 1; - continue; - } - if (!strcmp(arg, "--" CAS_OPT_NAME)) { - if (parse_push_cas_option(&cas, NULL, 0) < 0) - exit(1); - continue; - } - if (!strcmp(arg, "--no-" CAS_OPT_NAME)) { - if (parse_push_cas_option(&cas, NULL, 1) < 0) - exit(1); - continue; - } - if (starts_with(arg, "--" CAS_OPT_NAME "=")) { - if (parse_push_cas_option(&cas, - strchr(arg, '=') + 1, 0) < 0) - exit(1); - continue; - } - usage(send_pack_usage); - } - if (!dest) { - dest = arg; - continue; - } - refspecs = (const char **) argv; - nr_refspecs = argc - i; - break; + struct option options[] = { + OPT__VERBOSITY(&verbose), + OPT_STRING(0, "receive-pack", &receivepack, "receive-pack", N_("receive pack program")), + OPT_STRING(0, "exec", &receivepack, "receive-pack", N_("receive pack program")), + OPT_STRING(0, "remote", &remote_name, "remote", N_("remote name")), + OPT_BOOL(0, "all", &send_all, N_("push all refs")), + OPT_BOOL('n' , "dry-run", &dry_run, N_("dry run")), + OPT_BOOL(0, "mirror", &send_mirror, N_("mirror all refs")), + OPT_BOOL('f', "force", &force_update, N_("force updates")), + { OPTION_CALLBACK, + 0, "signed", &push_cert, "yes|no|if-asked", N_("GPG sign the push"), + PARSE_OPT_OPTARG, option_parse_push_signed }, + OPT_BOOL(0, "progress", &progress, N_("force progress reporting")), + OPT_BOOL(0, "thin", &use_thin_pack, N_("use thin pack")), + OPT_BOOL(0, "atomic", &atomic, N_("request atomic transaction on remote side")), + OPT_BOOL(0, "stateless-rpc", &stateless_rpc, N_("use stateless RPC protocol")), + OPT_BOOL(0, "stdin", &from_stdin, N_("read refs from stdin")), + OPT_BOOL(0, "helper-status", &helper_status, N_("print status from remote helper")), + { OPTION_CALLBACK, + 0, CAS_OPT_NAME, &cas, N_("refname>:<expect"), + N_("require old value of ref to be at this value"), + PARSE_OPT_OPTARG, parseopt_push_cas_option }, + OPT_END() + }; + + git_config(send_pack_config, NULL); + argc = parse_options(argc, argv, prefix, options, send_pack_usage, 0); + if (argc > 0) { + dest = argv[0]; + refspecs = (const char **)(argv + 1); + nr_refspecs = argc - 1; } + if (!dest) - usage(send_pack_usage); + usage_with_options(send_pack_usage, options); + + args.verbose = verbose; + args.dry_run = dry_run; + args.send_mirror = send_mirror; + args.force_update = force_update; + args.quiet = quiet; + args.push_cert = push_cert; + args.progress = progress; + args.use_thin_pack = use_thin_pack; + args.atomic = atomic; + args.stateless_rpc = stateless_rpc; if (from_stdin) { struct argv_array all_refspecs = ARGV_ARRAY_INIT; @@ -227,7 +212,7 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix) argv_array_push(&all_refspecs, buf); } else { struct strbuf line = STRBUF_INIT; - while (strbuf_getline(&line, stdin, '\n') != EOF) + while (strbuf_getline(&line, stdin) != EOF) argv_array_push(&all_refspecs, line.buf); strbuf_release(&line); } @@ -240,9 +225,9 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix) * --all and --mirror are incompatible; neither makes sense * with any refspecs. */ - if ((refspecs && (send_all || args.send_mirror)) || + if ((nr_refspecs > 0 && (send_all || args.send_mirror)) || (send_all && args.send_mirror)) - usage(send_pack_usage); + usage_with_options(send_pack_usage, options); if (remote_name) { remote = remote_get(remote_name); diff --git a/builtin/shortlog.c b/builtin/shortlog.c index c0bab6aaa9..ba0e1154a9 100644 --- a/builtin/shortlog.c +++ b/builtin/shortlog.c @@ -14,7 +14,26 @@ static char const * const shortlog_usage[] = { NULL }; -static int compare_by_number(const void *a1, const void *a2) +/* + * The util field of our string_list_items will contain one of two things: + * + * - if --summary is not in use, it will point to a string list of the + * oneline subjects assigned to this author + * + * - if --summary is in use, we don't need that list; we only need to know + * its size. So we abuse the pointer slot to store our integer counter. + * + * This macro accesses the latter. + */ +#define UTIL_TO_INT(x) ((intptr_t)(x)->util) + +static int compare_by_counter(const void *a1, const void *a2) +{ + const struct string_list_item *i1 = a1, *i2 = a2; + return UTIL_TO_INT(i2) - UTIL_TO_INT(i1); +} + +static int compare_by_list(const void *a1, const void *a2) { const struct string_list_item *i1 = a1, *i2 = a2; const struct string_list *l1 = i1->util, *l2 = i2->util; @@ -31,13 +50,9 @@ static void insert_one_record(struct shortlog *log, const char *author, const char *oneline) { - const char *dot3 = log->common_repo_prefix; - char *buffer, *p; struct string_list_item *item; const char *mailbuf, *namebuf; size_t namelen, maillen; - const char *eol; - struct strbuf subject = STRBUF_INIT; struct strbuf namemailbuf = STRBUF_INIT; struct ident_split ident; @@ -56,98 +71,95 @@ static void insert_one_record(struct shortlog *log, strbuf_addf(&namemailbuf, " <%.*s>", (int)maillen, mailbuf); item = string_list_insert(&log->list, namemailbuf.buf); - if (item->util == NULL) - item->util = xcalloc(1, sizeof(struct string_list)); - - /* Skip any leading whitespace, including any blank lines. */ - while (*oneline && isspace(*oneline)) - oneline++; - eol = strchr(oneline, '\n'); - if (!eol) - eol = oneline + strlen(oneline); - if (starts_with(oneline, "[PATCH")) { - char *eob = strchr(oneline, ']'); - if (eob && (!eol || eob < eol)) - oneline = eob + 1; - } - while (*oneline && isspace(*oneline) && *oneline != '\n') - oneline++; - format_subject(&subject, oneline, " "); - buffer = strbuf_detach(&subject, NULL); - - if (dot3) { - int dot3len = strlen(dot3); - if (dot3len > 5) { - while ((p = strstr(buffer, dot3)) != NULL) { - int taillen = strlen(p) - dot3len; - memcpy(p, "/.../", 5); - memmove(p + 5, p + dot3len, taillen + 1); + + if (log->summary) + item->util = (void *)(UTIL_TO_INT(item) + 1); + else { + const char *dot3 = log->common_repo_prefix; + char *buffer, *p; + struct strbuf subject = STRBUF_INIT; + const char *eol; + + /* Skip any leading whitespace, including any blank lines. */ + while (*oneline && isspace(*oneline)) + oneline++; + eol = strchr(oneline, '\n'); + if (!eol) + eol = oneline + strlen(oneline); + if (starts_with(oneline, "[PATCH")) { + char *eob = strchr(oneline, ']'); + if (eob && (!eol || eob < eol)) + oneline = eob + 1; + } + while (*oneline && isspace(*oneline) && *oneline != '\n') + oneline++; + format_subject(&subject, oneline, " "); + buffer = strbuf_detach(&subject, NULL); + + if (dot3) { + int dot3len = strlen(dot3); + if (dot3len > 5) { + while ((p = strstr(buffer, dot3)) != NULL) { + int taillen = strlen(p) - dot3len; + memcpy(p, "/.../", 5); + memmove(p + 5, p + dot3len, taillen + 1); + } } } - } - string_list_append(item->util, buffer); + if (item->util == NULL) + item->util = xcalloc(1, sizeof(struct string_list)); + string_list_append(item->util, buffer); + } } static void read_from_stdin(struct shortlog *log) { - char author[1024], oneline[1024]; + struct strbuf author = STRBUF_INIT; + struct strbuf oneline = STRBUF_INIT; - while (fgets(author, sizeof(author), stdin) != NULL) { - if (!(author[0] == 'A' || author[0] == 'a') || - !starts_with(author + 1, "uthor: ")) + while (strbuf_getline_lf(&author, stdin) != EOF) { + const char *v; + if (!skip_prefix(author.buf, "Author: ", &v) && + !skip_prefix(author.buf, "author ", &v)) continue; - while (fgets(oneline, sizeof(oneline), stdin) && - oneline[0] != '\n') + while (strbuf_getline_lf(&oneline, stdin) != EOF && + oneline.len) ; /* discard headers */ - while (fgets(oneline, sizeof(oneline), stdin) && - oneline[0] == '\n') + while (strbuf_getline_lf(&oneline, stdin) != EOF && + !oneline.len) ; /* discard blanks */ - insert_one_record(log, author + 8, oneline); + insert_one_record(log, v, oneline.buf); } + strbuf_release(&author); + strbuf_release(&oneline); } 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; - - pp_commit_easy(CMIT_FMT_RAW, commit, &buf); - buffer = buf.buf; - while (*buffer && *buffer != '\n') { - const char *eol = strchr(buffer, '\n'); - - if (eol == NULL) - eol = buffer + strlen(buffer); + struct strbuf author = STRBUF_INIT; + struct strbuf oneline = STRBUF_INIT; + struct pretty_print_context ctx = {0}; + + ctx.fmt = CMIT_FMT_USERFORMAT; + ctx.abbrev = log->abbrev; + ctx.subject = ""; + ctx.after_subject = ""; + ctx.date_mode.type = DATE_NORMAL; + ctx.output_encoding = get_log_output_encoding(); + + format_commit_message(commit, "%an <%ae>", &author, &ctx); + if (!log->summary) { + if (log->user_format) + pretty_print_commit(&ctx, commit, &oneline); else - eol++; - - if (starts_with(buffer, "author ")) - author = buffer + 7; - buffer = eol; - } - if (!author) { - warning(_("Missing author: %s"), - sha1_to_hex(commit->object.sha1)); - return; + format_commit_message(commit, "%s", &oneline, &ctx); } - 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; - ctx.output_encoding = get_log_output_encoding(); - pretty_print_commit(&ctx, commit, &ufbuf); - buffer = ufbuf.buf; - } else if (*buffer) { - buffer++; - } - insert_one_record(log, author, !*buffer ? "<none>" : buffer); - strbuf_release(&ufbuf); - strbuf_release(&buf); + + insert_one_record(log, author.buf, oneline.len ? oneline.buf : "<none>"); + + strbuf_release(&author); + strbuf_release(&oneline); } static void get_from_rev(struct rev_info *rev, struct shortlog *log) @@ -221,11 +233,11 @@ void shortlog_init(struct shortlog *log) int cmd_shortlog(int argc, const char **argv, const char *prefix) { - static struct shortlog log; - static struct rev_info rev; + struct shortlog log = { STRING_LIST_INIT_NODUP }; + struct rev_info rev; int nongit = !startup_info->have_repository; - static const struct option options[] = { + const struct option options[] = { OPT_BOOL('n', "numbered", &log.sort_by_number, N_("sort output according to the number of commits per author")), OPT_BOOL('s', "summary", &log.summary, @@ -264,6 +276,7 @@ parse_done: log.user_format = rev.commit_format == CMIT_FMT_USERFORMAT; log.abbrev = rev.abbrev; + log.file = rev.diffopt.file; /* assume HEAD if from a tty */ if (!nongit && !rev.pending.nr && isatty(0)) @@ -277,6 +290,8 @@ parse_done: get_from_rev(&rev, &log); shortlog_output(&log); + if (log.file != stdout) + fclose(log.file); return 0; } @@ -293,32 +308,34 @@ void shortlog_output(struct shortlog *log) struct strbuf sb = STRBUF_INIT; if (log->sort_by_number) - qsort(log->list.items, log->list.nr, sizeof(struct string_list_item), - compare_by_number); + QSORT(log->list.items, log->list.nr, + log->summary ? compare_by_counter : compare_by_list); for (i = 0; i < log->list.nr; i++) { - struct string_list *onelines = log->list.items[i].util; - + const struct string_list_item *item = &log->list.items[i]; if (log->summary) { - printf("%6d\t%s\n", onelines->nr, log->list.items[i].string); + fprintf(log->file, "%6d\t%s\n", + (int)UTIL_TO_INT(item), item->string); } else { - printf("%s (%d):\n", log->list.items[i].string, onelines->nr); + struct string_list *onelines = item->util; + fprintf(log->file, "%s (%d):\n", + item->string, onelines->nr); for (j = onelines->nr - 1; j >= 0; j--) { const char *msg = onelines->items[j].string; if (log->wrap_lines) { strbuf_reset(&sb); add_wrapped_shortlog_msg(&sb, msg, log); - fwrite(sb.buf, sb.len, 1, stdout); + fwrite(sb.buf, sb.len, 1, log->file); } else - printf(" %s\n", msg); + fprintf(log->file, " %s\n", msg); } - putchar('\n'); + putc('\n', log->file); + onelines->strdup_strings = 1; + string_list_clear(onelines, 0); + free(onelines); } - onelines->strdup_strings = 1; - string_list_clear(onelines, 0); - free(onelines); log->list.items[i].util = NULL; } diff --git a/builtin/show-branch.c b/builtin/show-branch.c index 323f857463..974f3403ab 100644 --- a/builtin/show-branch.c +++ b/builtin/show-branch.c @@ -3,6 +3,7 @@ #include "refs.h" #include "builtin.h" #include "color.h" +#include "argv-array.h" #include "parse-options.h" static const char* show_branch_usage[] = { @@ -16,9 +17,7 @@ static const char* show_branch_usage[] = { static int showbranch_use_color = -1; -static int default_num; -static int default_alloc; -static const char **default_arg; +static struct argv_array default_args = ARGV_ARRAY_INIT; #define UNINTERESTING 01 @@ -53,17 +52,6 @@ static struct commit *interesting(struct commit_list *list) return NULL; } -static struct commit *pop_one_commit(struct commit_list **list_p) -{ - struct commit *commit; - struct commit_list *list; - list = *list_p; - commit = list->item; - *list_p = list->next; - free(list); - return commit; -} - struct commit_name { const char *head_name; /* which head's ancestor? */ int generation; /* how many parents away from head_name */ @@ -213,7 +201,7 @@ static void join_revs(struct commit_list **list_p, while (*list_p) { struct commit_list *parents; int still_interesting = !!interesting(*list_p); - struct commit *commit = pop_one_commit(list_p); + struct commit *commit = pop_commit(list_p); int flags = commit->object.flags & all_mask; if (!still_interesting && extra <= 0) @@ -303,7 +291,7 @@ static void show_one_commit(struct commit *commit, int no_name) } else printf("[%s] ", - find_unique_abbrev(commit->object.sha1, + find_unique_abbrev(commit->object.oid.hash, DEFAULT_ABBREV)); } puts(pretty_str); @@ -365,8 +353,7 @@ static int compare_ref_name(const void *a_, const void *b_) static void sort_ref_range(int bottom, int top) { - qsort(ref_name + bottom, top - bottom, sizeof(ref_name[0]), - compare_ref_name); + QSORT(ref_name + bottom, top - bottom, compare_ref_name); } static int append_ref(const char *refname, const struct object_id *oid, @@ -385,8 +372,9 @@ static int append_ref(const char *refname, const struct object_id *oid, return 0; } if (MAX_REVS <= ref_name_cnt) { - warning("ignoring %s; cannot handle more than %d refs", - refname, MAX_REVS); + warning(Q_("ignoring %s; cannot handle more than %d ref", + "ignoring %s; cannot handle more than %d refs", + MAX_REVS), refname, MAX_REVS); return 0; } ref_name[ref_name_cnt++] = xstrdup(refname); @@ -504,11 +492,11 @@ static int show_merge_base(struct commit_list *seen, int num_rev) int exit_status = 1; while (seen) { - struct commit *commit = pop_one_commit(&seen); + struct commit *commit = pop_commit(&seen); int flags = commit->object.flags & all_mask; if (!(flags & UNINTERESTING) && ((flags & all_revs) == all_revs)) { - puts(sha1_to_hex(commit->object.sha1)); + puts(oid_to_hex(&commit->object.oid)); exit_status = 0; commit->object.flags |= UNINTERESTING; } @@ -528,7 +516,7 @@ static int show_independent(struct commit **rev, unsigned int flag = rev_mask[i]; if (commit->object.flags == flag) - puts(sha1_to_hex(commit->object.sha1)); + puts(oid_to_hex(&commit->object.oid)); commit->object.flags |= UNINTERESTING; } return 0; @@ -550,9 +538,8 @@ static void append_one_rev(const char *av) for_each_ref(append_matching_ref, NULL); if (saved_matches == ref_name_cnt && ref_name_cnt < MAX_REVS) - error("no matching refs with %s", av); - if (saved_matches + 1 < ref_name_cnt) - sort_ref_range(saved_matches, ref_name_cnt); + error(_("no matching refs with %s"), av); + sort_ref_range(saved_matches, ref_name_cnt); return; } die("bad sha1 reference %s", av); @@ -567,16 +554,9 @@ static int git_show_branch_config(const char *var, const char *value, void *cb) * default_arg is now passed to parse_options(), so we need to * mimic the real argv a bit better. */ - if (!default_num) { - default_alloc = 20; - default_arg = xcalloc(default_alloc, sizeof(*default_arg)); - default_arg[default_num++] = "show-branch"; - } else if (default_alloc <= default_num + 1) { - default_alloc = default_alloc * 3 / 2 + 20; - REALLOC_ARRAY(default_arg, default_alloc); - } - default_arg[default_num++] = xstrdup(value); - default_arg[default_num] = NULL; + if (!default_args.argc) + argv_array_push(&default_args, "show-branch"); + argv_array_push(&default_args, value); return 0; } @@ -696,9 +676,9 @@ int cmd_show_branch(int ac, const char **av, const char *prefix) git_config(git_show_branch_config, NULL); /* If nothing is specified, try the default first */ - if (ac == 1 && default_num) { - ac = default_num; - av = default_arg; + if (ac == 1 && default_args.argc) { + ac = default_args.argc; + av = default_args.argv; } ac = parse_options(ac, av, prefix, builtin_show_branch_options, @@ -720,8 +700,8 @@ int cmd_show_branch(int ac, const char **av, const char *prefix) * * Also --all and --remotes do not make sense either. */ - die("--reflog is incompatible with --all, --remotes, " - "--independent or --merge-base"); + die(_("--reflog is incompatible with --all, --remotes, " + "--independent or --merge-base")); } /* If nothing is specified, show all branches by default */ @@ -730,7 +710,6 @@ int cmd_show_branch(int ac, const char **av, const char *prefix) if (reflog) { struct object_id oid; - char nth_desc[256]; char *ref; int base = 0; unsigned int flags = 0; @@ -744,15 +723,18 @@ int cmd_show_branch(int ac, const char **av, const char *prefix) fake_av[1] = NULL; av = fake_av; ac = 1; + if (!*av) + die(_("no branches given, and HEAD is not valid")); } if (ac != 1) - die("--reflog option needs one branch name"); + die(_("--reflog option needs one branch name")); if (MAX_REVS < reflog) - die("Only %d entries can be shown at one time.", - MAX_REVS); + die(Q_("only %d entry can be shown at one time.", + "only %d entries can be shown at one time.", + MAX_REVS), MAX_REVS); if (!dwim_ref(*av, strlen(*av), oid.hash, &ref)) - die("No such ref %s", *av); + die(_("no such ref %s"), *av); /* Has the base been specified? */ if (reflog_base) { @@ -769,6 +751,7 @@ int cmd_show_branch(int ac, const char **av, const char *prefix) for (i = 0; i < reflog; i++) { char *logmsg; + char *nth_desc; const char *msg; unsigned long timestamp; int tz; @@ -784,11 +767,14 @@ int cmd_show_branch(int ac, const char **av, const char *prefix) else msg++; reflog_msg[i] = xstrfmt("(%s) %s", - show_date(timestamp, tz, 1), + show_date(timestamp, tz, + DATE_MODE(RELATIVE)), msg); free(logmsg); - sprintf(nth_desc, "%s@{%d}", *av, base+i); + + nth_desc = xstrfmt("%s@{%d}", *av, base+i); append_ref(nth_desc, &oid, 1); + free(nth_desc); } free(ref); } @@ -840,12 +826,14 @@ int cmd_show_branch(int ac, const char **av, const char *prefix) unsigned int flag = 1u << (num_rev + REV_SHIFT); if (MAX_REVS <= num_rev) - die("cannot handle more than %d revs.", MAX_REVS); + die(Q_("cannot handle more than %d rev.", + "cannot handle more than %d revs.", + MAX_REVS), MAX_REVS); if (get_sha1(ref_name[num_rev], revkey.hash)) - die("'%s' is not a valid ref.", ref_name[num_rev]); + die(_("'%s' is not a valid ref."), ref_name[num_rev]); commit = lookup_commit_reference(revkey.hash); if (!commit) - die("cannot find commit %s (%s)", + die(_("cannot find commit %s (%s)"), ref_name[num_rev], oid_to_hex(&revkey)); parse_commit(commit); mark_seen(commit, &seen); @@ -881,7 +869,7 @@ int cmd_show_branch(int ac, const char **av, const char *prefix) head_len, ref_name[i], head_oid.hash, - rev[i]->object.sha1); + rev[i]->object.oid.hash); if (extra < 0) printf("%c [%s] ", is_head ? '*' : ' ', ref_name[i]); @@ -924,7 +912,7 @@ int cmd_show_branch(int ac, const char **av, const char *prefix) all_revs = all_mask & ~((1u << REV_SHIFT) - 1); while (seen) { - struct commit *commit = pop_one_commit(&seen); + struct commit *commit = pop_commit(&seen); int this_flag = commit->object.flags; int is_merge_point = ((this_flag & all_revs) == all_revs); diff --git a/builtin/show-ref.c b/builtin/show-ref.c index dfbc314ac2..6d4e669002 100644 --- a/builtin/show-ref.c +++ b/builtin/show-ref.c @@ -8,7 +8,7 @@ static const char * const show_ref_usage[] = { N_("git show-ref [-q | --quiet] [--verify] [--head] [-d | --dereference] [-s | --hash[=<n>]] [--abbrev[=<n>]] [--tags] [--heads] [--] [<pattern>...]"), - N_("git show-ref --exclude-existing[=pattern] < ref-list"), + N_("git show-ref --exclude-existing[=<pattern>]"), NULL }; @@ -161,11 +161,6 @@ static int exclude_existing_callback(const struct option *opt, const char *arg, return 0; } -static int help_callback(const struct option *opt, const char *arg, int unset) -{ - return -1; -} - static const struct option show_ref_options[] = { OPT_BOOL(0, "tags", &tags_only, N_("only show tags (can be combined with heads)")), OPT_BOOL(0, "heads", &heads_only, N_("only show heads (can be combined with tags)")), @@ -186,18 +181,13 @@ static const struct option show_ref_options[] = { { OPTION_CALLBACK, 0, "exclude-existing", &exclude_existing_arg, N_("pattern"), N_("show refs from stdin that aren't in local repository"), PARSE_OPT_OPTARG | PARSE_OPT_NONEG, exclude_existing_callback }, - { OPTION_CALLBACK, 0, "help-all", NULL, NULL, N_("show usage"), - PARSE_OPT_HIDDEN | PARSE_OPT_NOARG, help_callback }, OPT_END() }; int cmd_show_ref(int argc, const char **argv, const char *prefix) { - if (argc == 2 && !strcmp(argv[1], "-h")) - usage_with_options(show_ref_usage, show_ref_options); - argc = parse_options(argc, argv, prefix, show_ref_options, - show_ref_usage, PARSE_OPT_NO_INTERNAL_HELP); + show_ref_usage, 0); if (exclude_arg) return exclude_existing(exclude_existing_arg); diff --git a/builtin/stripspace.c b/builtin/stripspace.c index 1259ed708b..1e62a008cb 100644 --- a/builtin/stripspace.c +++ b/builtin/stripspace.c @@ -1,71 +1,7 @@ #include "builtin.h" #include "cache.h" - -/* - * Returns the length of a line, without trailing spaces. - * - * If the line ends with newline, it will be removed too. - */ -static size_t cleanup(char *line, size_t len) -{ - while (len) { - unsigned char c = line[len - 1]; - if (!isspace(c)) - break; - len--; - } - - return len; -} - -/* - * Remove empty lines from the beginning and end - * and also trailing spaces from every line. - * - * Turn multiple consecutive empty lines between paragraphs - * into just one empty line. - * - * If the input has only empty lines and spaces, - * no output will be produced. - * - * If last line does not have a newline at the end, one is added. - * - * Enable skip_comments to skip every line starting with comment - * character. - */ -void stripspace(struct strbuf *sb, int skip_comments) -{ - int empties = 0; - size_t i, j, len, newlen; - char *eol; - - /* We may have to add a newline. */ - strbuf_grow(sb, 1); - - for (i = j = 0; i < sb->len; i += len, j += newlen) { - eol = memchr(sb->buf + i, '\n', sb->len - i); - len = eol ? eol - (sb->buf + i) + 1 : sb->len - i; - - if (skip_comments && len && sb->buf[i] == comment_line_char) { - newlen = 0; - continue; - } - newlen = cleanup(sb->buf + i, len); - - /* Not just an empty line? */ - if (newlen) { - if (empties > 0 && j > 0) - sb->buf[j++] = '\n'; - empties = 0; - memmove(sb->buf + j, sb->buf + i, newlen); - sb->buf[newlen + j++] = '\n'; - } else { - empties++; - } - } - - strbuf_setlen(sb, j); -} +#include "parse-options.h" +#include "strbuf.h" static void comment_lines(struct strbuf *buf) { @@ -77,41 +13,47 @@ static void comment_lines(struct strbuf *buf) free(msg); } -static const char *usage_msg = "\n" -" git stripspace [-s | --strip-comments] < input\n" -" git stripspace [-c | --comment-lines] < input"; +static const char * const stripspace_usage[] = { + N_("git stripspace [-s | --strip-comments]"), + N_("git stripspace [-c | --comment-lines]"), + NULL +}; + +enum stripspace_mode { + STRIP_DEFAULT = 0, + STRIP_COMMENTS, + COMMENT_LINES +}; int cmd_stripspace(int argc, const char **argv, const char *prefix) { struct strbuf buf = STRBUF_INIT; - int strip_comments = 0; - enum { INVAL = 0, STRIP_SPACE = 1, COMMENT_LINES = 2 } mode = STRIP_SPACE; - - if (argc == 2) { - if (!strcmp(argv[1], "-s") || - !strcmp(argv[1], "--strip-comments")) { - strip_comments = 1; - } else if (!strcmp(argv[1], "-c") || - !strcmp(argv[1], "--comment-lines")) { - mode = COMMENT_LINES; - } else { - mode = INVAL; - } - } else if (argc > 1) { - mode = INVAL; - } - - if (mode == INVAL) - usage(usage_msg); - - if (strip_comments || mode == COMMENT_LINES) + enum stripspace_mode mode = STRIP_DEFAULT; + + const struct option options[] = { + OPT_CMDMODE('s', "strip-comments", &mode, + N_("skip and remove all lines starting with comment character"), + STRIP_COMMENTS), + OPT_CMDMODE('c', "comment-lines", &mode, + N_("prepend comment character and space to each line"), + COMMENT_LINES), + OPT_END() + }; + + argc = parse_options(argc, argv, prefix, options, stripspace_usage, 0); + if (argc) + usage_with_options(stripspace_usage, options); + + if (mode == STRIP_COMMENTS || mode == COMMENT_LINES) { + setup_git_directory_gently(NULL); git_config(git_default_config, NULL); + } if (strbuf_read(&buf, 0, 1024) < 0) die_errno("could not read the input"); - if (mode == STRIP_SPACE) - stripspace(&buf, strip_comments); + if (mode == STRIP_DEFAULT || mode == STRIP_COMMENTS) + strbuf_stripspace(&buf, mode == STRIP_COMMENTS); else comment_lines(&buf); diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c new file mode 100644 index 0000000000..4beeda5f9f --- /dev/null +++ b/builtin/submodule--helper.c @@ -0,0 +1,1109 @@ +#include "builtin.h" +#include "cache.h" +#include "parse-options.h" +#include "quote.h" +#include "pathspec.h" +#include "dir.h" +#include "utf8.h" +#include "submodule.h" +#include "submodule-config.h" +#include "string-list.h" +#include "run-command.h" +#include "remote.h" +#include "refs.h" +#include "connect.h" + +static char *get_default_remote(void) +{ + char *dest = NULL, *ret; + unsigned char sha1[20]; + struct strbuf sb = STRBUF_INIT; + const char *refname = resolve_ref_unsafe("HEAD", 0, sha1, NULL); + + if (!refname) + die(_("No such ref: %s"), "HEAD"); + + /* detached HEAD */ + if (!strcmp(refname, "HEAD")) + return xstrdup("origin"); + + if (!skip_prefix(refname, "refs/heads/", &refname)) + die(_("Expecting a full ref name, got %s"), refname); + + strbuf_addf(&sb, "branch.%s.remote", refname); + if (git_config_get_string(sb.buf, &dest)) + ret = xstrdup("origin"); + else + ret = dest; + + strbuf_release(&sb); + return ret; +} + +static int starts_with_dot_slash(const char *str) +{ + return str[0] == '.' && is_dir_sep(str[1]); +} + +static int starts_with_dot_dot_slash(const char *str) +{ + return str[0] == '.' && str[1] == '.' && is_dir_sep(str[2]); +} + +/* + * Returns 1 if it was the last chop before ':'. + */ +static int chop_last_dir(char **remoteurl, int is_relative) +{ + char *rfind = find_last_dir_sep(*remoteurl); + if (rfind) { + *rfind = '\0'; + return 0; + } + + rfind = strrchr(*remoteurl, ':'); + if (rfind) { + *rfind = '\0'; + return 1; + } + + if (is_relative || !strcmp(".", *remoteurl)) + die(_("cannot strip one component off url '%s'"), + *remoteurl); + + free(*remoteurl); + *remoteurl = xstrdup("."); + return 0; +} + +/* + * The `url` argument is the URL that navigates to the submodule origin + * repo. When relative, this URL is relative to the superproject origin + * URL repo. The `up_path` argument, if specified, is the relative + * path that navigates from the submodule working tree to the superproject + * working tree. Returns the origin URL of the submodule. + * + * Return either an absolute URL or filesystem path (if the superproject + * origin URL is an absolute URL or filesystem path, respectively) or a + * relative file system path (if the superproject origin URL is a relative + * file system path). + * + * When the output is a relative file system path, the path is either + * relative to the submodule working tree, if up_path is specified, or to + * the superproject working tree otherwise. + * + * NEEDSWORK: This works incorrectly on the domain and protocol part. + * remote_url url outcome expectation + * http://a.com/b ../c http://a.com/c as is + * http://a.com/b/ ../c http://a.com/c same as previous line, but + * ignore trailing slash in url + * http://a.com/b ../../c http://c error out + * http://a.com/b ../../../c http:/c error out + * http://a.com/b ../../../../c http:c error out + * http://a.com/b ../../../../../c .:c error out + * NEEDSWORK: Given how chop_last_dir() works, this function is broken + * when a local part has a colon in its path component, too. + */ +static char *relative_url(const char *remote_url, + const char *url, + const char *up_path) +{ + int is_relative = 0; + int colonsep = 0; + char *out; + char *remoteurl = xstrdup(remote_url); + struct strbuf sb = STRBUF_INIT; + size_t len = strlen(remoteurl); + + if (is_dir_sep(remoteurl[len-1])) + remoteurl[len-1] = '\0'; + + if (!url_is_local_not_ssh(remoteurl) || is_absolute_path(remoteurl)) + is_relative = 0; + else { + is_relative = 1; + /* + * Prepend a './' to ensure all relative + * remoteurls start with './' or '../' + */ + if (!starts_with_dot_slash(remoteurl) && + !starts_with_dot_dot_slash(remoteurl)) { + strbuf_reset(&sb); + strbuf_addf(&sb, "./%s", remoteurl); + free(remoteurl); + remoteurl = strbuf_detach(&sb, NULL); + } + } + /* + * When the url starts with '../', remove that and the + * last directory in remoteurl. + */ + while (url) { + if (starts_with_dot_dot_slash(url)) { + url += 3; + colonsep |= chop_last_dir(&remoteurl, is_relative); + } else if (starts_with_dot_slash(url)) + url += 2; + else + break; + } + strbuf_reset(&sb); + strbuf_addf(&sb, "%s%s%s", remoteurl, colonsep ? ":" : "/", url); + if (ends_with(url, "/")) + strbuf_setlen(&sb, sb.len - 1); + free(remoteurl); + + if (starts_with_dot_slash(sb.buf)) + out = xstrdup(sb.buf + 2); + else + out = xstrdup(sb.buf); + strbuf_reset(&sb); + + if (!up_path || !is_relative) + return out; + + strbuf_addf(&sb, "%s%s", up_path, out); + free(out); + return strbuf_detach(&sb, NULL); +} + +static int resolve_relative_url(int argc, const char **argv, const char *prefix) +{ + char *remoteurl = NULL; + char *remote = get_default_remote(); + const char *up_path = NULL; + char *res; + const char *url; + struct strbuf sb = STRBUF_INIT; + + if (argc != 2 && argc != 3) + die("resolve-relative-url only accepts one or two arguments"); + + url = argv[1]; + strbuf_addf(&sb, "remote.%s.url", remote); + free(remote); + + if (git_config_get_string(sb.buf, &remoteurl)) + /* the repository is its own authoritative upstream */ + remoteurl = xgetcwd(); + + if (argc == 3) + up_path = argv[2]; + + res = relative_url(remoteurl, url, up_path); + puts(res); + free(res); + free(remoteurl); + return 0; +} + +static int resolve_relative_url_test(int argc, const char **argv, const char *prefix) +{ + char *remoteurl, *res; + const char *up_path, *url; + + if (argc != 4) + die("resolve-relative-url-test only accepts three arguments: <up_path> <remoteurl> <url>"); + + up_path = argv[1]; + remoteurl = xstrdup(argv[2]); + url = argv[3]; + + if (!strcmp(up_path, "(null)")) + up_path = NULL; + + res = relative_url(remoteurl, url, up_path); + puts(res); + free(res); + free(remoteurl); + return 0; +} + +struct module_list { + const struct cache_entry **entries; + int alloc, nr; +}; +#define MODULE_LIST_INIT { NULL, 0, 0 } + +static int module_list_compute(int argc, const char **argv, + const char *prefix, + struct pathspec *pathspec, + struct module_list *list) +{ + int i, result = 0; + char *ps_matched = NULL; + parse_pathspec(pathspec, 0, + PATHSPEC_PREFER_FULL | + PATHSPEC_STRIP_SUBMODULE_SLASH_CHEAP, + prefix, argv); + + if (pathspec->nr) + ps_matched = xcalloc(pathspec->nr, 1); + + if (read_cache() < 0) + die(_("index file corrupt")); + + for (i = 0; i < active_nr; i++) { + const struct cache_entry *ce = active_cache[i]; + + if (!match_pathspec(pathspec, ce->name, ce_namelen(ce), + 0, ps_matched, 1) || + !S_ISGITLINK(ce->ce_mode)) + continue; + + ALLOC_GROW(list->entries, list->nr + 1, list->alloc); + list->entries[list->nr++] = ce; + while (i + 1 < active_nr && + !strcmp(ce->name, active_cache[i + 1]->name)) + /* + * Skip entries with the same name in different stages + * to make sure an entry is returned only once. + */ + i++; + } + + if (ps_matched && report_path_error(ps_matched, pathspec, prefix)) + result = -1; + + free(ps_matched); + + return result; +} + +static int module_list(int argc, const char **argv, const char *prefix) +{ + int i; + struct pathspec pathspec; + struct module_list list = MODULE_LIST_INIT; + + struct option module_list_options[] = { + OPT_STRING(0, "prefix", &prefix, + N_("path"), + N_("alternative anchor for relative paths")), + OPT_END() + }; + + const char *const git_submodule_helper_usage[] = { + N_("git submodule--helper list [--prefix=<path>] [<path>...]"), + NULL + }; + + argc = parse_options(argc, argv, prefix, module_list_options, + git_submodule_helper_usage, 0); + + if (module_list_compute(argc, argv, prefix, &pathspec, &list) < 0) + return 1; + + for (i = 0; i < list.nr; i++) { + const struct cache_entry *ce = list.entries[i]; + + if (ce_stage(ce)) + printf("%06o %s U\t", ce->ce_mode, sha1_to_hex(null_sha1)); + else + printf("%06o %s %d\t", ce->ce_mode, + oid_to_hex(&ce->oid), ce_stage(ce)); + + utf8_fprintf(stdout, "%s\n", ce->name); + } + return 0; +} + +static void init_submodule(const char *path, const char *prefix, int quiet) +{ + const struct submodule *sub; + struct strbuf sb = STRBUF_INIT; + char *upd = NULL, *url = NULL, *displaypath; + + /* Only loads from .gitmodules, no overlay with .git/config */ + gitmodules_config(); + + if (prefix) { + strbuf_addf(&sb, "%s%s", prefix, path); + displaypath = strbuf_detach(&sb, NULL); + } else + displaypath = xstrdup(path); + + sub = submodule_from_path(null_sha1, path); + + if (!sub) + die(_("No url found for submodule path '%s' in .gitmodules"), + displaypath); + + /* + * Copy url setting when it is not set yet. + * To look up the url in .git/config, we must not fall back to + * .gitmodules, so look it up directly. + */ + strbuf_reset(&sb); + strbuf_addf(&sb, "submodule.%s.url", sub->name); + if (git_config_get_string(sb.buf, &url)) { + url = xstrdup(sub->url); + + if (!url) + die(_("No url found for submodule path '%s' in .gitmodules"), + displaypath); + + /* Possibly a url relative to parent */ + if (starts_with_dot_dot_slash(url) || + starts_with_dot_slash(url)) { + char *remoteurl, *relurl; + char *remote = get_default_remote(); + struct strbuf remotesb = STRBUF_INIT; + strbuf_addf(&remotesb, "remote.%s.url", remote); + free(remote); + + if (git_config_get_string(remotesb.buf, &remoteurl)) + /* + * The repository is its own + * authoritative upstream + */ + remoteurl = xgetcwd(); + relurl = relative_url(remoteurl, url, NULL); + strbuf_release(&remotesb); + free(remoteurl); + free(url); + url = relurl; + } + + if (git_config_set_gently(sb.buf, url)) + die(_("Failed to register url for submodule path '%s'"), + displaypath); + if (!quiet) + fprintf(stderr, + _("Submodule '%s' (%s) registered for path '%s'\n"), + sub->name, url, displaypath); + } + + /* Copy "update" setting when it is not set yet */ + strbuf_reset(&sb); + strbuf_addf(&sb, "submodule.%s.update", sub->name); + if (git_config_get_string(sb.buf, &upd) && + sub->update_strategy.type != SM_UPDATE_UNSPECIFIED) { + if (sub->update_strategy.type == SM_UPDATE_COMMAND) { + fprintf(stderr, _("warning: command update mode suggested for submodule '%s'\n"), + sub->name); + upd = xstrdup("none"); + } else + upd = xstrdup(submodule_strategy_to_string(&sub->update_strategy)); + + if (git_config_set_gently(sb.buf, upd)) + die(_("Failed to register update mode for submodule path '%s'"), displaypath); + } + strbuf_release(&sb); + free(displaypath); + free(url); + free(upd); +} + +static int module_init(int argc, const char **argv, const char *prefix) +{ + struct pathspec pathspec; + struct module_list list = MODULE_LIST_INIT; + int quiet = 0; + int i; + + struct option module_init_options[] = { + OPT_STRING(0, "prefix", &prefix, + N_("path"), + N_("alternative anchor for relative paths")), + OPT__QUIET(&quiet, N_("Suppress output for initializing a submodule")), + OPT_END() + }; + + const char *const git_submodule_helper_usage[] = { + N_("git submodule--helper init [<path>]"), + NULL + }; + + argc = parse_options(argc, argv, prefix, module_init_options, + git_submodule_helper_usage, 0); + + if (module_list_compute(argc, argv, prefix, &pathspec, &list) < 0) + return 1; + + for (i = 0; i < list.nr; i++) + init_submodule(list.entries[i]->name, prefix, quiet); + + return 0; +} + +static int module_name(int argc, const char **argv, const char *prefix) +{ + const struct submodule *sub; + + if (argc != 2) + usage(_("git submodule--helper name <path>")); + + gitmodules_config(); + sub = submodule_from_path(null_sha1, argv[1]); + + if (!sub) + die(_("no submodule mapping found in .gitmodules for path '%s'"), + argv[1]); + + printf("%s\n", sub->name); + + return 0; +} + +static int clone_submodule(const char *path, const char *gitdir, const char *url, + const char *depth, struct string_list *reference, + int quiet, int progress) +{ + struct child_process cp = CHILD_PROCESS_INIT; + + argv_array_push(&cp.args, "clone"); + argv_array_push(&cp.args, "--no-checkout"); + if (quiet) + argv_array_push(&cp.args, "--quiet"); + if (progress) + argv_array_push(&cp.args, "--progress"); + if (depth && *depth) + argv_array_pushl(&cp.args, "--depth", depth, NULL); + if (reference->nr) { + struct string_list_item *item; + for_each_string_list_item(item, reference) + argv_array_pushl(&cp.args, "--reference", + item->string, NULL); + } + if (gitdir && *gitdir) + argv_array_pushl(&cp.args, "--separate-git-dir", gitdir, NULL); + + argv_array_push(&cp.args, url); + argv_array_push(&cp.args, path); + + cp.git_cmd = 1; + prepare_submodule_repo_env(&cp.env_array); + cp.no_stdin = 1; + + return run_command(&cp); +} + +struct submodule_alternate_setup { + const char *submodule_name; + enum SUBMODULE_ALTERNATE_ERROR_MODE { + SUBMODULE_ALTERNATE_ERROR_DIE, + SUBMODULE_ALTERNATE_ERROR_INFO, + SUBMODULE_ALTERNATE_ERROR_IGNORE + } error_mode; + struct string_list *reference; +}; +#define SUBMODULE_ALTERNATE_SETUP_INIT { NULL, \ + SUBMODULE_ALTERNATE_ERROR_IGNORE, NULL } + +static int add_possible_reference_from_superproject( + struct alternate_object_database *alt, void *sas_cb) +{ + struct submodule_alternate_setup *sas = sas_cb; + + /* + * If the alternate object store is another repository, try the + * standard layout with .git/modules/<name>/objects + */ + if (ends_with(alt->path, ".git/objects")) { + char *sm_alternate; + struct strbuf sb = STRBUF_INIT; + struct strbuf err = STRBUF_INIT; + strbuf_add(&sb, alt->path, strlen(alt->path) - strlen("objects")); + + /* + * We need to end the new path with '/' to mark it as a dir, + * otherwise a submodule name containing '/' will be broken + * as the last part of a missing submodule reference would + * be taken as a file name. + */ + strbuf_addf(&sb, "modules/%s/", sas->submodule_name); + + sm_alternate = compute_alternate_path(sb.buf, &err); + if (sm_alternate) { + string_list_append(sas->reference, xstrdup(sb.buf)); + free(sm_alternate); + } else { + switch (sas->error_mode) { + case SUBMODULE_ALTERNATE_ERROR_DIE: + die(_("submodule '%s' cannot add alternate: %s"), + sas->submodule_name, err.buf); + case SUBMODULE_ALTERNATE_ERROR_INFO: + fprintf(stderr, _("submodule '%s' cannot add alternate: %s"), + sas->submodule_name, err.buf); + case SUBMODULE_ALTERNATE_ERROR_IGNORE: + ; /* nothing */ + } + } + strbuf_release(&sb); + } + + return 0; +} + +static void prepare_possible_alternates(const char *sm_name, + struct string_list *reference) +{ + char *sm_alternate = NULL, *error_strategy = NULL; + struct submodule_alternate_setup sas = SUBMODULE_ALTERNATE_SETUP_INIT; + + git_config_get_string("submodule.alternateLocation", &sm_alternate); + if (!sm_alternate) + return; + + git_config_get_string("submodule.alternateErrorStrategy", &error_strategy); + + if (!error_strategy) + error_strategy = xstrdup("die"); + + sas.submodule_name = sm_name; + sas.reference = reference; + if (!strcmp(error_strategy, "die")) + sas.error_mode = SUBMODULE_ALTERNATE_ERROR_DIE; + else if (!strcmp(error_strategy, "info")) + sas.error_mode = SUBMODULE_ALTERNATE_ERROR_INFO; + else if (!strcmp(error_strategy, "ignore")) + sas.error_mode = SUBMODULE_ALTERNATE_ERROR_IGNORE; + else + die(_("Value '%s' for submodule.alternateErrorStrategy is not recognized"), error_strategy); + + if (!strcmp(sm_alternate, "superproject")) + foreach_alt_odb(add_possible_reference_from_superproject, &sas); + else if (!strcmp(sm_alternate, "no")) + ; /* do nothing */ + else + die(_("Value '%s' for submodule.alternateLocation is not recognized"), sm_alternate); + + free(sm_alternate); + free(error_strategy); +} + +static int module_clone(int argc, const char **argv, const char *prefix) +{ + const char *name = NULL, *url = NULL, *depth = NULL; + int quiet = 0; + int progress = 0; + FILE *submodule_dot_git; + char *p, *path = NULL, *sm_gitdir; + struct strbuf rel_path = STRBUF_INIT; + struct strbuf sb = STRBUF_INIT; + struct string_list reference = STRING_LIST_INIT_NODUP; + + struct option module_clone_options[] = { + OPT_STRING(0, "prefix", &prefix, + N_("path"), + N_("alternative anchor for relative paths")), + OPT_STRING(0, "path", &path, + N_("path"), + N_("where the new submodule will be cloned to")), + OPT_STRING(0, "name", &name, + N_("string"), + N_("name of the new submodule")), + OPT_STRING(0, "url", &url, + N_("string"), + N_("url where to clone the submodule from")), + OPT_STRING_LIST(0, "reference", &reference, + N_("repo"), + N_("reference repository")), + OPT_STRING(0, "depth", &depth, + N_("string"), + N_("depth for shallow clones")), + OPT__QUIET(&quiet, "Suppress output for cloning a submodule"), + OPT_BOOL(0, "progress", &progress, + N_("force cloning progress")), + OPT_END() + }; + + const char *const git_submodule_helper_usage[] = { + N_("git submodule--helper clone [--prefix=<path>] [--quiet] " + "[--reference <repository>] [--name <name>] [--depth <depth>] " + "--url <url> --path <path>"), + NULL + }; + + argc = parse_options(argc, argv, prefix, module_clone_options, + git_submodule_helper_usage, 0); + + if (argc || !url || !path || !*path) + usage_with_options(git_submodule_helper_usage, + module_clone_options); + + strbuf_addf(&sb, "%s/modules/%s", get_git_dir(), name); + sm_gitdir = xstrdup(absolute_path(sb.buf)); + strbuf_reset(&sb); + + if (!is_absolute_path(path)) { + strbuf_addf(&sb, "%s/%s", get_git_work_tree(), path); + path = strbuf_detach(&sb, NULL); + } else + path = xstrdup(path); + + if (!file_exists(sm_gitdir)) { + if (safe_create_leading_directories_const(sm_gitdir) < 0) + die(_("could not create directory '%s'"), sm_gitdir); + + prepare_possible_alternates(name, &reference); + + if (clone_submodule(path, sm_gitdir, url, depth, &reference, + quiet, progress)) + die(_("clone of '%s' into submodule path '%s' failed"), + url, path); + } else { + if (safe_create_leading_directories_const(path) < 0) + die(_("could not create directory '%s'"), path); + strbuf_addf(&sb, "%s/index", sm_gitdir); + unlink_or_warn(sb.buf); + strbuf_reset(&sb); + } + + /* Write a .git file in the submodule to redirect to the superproject. */ + strbuf_addf(&sb, "%s/.git", path); + if (safe_create_leading_directories_const(sb.buf) < 0) + die(_("could not create leading directories of '%s'"), sb.buf); + submodule_dot_git = fopen(sb.buf, "w"); + if (!submodule_dot_git) + die_errno(_("cannot open file '%s'"), sb.buf); + + fprintf_or_die(submodule_dot_git, "gitdir: %s\n", + relative_path(sm_gitdir, path, &rel_path)); + if (fclose(submodule_dot_git)) + die(_("could not close file %s"), sb.buf); + strbuf_reset(&sb); + strbuf_reset(&rel_path); + + /* Redirect the worktree of the submodule in the superproject's config */ + p = git_pathdup_submodule(path, "config"); + if (!p) + die(_("could not get submodule directory for '%s'"), path); + git_config_set_in_file(p, "core.worktree", + relative_path(path, sm_gitdir, &rel_path)); + strbuf_release(&sb); + strbuf_release(&rel_path); + free(sm_gitdir); + free(path); + free(p); + return 0; +} + +struct submodule_update_clone { + /* index into 'list', the list of submodules to look into for cloning */ + int current; + struct module_list list; + unsigned warn_if_uninitialized : 1; + + /* update parameter passed via commandline */ + struct submodule_update_strategy update; + + /* configuration parameters which are passed on to the children */ + int progress; + int quiet; + int recommend_shallow; + struct string_list references; + const char *depth; + const char *recursive_prefix; + const char *prefix; + + /* Machine-readable status lines to be consumed by git-submodule.sh */ + struct string_list projectlines; + + /* If we want to stop as fast as possible and return an error */ + unsigned quickstop : 1; + + /* failed clones to be retried again */ + const struct cache_entry **failed_clones; + int failed_clones_nr, failed_clones_alloc; +}; +#define SUBMODULE_UPDATE_CLONE_INIT {0, MODULE_LIST_INIT, 0, \ + SUBMODULE_UPDATE_STRATEGY_INIT, 0, 0, -1, STRING_LIST_INIT_DUP, \ + NULL, NULL, NULL, \ + STRING_LIST_INIT_DUP, 0, NULL, 0, 0} + + +static void next_submodule_warn_missing(struct submodule_update_clone *suc, + struct strbuf *out, const char *displaypath) +{ + /* + * Only mention uninitialized submodules when their + * paths have been specified. + */ + if (suc->warn_if_uninitialized) { + strbuf_addf(out, + _("Submodule path '%s' not initialized"), + displaypath); + strbuf_addch(out, '\n'); + strbuf_addstr(out, + _("Maybe you want to use 'update --init'?")); + strbuf_addch(out, '\n'); + } +} + +/** + * Determine whether 'ce' needs to be cloned. If so, prepare the 'child' to + * run the clone. Returns 1 if 'ce' needs to be cloned, 0 otherwise. + */ +static int prepare_to_clone_next_submodule(const struct cache_entry *ce, + struct child_process *child, + struct submodule_update_clone *suc, + struct strbuf *out) +{ + const struct submodule *sub = NULL; + struct strbuf displaypath_sb = STRBUF_INIT; + struct strbuf sb = STRBUF_INIT; + const char *displaypath = NULL; + char *url = NULL; + int needs_cloning = 0; + + if (ce_stage(ce)) { + if (suc->recursive_prefix) + strbuf_addf(&sb, "%s/%s", suc->recursive_prefix, ce->name); + else + strbuf_addstr(&sb, ce->name); + strbuf_addf(out, _("Skipping unmerged submodule %s"), sb.buf); + strbuf_addch(out, '\n'); + goto cleanup; + } + + sub = submodule_from_path(null_sha1, ce->name); + + if (suc->recursive_prefix) + displaypath = relative_path(suc->recursive_prefix, + ce->name, &displaypath_sb); + else + displaypath = ce->name; + + if (!sub) { + next_submodule_warn_missing(suc, out, displaypath); + goto cleanup; + } + + if (suc->update.type == SM_UPDATE_NONE + || (suc->update.type == SM_UPDATE_UNSPECIFIED + && sub->update_strategy.type == SM_UPDATE_NONE)) { + strbuf_addf(out, _("Skipping submodule '%s'"), displaypath); + strbuf_addch(out, '\n'); + goto cleanup; + } + + /* + * Looking up the url in .git/config. + * We must not fall back to .gitmodules as we only want + * to process configured submodules. + */ + strbuf_reset(&sb); + strbuf_addf(&sb, "submodule.%s.url", sub->name); + git_config_get_string(sb.buf, &url); + if (!url) { + next_submodule_warn_missing(suc, out, displaypath); + goto cleanup; + } + + strbuf_reset(&sb); + strbuf_addf(&sb, "%s/.git", ce->name); + needs_cloning = !file_exists(sb.buf); + + strbuf_reset(&sb); + strbuf_addf(&sb, "%06o %s %d %d\t%s\n", ce->ce_mode, + oid_to_hex(&ce->oid), ce_stage(ce), + needs_cloning, ce->name); + string_list_append(&suc->projectlines, sb.buf); + + if (!needs_cloning) + goto cleanup; + + child->git_cmd = 1; + child->no_stdin = 1; + child->stdout_to_stderr = 1; + child->err = -1; + argv_array_push(&child->args, "submodule--helper"); + argv_array_push(&child->args, "clone"); + if (suc->progress) + argv_array_push(&child->args, "--progress"); + if (suc->quiet) + argv_array_push(&child->args, "--quiet"); + if (suc->prefix) + argv_array_pushl(&child->args, "--prefix", suc->prefix, NULL); + if (suc->recommend_shallow && sub->recommend_shallow == 1) + argv_array_push(&child->args, "--depth=1"); + argv_array_pushl(&child->args, "--path", sub->path, NULL); + argv_array_pushl(&child->args, "--name", sub->name, NULL); + argv_array_pushl(&child->args, "--url", url, NULL); + if (suc->references.nr) { + struct string_list_item *item; + for_each_string_list_item(item, &suc->references) + argv_array_pushl(&child->args, "--reference", item->string, NULL); + } + if (suc->depth) + argv_array_push(&child->args, suc->depth); + +cleanup: + free(url); + strbuf_reset(&displaypath_sb); + strbuf_reset(&sb); + + return needs_cloning; +} + +static int update_clone_get_next_task(struct child_process *child, + struct strbuf *err, + void *suc_cb, + void **idx_task_cb) +{ + struct submodule_update_clone *suc = suc_cb; + const struct cache_entry *ce; + int index; + + for (; suc->current < suc->list.nr; suc->current++) { + ce = suc->list.entries[suc->current]; + if (prepare_to_clone_next_submodule(ce, child, suc, err)) { + int *p = xmalloc(sizeof(*p)); + *p = suc->current; + *idx_task_cb = p; + suc->current++; + return 1; + } + } + + /* + * The loop above tried cloning each submodule once, now try the + * stragglers again, which we can imagine as an extension of the + * entry list. + */ + index = suc->current - suc->list.nr; + if (index < suc->failed_clones_nr) { + int *p; + ce = suc->failed_clones[index]; + if (!prepare_to_clone_next_submodule(ce, child, suc, err)) { + suc->current ++; + strbuf_addstr(err, "BUG: submodule considered for " + "cloning, doesn't need cloning " + "any more?\n"); + return 0; + } + p = xmalloc(sizeof(*p)); + *p = suc->current; + *idx_task_cb = p; + suc->current ++; + return 1; + } + + return 0; +} + +static int update_clone_start_failure(struct strbuf *err, + void *suc_cb, + void *idx_task_cb) +{ + struct submodule_update_clone *suc = suc_cb; + suc->quickstop = 1; + return 1; +} + +static int update_clone_task_finished(int result, + struct strbuf *err, + void *suc_cb, + void *idx_task_cb) +{ + const struct cache_entry *ce; + struct submodule_update_clone *suc = suc_cb; + + int *idxP = *(int**)idx_task_cb; + int idx = *idxP; + free(idxP); + + if (!result) + return 0; + + if (idx < suc->list.nr) { + ce = suc->list.entries[idx]; + strbuf_addf(err, _("Failed to clone '%s'. Retry scheduled"), + ce->name); + strbuf_addch(err, '\n'); + ALLOC_GROW(suc->failed_clones, + suc->failed_clones_nr + 1, + suc->failed_clones_alloc); + suc->failed_clones[suc->failed_clones_nr++] = ce; + return 0; + } else { + idx -= suc->list.nr; + ce = suc->failed_clones[idx]; + strbuf_addf(err, _("Failed to clone '%s' a second time, aborting"), + ce->name); + strbuf_addch(err, '\n'); + suc->quickstop = 1; + return 1; + } + + return 0; +} + +static int update_clone(int argc, const char **argv, const char *prefix) +{ + const char *update = NULL; + int max_jobs = -1; + struct string_list_item *item; + struct pathspec pathspec; + struct submodule_update_clone suc = SUBMODULE_UPDATE_CLONE_INIT; + + struct option module_update_clone_options[] = { + OPT_STRING(0, "prefix", &prefix, + N_("path"), + N_("path into the working tree")), + OPT_STRING(0, "recursive-prefix", &suc.recursive_prefix, + N_("path"), + N_("path into the working tree, across nested " + "submodule boundaries")), + OPT_STRING(0, "update", &update, + N_("string"), + N_("rebase, merge, checkout or none")), + OPT_STRING_LIST(0, "reference", &suc.references, N_("repo"), + N_("reference repository")), + OPT_STRING(0, "depth", &suc.depth, "<depth>", + N_("Create a shallow clone truncated to the " + "specified number of revisions")), + OPT_INTEGER('j', "jobs", &max_jobs, + N_("parallel jobs")), + OPT_BOOL(0, "recommend-shallow", &suc.recommend_shallow, + N_("whether the initial clone should follow the shallow recommendation")), + OPT__QUIET(&suc.quiet, N_("don't print cloning progress")), + OPT_BOOL(0, "progress", &suc.progress, + N_("force cloning progress")), + OPT_END() + }; + + const char *const git_submodule_helper_usage[] = { + N_("git submodule--helper update_clone [--prefix=<path>] [<path>...]"), + NULL + }; + suc.prefix = prefix; + + argc = parse_options(argc, argv, prefix, module_update_clone_options, + git_submodule_helper_usage, 0); + + if (update) + if (parse_submodule_update_strategy(update, &suc.update) < 0) + die(_("bad value for update parameter")); + + if (module_list_compute(argc, argv, prefix, &pathspec, &suc.list) < 0) + return 1; + + if (pathspec.nr) + suc.warn_if_uninitialized = 1; + + /* Overlay the parsed .gitmodules file with .git/config */ + gitmodules_config(); + git_config(submodule_config, NULL); + + if (max_jobs < 0) + max_jobs = parallel_submodules(); + + run_processes_parallel(max_jobs, + update_clone_get_next_task, + update_clone_start_failure, + update_clone_task_finished, + &suc); + + /* + * We saved the output and put it out all at once now. + * That means: + * - the listener does not have to interleave their (checkout) + * work with our fetching. The writes involved in a + * checkout involve more straightforward sequential I/O. + * - the listener can avoid doing any work if fetching failed. + */ + if (suc.quickstop) + return 1; + + for_each_string_list_item(item, &suc.projectlines) + utf8_fprintf(stdout, "%s", item->string); + + return 0; +} + +static int resolve_relative_path(int argc, const char **argv, const char *prefix) +{ + struct strbuf sb = STRBUF_INIT; + if (argc != 3) + die("submodule--helper relative-path takes exactly 2 arguments, got %d", argc); + + printf("%s", relative_path(argv[1], argv[2], &sb)); + strbuf_release(&sb); + return 0; +} + +static const char *remote_submodule_branch(const char *path) +{ + const struct submodule *sub; + gitmodules_config(); + git_config(submodule_config, NULL); + + sub = submodule_from_path(null_sha1, path); + if (!sub) + return NULL; + + if (!sub->branch) + return "master"; + + if (!strcmp(sub->branch, ".")) { + unsigned char sha1[20]; + const char *refname = resolve_ref_unsafe("HEAD", 0, sha1, NULL); + + if (!refname) + die(_("No such ref: %s"), "HEAD"); + + /* detached HEAD */ + if (!strcmp(refname, "HEAD")) + die(_("Submodule (%s) branch configured to inherit " + "branch from superproject, but the superproject " + "is not on any branch"), sub->name); + + if (!skip_prefix(refname, "refs/heads/", &refname)) + die(_("Expecting a full ref name, got %s"), refname); + return refname; + } + + return sub->branch; +} + +static int resolve_remote_submodule_branch(int argc, const char **argv, + const char *prefix) +{ + const char *ret; + struct strbuf sb = STRBUF_INIT; + if (argc != 2) + die("submodule--helper remote-branch takes exactly one arguments, got %d", argc); + + ret = remote_submodule_branch(argv[1]); + if (!ret) + die("submodule %s doesn't exist", argv[1]); + + printf("%s", ret); + strbuf_release(&sb); + return 0; +} + +struct cmd_struct { + const char *cmd; + int (*fn)(int, const char **, const char *); +}; + +static struct cmd_struct commands[] = { + {"list", module_list}, + {"name", module_name}, + {"clone", module_clone}, + {"update-clone", update_clone}, + {"relative-path", resolve_relative_path}, + {"resolve-relative-url", resolve_relative_url}, + {"resolve-relative-url-test", resolve_relative_url_test}, + {"init", module_init}, + {"remote-branch", resolve_remote_submodule_branch} +}; + +int cmd_submodule__helper(int argc, const char **argv, const char *prefix) +{ + int i; + if (argc < 2) + die(_("submodule--helper subcommand must be " + "called with a subcommand")); + + for (i = 0; i < ARRAY_SIZE(commands); i++) + if (!strcmp(argv[1], commands[i].cmd)) + return commands[i].fn(argc - 1, argv + 1, prefix); + + die(_("'%s' is not a valid submodule--helper " + "subcommand"), argv[1]); +} diff --git a/builtin/symbolic-ref.c b/builtin/symbolic-ref.c index ce0fde705c..96eed94468 100644 --- a/builtin/symbolic-ref.c +++ b/builtin/symbolic-ref.c @@ -56,6 +56,8 @@ int cmd_symbolic_ref(int argc, const char **argv, const char *prefix) ret = check_symref(argv[0], 1, 0, 0); if (ret) die("Cannot delete %s, not a symbolic ref", argv[0]); + if (!strcmp(argv[0], "HEAD")) + die("deleting '%s' is not allowed", argv[0]); return delete_ref(argv[0], NULL, REF_NODEREF); } @@ -67,7 +69,7 @@ int cmd_symbolic_ref(int argc, const char **argv, const char *prefix) if (!strcmp(argv[0], "HEAD") && !starts_with(argv[1], "refs/")) die("Refusing to point HEAD outside of refs/"); - create_symref(argv[0], argv[1], msg); + ret = !!create_symref(argv[0], argv[1], msg); break; default: usage_with_options(git_symbolic_ref_usage, options); diff --git a/builtin/tag.c b/builtin/tag.c index 5f6cdc5a03..50e4ae5678 100644 --- a/builtin/tag.c +++ b/builtin/tag.c @@ -17,271 +17,51 @@ #include "gpg-interface.h" #include "sha1-array.h" #include "column.h" +#include "ref-filter.h" static const char * const git_tag_usage[] = { N_("git tag [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>] <tagname> [<head>]"), N_("git tag -d <tagname>..."), N_("git tag -l [-n[<num>]] [--contains <commit>] [--points-at <object>]" - "\n\t\t[<pattern>...]"), + "\n\t\t[--format=<format>] [--[no-]merged [<commit>]] [<pattern>...]"), N_("git tag -v <tagname>..."), NULL }; -#define STRCMP_SORT 0 /* must be zero */ -#define VERCMP_SORT 1 -#define SORT_MASK 0x7fff -#define REVERSE_SORT 0x8000 - -static int tag_sort; - -struct tag_filter { - const char **patterns; - int lines; - int sort; - struct string_list tags; - struct commit_list *with_commit; -}; - -static struct sha1_array points_at; static unsigned int colopts; +static int force_sign_annotate; -static int match_pattern(const char **patterns, const char *ref) -{ - /* no pattern means match everything */ - if (!*patterns) - return 1; - for (; *patterns; patterns++) - if (!wildmatch(*patterns, ref, 0, NULL)) - return 1; - return 0; -} - -static const unsigned char *match_points_at(const char *refname, - const unsigned char *sha1) -{ - const unsigned char *tagged_sha1 = NULL; - struct object *obj; - - if (sha1_array_lookup(&points_at, sha1) >= 0) - return sha1; - obj = parse_object(sha1); - if (!obj) - die(_("malformed object at '%s'"), refname); - if (obj->type == OBJ_TAG) - tagged_sha1 = ((struct tag *)obj)->tagged->sha1; - if (tagged_sha1 && sha1_array_lookup(&points_at, tagged_sha1) >= 0) - return tagged_sha1; - return NULL; -} - -static int in_commit_list(const struct commit_list *want, struct commit *c) -{ - for (; want; want = want->next) - if (!hashcmp(want->item->object.sha1, c->object.sha1)) - return 1; - return 0; -} - -enum contains_result { - CONTAINS_UNKNOWN = -1, - CONTAINS_NO = 0, - CONTAINS_YES = 1 -}; - -/* - * Test whether the candidate or one of its parents is contained in the list. - * Do not recurse to find out, though, but return -1 if inconclusive. - */ -static enum contains_result contains_test(struct commit *candidate, - const struct commit_list *want) -{ - /* 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)) { - candidate->object.flags |= TMP_MARK; - return 1; - } - - if (parse_commit(candidate) < 0) - return 0; - - return -1; -} - -/* - * Mimicking the real stack, this stack lives on the heap, avoiding stack - * overflows. - * - * At each recursion step, the stack items points to the commits whose - * ancestors are to be inspected. - */ -struct stack { - int nr, alloc; - struct stack_entry { - struct commit *commit; - struct commit_list *parents; - } *stack; -}; - -static void push_to_stack(struct commit *candidate, struct stack *stack) -{ - int index = stack->nr++; - ALLOC_GROW(stack->stack, stack->nr, stack->alloc); - stack->stack[index].commit = candidate; - stack->stack[index].parents = candidate->parents; -} - -static enum contains_result contains(struct commit *candidate, - const struct commit_list *want) -{ - struct stack stack = { 0, 0, NULL }; - int result = contains_test(candidate, want); - - if (result != CONTAINS_UNKNOWN) - return result; - - push_to_stack(candidate, &stack); - while (stack.nr) { - struct stack_entry *entry = &stack.stack[stack.nr - 1]; - struct commit *commit = entry->commit; - struct commit_list *parents = entry->parents; - - if (!parents) { - commit->object.flags |= UNINTERESTING; - stack.nr--; - } - /* - * If we just popped the stack, parents->item has been marked, - * therefore contains_test will return a meaningful 0 or 1. - */ - else switch (contains_test(parents->item, want)) { - case CONTAINS_YES: - commit->object.flags |= TMP_MARK; - stack.nr--; - break; - case CONTAINS_NO: - entry->parents = parents->next; - break; - case CONTAINS_UNKNOWN: - push_to_stack(parents->item, &stack); - break; - } - } - free(stack.stack); - return contains_test(candidate, want); -} - -static void show_tag_lines(const struct object_id *oid, int lines) +static int list_tags(struct ref_filter *filter, struct ref_sorting *sorting, const char *format) { + struct ref_array array; + char *to_free = NULL; int i; - unsigned long size; - enum object_type type; - char *buf, *sp, *eol; - size_t len; - buf = read_sha1_file(oid->hash, &type, &size); - if (!buf) - die_errno("unable to read object %s", oid_to_hex(oid)); - if (type != OBJ_COMMIT && type != OBJ_TAG) - goto free_return; - if (!size) - die("an empty %s object %s?", - typename(type), oid_to_hex(oid)); + memset(&array, 0, sizeof(array)); - /* skip header */ - sp = strstr(buf, "\n\n"); - if (!sp) - goto free_return; - - /* only take up to "lines" lines, and strip the signature from a tag */ - if (type == OBJ_TAG) - size = parse_signature(buf, size); - for (i = 0, sp += 2; i < lines && sp < buf + size; i++) { - if (i) - printf("\n "); - eol = memchr(sp, '\n', size - (sp - buf)); - len = eol ? eol - sp : size - (sp - buf); - fwrite(sp, len, 1, stdout); - if (!eol) - break; - sp = eol + 1; - } -free_return: - free(buf); -} - -static int show_reference(const char *refname, const struct object_id *oid, - int flag, void *cb_data) -{ - struct tag_filter *filter = cb_data; - - if (match_pattern(filter->patterns, refname)) { - if (filter->with_commit) { - struct commit *commit; + if (filter->lines == -1) + filter->lines = 0; - commit = lookup_commit_reference_gently(oid->hash, 1); - if (!commit) - return 0; - if (!contains(commit, filter->with_commit)) - return 0; - } - - if (points_at.nr && !match_points_at(refname, oid->hash)) - return 0; - - if (!filter->lines) { - if (filter->sort) - string_list_append(&filter->tags, refname); - else - printf("%s\n", refname); - return 0; - } - printf("%-15s ", refname); - show_tag_lines(oid, filter->lines); - putchar('\n'); + if (!format) { + if (filter->lines) { + to_free = xstrfmt("%s %%(contents:lines=%d)", + "%(align:15)%(refname:strip=2)%(end)", + filter->lines); + format = to_free; + } else + format = "%(refname:strip=2)"; } - return 0; -} + verify_ref_format(format); + filter->with_commit_tag_algo = 1; + filter_refs(&array, filter, FILTER_REFS_TAGS); + ref_array_sort(sorting, &array); -static int sort_by_version(const void *a_, const void *b_) -{ - const struct string_list_item *a = a_; - const struct string_list_item *b = b_; - return versioncmp(a->string, b->string); -} + for (i = 0; i < array.nr; i++) + show_ref_array_item(array.items[i], format, 0); + ref_array_clear(&array); + free(to_free); -static int list_tags(const char **patterns, int lines, - struct commit_list *with_commit, int sort) -{ - struct tag_filter filter; - - filter.patterns = patterns; - filter.lines = lines; - filter.sort = sort; - filter.with_commit = with_commit; - memset(&filter.tags, 0, sizeof(filter.tags)); - filter.tags.strdup_strings = 1; - - for_each_tag_ref(show_reference, (void *)&filter); - if (sort) { - int i; - if ((sort & SORT_MASK) == VERCMP_SORT) - qsort(filter.tags.items, filter.tags.nr, - sizeof(struct string_list_item), sort_by_version); - if (sort & REVERSE_SORT) - for (i = filter.tags.nr - 1; i >= 0; i--) - printf("%s\n", filter.tags.items[i].string); - else - for (i = 0; i < filter.tags.nr; i++) - printf("%s\n", filter.tags.items[i].string); - string_list_clear(&filter.tags, 0); - } return 0; } @@ -325,13 +105,7 @@ static int delete_tag(const char *name, const char *ref, static int verify_tag(const char *name, const char *ref, const unsigned char *sha1) { - const char *argv_verify_tag[] = {"verify-tag", - "-v", "SHA1_HEX", NULL}; - 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 0; + return gpg_verify_tag(sha1, name, GPG_VERIFY_VERBOSE); } static int do_sign(struct strbuf *buffer) @@ -348,35 +122,26 @@ static const char tag_template_nocleanup[] = "Lines starting with '%c' will be kept; you may remove them" " yourself if you want to.\n"); -/* - * Parse a sort string, and return 0 if parsed successfully. Will return - * non-zero when the sort string does not parse into a known type. If var is - * given, the error message becomes a warning and includes information about - * the configuration value. - */ -static int parse_sort_string(const char *var, const char *arg, int *sort) +/* Parse arg given and add it the ref_sorting array */ +static int parse_sorting_string(const char *arg, struct ref_sorting **sorting_tail) { - int type = 0, flags = 0; + struct ref_sorting *s; + int len; - if (skip_prefix(arg, "-", &arg)) - flags |= REVERSE_SORT; + s = xcalloc(1, sizeof(*s)); + s->next = *sorting_tail; + *sorting_tail = s; - if (skip_prefix(arg, "version:", &arg) || skip_prefix(arg, "v:", &arg)) - type = VERCMP_SORT; - else - type = STRCMP_SORT; - - if (strcmp(arg, "refname")) { - if (!var) - return error(_("unsupported sort specification '%s'"), arg); - else { - warning(_("unsupported sort specification '%s' in variable '%s'"), - var, arg); - return -1; - } + if (*arg == '-') { + s->reverse = 1; + arg++; } + if (skip_prefix(arg, "version:", &arg) || + skip_prefix(arg, "v:", &arg)) + s->version = 1; - *sort = (type | flags); + len = strlen(arg); + s->atom = parse_ref_filter_atom(arg, arg+len); return 0; } @@ -384,17 +149,23 @@ static int parse_sort_string(const char *var, const char *arg, int *sort) static int git_tag_config(const char *var, const char *value, void *cb) { int status; + struct ref_sorting **sorting_tail = (struct ref_sorting **)cb; if (!strcmp(var, "tag.sort")) { if (!value) return config_error_nonbool(var); - parse_sort_string(var, value, &tag_sort); + parse_sorting_string(value, sorting_tail); return 0; } status = git_gpg_config(var, value, cb); if (status) return status; + if (!strcmp(var, "tag.forcesignannotated")) { + force_sign_annotate = git_config_bool(var, value); + return 0; + } + if (starts_with(var, "column.")) return git_column_config(var, value, "tag", &colopts); return git_default_config(var, value, cb); @@ -498,7 +269,7 @@ static void create_tag(const unsigned char *object, const char *tag, } if (opt->cleanup_mode != CLEANUP_NONE) - stripspace(buf, opt->cleanup_mode == CLEANUP_ALL); + strbuf_stripspace(buf, opt->cleanup_mode == CLEANUP_ALL); if (!opt->message_given && !buf->len) die(_("no tag message?")); @@ -546,30 +317,6 @@ static int strbuf_check_tag_ref(struct strbuf *sb, const char *name) return check_refname_format(sb->buf, 0); } -static int parse_opt_points_at(const struct option *opt __attribute__((unused)), - const char *arg, int unset) -{ - unsigned char sha1[20]; - - if (unset) { - sha1_array_clear(&points_at); - return 0; - } - if (!arg) - return error(_("switch 'points-at' requires an object")); - if (get_sha1(arg, sha1)) - return error(_("malformed object name '%s'"), arg); - sha1_array_append(&points_at, sha1); - return 0; -} - -static int parse_opt_sort(const struct option *opt, const char *arg, int unset) -{ - int *sort = opt->value; - - return parse_sort_string(NULL, arg, sort); -} - int cmd_tag(int argc, const char **argv, const char *prefix) { struct strbuf buf = STRBUF_INIT; @@ -578,16 +325,19 @@ int cmd_tag(int argc, const char **argv, const char *prefix) const char *object_ref, *tag; struct create_tag_options opt; char *cleanup_arg = NULL; - int annotate = 0, force = 0, lines = -1; - int cmdmode = 0; + int create_reflog = 0; + int annotate = 0, force = 0; + int cmdmode = 0, create_tag_object = 0; const char *msgfile = NULL, *keyid = NULL; struct msg_arg msg = { 0, STRBUF_INIT }; - struct commit_list *with_commit = NULL; struct ref_transaction *transaction; struct strbuf err = STRBUF_INIT; + struct ref_filter filter; + static struct ref_sorting *sorting = NULL, **sorting_tail = &sorting; + const char *format = NULL; struct option options[] = { OPT_CMDMODE('l', "list", &cmdmode, N_("list tag names"), 'l'), - { OPTION_INTEGER, 'n', NULL, &lines, N_("n"), + { OPTION_INTEGER, 'n', NULL, &filter.lines, N_("n"), N_("print <n> lines of each tag message"), PARSE_OPT_OPTARG, NULL, 1 }, OPT_CMDMODE('d', "delete", &cmdmode, N_("delete tags"), 'd'), @@ -605,35 +355,29 @@ int cmd_tag(int argc, const char **argv, const char *prefix) OPT_STRING('u', "local-user", &keyid, N_("key-id"), N_("use another key to sign the tag")), OPT__FORCE(&force, N_("replace the tag if exists")), + OPT_BOOL(0, "create-reflog", &create_reflog, N_("create a reflog")), OPT_GROUP(N_("Tag listing options")), OPT_COLUMN(0, "column", &colopts, N_("show tag list in columns")), + OPT_CONTAINS(&filter.with_commit, N_("print only tags that contain the commit")), + OPT_WITH(&filter.with_commit, N_("print only tags that contain the commit")), + OPT_MERGED(&filter, N_("print only tags that are merged")), + OPT_NO_MERGED(&filter, N_("print only tags that are not merged")), + OPT_CALLBACK(0 , "sort", sorting_tail, N_("key"), + N_("field name to sort on"), &parse_opt_ref_sorting), { - OPTION_CALLBACK, 0, "sort", &tag_sort, N_("type"), N_("sort tags"), - PARSE_OPT_NONEG, parse_opt_sort - }, - { - OPTION_CALLBACK, 0, "contains", &with_commit, N_("commit"), - N_("print only tags that contain the commit"), - PARSE_OPT_LASTARG_DEFAULT, - parse_opt_with_commit, (intptr_t)"HEAD", - }, - { - OPTION_CALLBACK, 0, "with", &with_commit, N_("commit"), - N_("print only tags that contain the commit"), - PARSE_OPT_HIDDEN | PARSE_OPT_LASTARG_DEFAULT, - parse_opt_with_commit, (intptr_t)"HEAD", - }, - { - OPTION_CALLBACK, 0, "points-at", NULL, N_("object"), - N_("print only tags of the object"), 0, parse_opt_points_at + OPTION_CALLBACK, 0, "points-at", &filter.points_at, N_("object"), + N_("print only tags of the object"), 0, parse_opt_object_name }, + OPT_STRING( 0 , "format", &format, N_("format"), N_("format to use for the output")), OPT_END() }; - git_config(git_tag_config, NULL); + git_config(git_tag_config, sorting_tail); memset(&opt, 0, sizeof(opt)); + memset(&filter, 0, sizeof(filter)); + filter.lines = -1; argc = parse_options(argc, argv, prefix, options, git_tag_usage, 0); @@ -641,20 +385,22 @@ int cmd_tag(int argc, const char **argv, const char *prefix) opt.sign = 1; set_signing_key(keyid); } - if (opt.sign) - annotate = 1; + create_tag_object = (opt.sign || annotate || msg.given || msgfile); + if (argc == 0 && !cmdmode) cmdmode = 'l'; - if ((annotate || msg.given || msgfile || force) && (cmdmode != 0)) + if ((create_tag_object || force) && (cmdmode != 0)) usage_with_options(git_tag_usage, options); finalize_colopts(&colopts, -1); - if (cmdmode == 'l' && lines != -1) { + if (cmdmode == 'l' && filter.lines != -1) { if (explicitly_enable_column(colopts)) die(_("--column and -n are incompatible")); colopts = 0; } + if (!sorting) + sorting = ref_default_sorting(); if (cmdmode == 'l') { int ret; if (column_active(colopts)) { @@ -663,19 +409,20 @@ int cmd_tag(int argc, const char **argv, const char *prefix) copts.padding = 2; run_column_filter(colopts, &copts); } - if (lines != -1 && tag_sort) - die(_("--sort and -n are incompatible")); - ret = list_tags(argv, lines == -1 ? 0 : lines, with_commit, tag_sort); + filter.name_patterns = argv; + ret = list_tags(&filter, sorting, format); if (column_active(colopts)) stop_column_filter(); return ret; } - if (lines != -1) + if (filter.lines != -1) die(_("-n option is only allowed with -l.")); - if (with_commit) + if (filter.with_commit) die(_("--contains option is only allowed with -l.")); - if (points_at.nr) + if (filter.points_at.nr) die(_("--points-at option is only allowed with -l.")); + if (filter.merge_commit) + die(_("--merged and --no-merged option are only allowed with -l")); if (cmdmode == 'd') return for_each_tag_name(argv, delete_tag); if (cmdmode == 'v') @@ -684,7 +431,6 @@ 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.")); - annotate = 1; if (msg.given) strbuf_addbuf(&buf, &(msg.buf)); else { @@ -727,13 +473,17 @@ int cmd_tag(int argc, const char **argv, const char *prefix) else die(_("Invalid cleanup mode %s"), cleanup_arg); - if (annotate) + if (create_tag_object) { + if (force_sign_annotate && !annotate) + opt.sign = 1; create_tag(object, tag, &buf, &opt, prev, object); + } transaction = ref_transaction_begin(&err); if (!transaction || ref_transaction_update(transaction, ref.buf, object, prev, - 0, NULL, &err) || + create_reflog ? REF_FORCE_CREATE_REFLOG : 0, + NULL, &err) || ref_transaction_commit(transaction, &err)) die("%s", err.buf); ref_transaction_free(transaction); diff --git a/builtin/unpack-file.c b/builtin/unpack-file.c index 19200291a2..6fc6bcdf7f 100644 --- a/builtin/unpack-file.c +++ b/builtin/unpack-file.c @@ -12,7 +12,7 @@ static char *create_temp_file(unsigned char *sha1) if (!buf || type != OBJ_BLOB) die("unable to read blob object %s", sha1_to_hex(sha1)); - strcpy(path, ".merge_file_XXXXXX"); + xsnprintf(path, sizeof(path), ".merge_file_XXXXXX"); fd = xmkstemp(path); if (write_in_full(fd, buf, size) != size) die_errno("unable to write temp-file"); diff --git a/builtin/unpack-objects.c b/builtin/unpack-objects.c index ac6667242c..4532aa0831 100644 --- a/builtin/unpack-objects.c +++ b/builtin/unpack-objects.c @@ -13,13 +13,15 @@ #include "fsck.h" static int dry_run, quiet, recover, has_errors, strict; -static const char unpack_usage[] = "git unpack-objects [-n] [-q] [-r] [--strict] < pack-file"; +static const char unpack_usage[] = "git unpack-objects [-n] [-q] [-r] [--strict]"; /* We always read in 4kB chunks. */ static unsigned char buffer[4096]; static unsigned int offset, len; static off_t consumed_bytes; +static off_t max_input_size; static git_SHA_CTX ctx; +static struct fsck_options fsck_options = FSCK_OPTIONS_STRICT; /* * When running under --strict mode, objects whose reachability are @@ -45,7 +47,7 @@ static void add_object_buffer(struct object *object, char *buffer, unsigned long obj->buffer = buffer; obj->size = size; if (add_decoration(&obj_decorate, object, obj)) - die("object %s tried to add buffer twice!", sha1_to_hex(object->sha1)); + die("object %s tried to add buffer twice!", oid_to_hex(&object->oid)); } /* @@ -86,6 +88,8 @@ static void use(int bytes) if (signed_add_overflows(consumed_bytes, bytes)) die("pack too large for current definition of off_t"); consumed_bytes += bytes; + if (max_input_size && consumed_bytes > max_input_size) + die(_("pack exceeds maximum allowed size")); } static void *get_data(unsigned long size) @@ -169,7 +173,7 @@ static void write_cached_object(struct object *obj, struct obj_buffer *obj_buf) unsigned char sha1[20]; if (write_sha1_file(obj_buf->buffer, obj_buf->size, typename(obj->type), sha1) < 0) - die("failed to write object %s", sha1_to_hex(obj->sha1)); + die("failed to write object %s", oid_to_hex(&obj->oid)); obj->flags |= FLAG_WRITTEN; } @@ -178,7 +182,7 @@ static void write_cached_object(struct object *obj, struct obj_buffer *obj_buf) * that have reachability requirements and calls this function. * Verify its reachability and validity recursively and write it out. */ -static int check_object(struct object *obj, int type, void *data) +static int check_object(struct object *obj, int type, void *data, struct fsck_options *options) { struct obj_buffer *obj_buf; @@ -193,7 +197,7 @@ static int check_object(struct object *obj, int type, void *data) if (!(obj->flags & FLAG_OPEN)) { unsigned long size; - int type = sha1_object_info(obj->sha1, &size); + int type = sha1_object_info(obj->oid.hash, &size); if (type != obj->type || type <= 0) die("object of unexpected type"); obj->flags |= FLAG_WRITTEN; @@ -202,12 +206,12 @@ static int check_object(struct object *obj, int type, void *data) obj_buf = lookup_object_buffer(obj); if (!obj_buf) - die("Whoops! Cannot find object '%s'", sha1_to_hex(obj->sha1)); - if (fsck_object(obj, obj_buf->buffer, obj_buf->size, 1, - fsck_error_function)) + die("Whoops! Cannot find object '%s'", oid_to_hex(&obj->oid)); + if (fsck_object(obj, obj_buf->buffer, obj_buf->size, &fsck_options)) die("Error in object"); - if (fsck_walk(obj, check_object, NULL)) - die("Error on reachable objects of %s", sha1_to_hex(obj->sha1)); + fsck_options.walk = check_object; + if (fsck_walk(obj, NULL, &fsck_options)) + die("Error on reachable objects of %s", oid_to_hex(&obj->oid)); write_cached_object(obj, obj_buf); return 0; } @@ -217,7 +221,7 @@ static void write_rest(void) unsigned i; for (i = 0; i < nr_objects; i++) { if (obj_list[i].obj) - check_object(obj_list[i].obj, OBJ_ANY, NULL); + check_object(obj_list[i].obj, OBJ_ANY, NULL, NULL); } } @@ -354,7 +358,7 @@ static void unpack_delta_entry(enum object_type type, unsigned long delta_size, return; /* we are done */ else { /* cannot resolve yet --- queue it */ - hashcpy(obj_list[nr].sha1, null_sha1); + hashclr(obj_list[nr].sha1); add_delta_to_list(nr, base_sha1, 0, delta_data, delta_size); return; } @@ -405,7 +409,7 @@ static void unpack_delta_entry(enum object_type type, unsigned long delta_size, * The delta base object is itself a delta that * has not been resolved yet. */ - hashcpy(obj_list[nr].sha1, null_sha1); + hashclr(obj_list[nr].sha1); add_delta_to_list(nr, null_sha1, base_offset, delta_data, delta_size); return; } @@ -529,6 +533,11 @@ int cmd_unpack_objects(int argc, const char **argv, const char *prefix) strict = 1; continue; } + if (skip_prefix(arg, "--strict=", &arg)) { + strict = 1; + fsck_set_msg_types(&fsck_options, arg); + continue; + } if (starts_with(arg, "--pack_header=")) { struct pack_header *hdr; char *c; @@ -544,6 +553,10 @@ int cmd_unpack_objects(int argc, const char **argv, const char *prefix) len = sizeof(*hdr); continue; } + if (skip_prefix(arg, "--max-input-size=", &arg)) { + max_input_size = strtoumax(arg, NULL, 10); + continue; + } usage(unpack_usage); } diff --git a/builtin/update-index.c b/builtin/update-index.c index 7431938fa6..f3f07e7f1c 100644 --- a/builtin/update-index.c +++ b/builtin/update-index.c @@ -35,6 +35,15 @@ static int mark_skip_worktree_only; #define UNMARK_FLAG 2 static struct strbuf mtime_dir = STRBUF_INIT; +/* Untracked cache mode */ +enum uc_mode { + UC_UNSPECIFIED = -1, + UC_DISABLE = 0, + UC_ENABLE, + UC_TEST, + UC_FORCE +}; + __attribute__((format (printf, 1, 2))) static void report(const char *fmt, ...) { @@ -121,7 +130,7 @@ static int test_if_untracked_cache_is_supported(void) if (!mkdtemp(mtime_dir.buf)) die_errno("Could not make temporary directory"); - fprintf(stderr, _("Testing ")); + fprintf(stderr, _("Testing mtime in '%s' "), xgetcwd()); atexit(remove_test_directory); xstat_mtime_dir(&st); fill_stat_data(&base, &st); @@ -246,7 +255,7 @@ static int process_lstat_error(const char *path, int err) { if (err == ENOENT || err == ENOTDIR) return remove_one_path(path); - return error("lstat(\"%s\"): %s", path, strerror(errno)); + return error("lstat(\"%s\"): %s", path, strerror(err)); } static int add_one_path(const struct cache_entry *old, const char *path, int len, struct stat *st) @@ -266,7 +275,7 @@ static int add_one_path(const struct cache_entry *old, const char *path, int len fill_stat_cache_info(ce, st); ce->ce_mode = ce_mode_from_stat(old, st->st_mode); - if (index_path(ce->sha1, path, st, + if (index_path(ce->oid.hash, path, st, info_only ? 0 : HASH_WRITE_OBJECT)) { free(ce); return -1; @@ -303,7 +312,7 @@ static int add_one_path(const struct cache_entry *old, const char *path, int len */ static int process_directory(const char *path, int len, struct stat *st) { - unsigned char sha1[20]; + struct object_id oid; int pos = cache_name_pos(path, len); /* Exact match: file or existing gitlink */ @@ -312,7 +321,7 @@ static int process_directory(const char *path, int len, struct stat *st) if (S_ISGITLINK(ce->ce_mode)) { /* Do nothing to the index if there is no HEAD! */ - if (resolve_gitlink_ref(path, "HEAD", sha1) < 0) + if (resolve_gitlink_ref(path, "HEAD", oid.hash) < 0) return 0; return add_one_path(ce, path, len, st); @@ -338,7 +347,7 @@ static int process_directory(const char *path, int len, struct stat *st) } /* No match - should we add it as a gitlink? */ - if (!resolve_gitlink_ref(path, "HEAD", sha1)) + if (!resolve_gitlink_ref(path, "HEAD", oid.hash)) return add_one_path(NULL, path, len, st); /* Error out. */ @@ -381,7 +390,7 @@ static int process_path(const char *path) return add_one_path(ce, path, len, &st); } -static int add_cacheinfo(unsigned int mode, const unsigned char *sha1, +static int add_cacheinfo(unsigned int mode, const struct object_id *oid, const char *path, int stage) { int size, len, option; @@ -394,7 +403,7 @@ static int add_cacheinfo(unsigned int mode, const unsigned char *sha1, size = cache_entry_size(len); ce = xcalloc(1, size); - hashcpy(ce->sha1, sha1); + oidcpy(&ce->oid, oid); memcpy(ce->name, path, len); ce->ce_flags = create_ce_flags(stage); ce->ce_namelen = len; @@ -410,30 +419,18 @@ static int add_cacheinfo(unsigned int mode, const unsigned char *sha1, return 0; } -static void chmod_path(int flip, const char *path) +static void chmod_path(char flip, const char *path) { int pos; struct cache_entry *ce; - unsigned int mode; pos = cache_name_pos(path, strlen(path)); if (pos < 0) goto fail; ce = active_cache[pos]; - mode = ce->ce_mode; - if (!S_ISREG(mode)) + if (chmod_cache_entry(ce, flip) < 0) goto fail; - switch (flip) { - case '+': - ce->ce_mode |= 0111; break; - case '-': - ce->ce_mode &= ~0111; break; - default: - goto fail; - } - cache_tree_invalidate_path(&the_index, path); - ce->ce_flags |= CE_UPDATE_IN_BASE; - active_cache_changed |= CE_ENTRY_CHANGED; + report("chmod %cx '%s'", flip, path); return; fail: @@ -468,15 +465,17 @@ static void update_one(const char *path) report("add '%s'", path); } -static void read_index_info(int line_termination) +static void read_index_info(int nul_term_line) { struct strbuf buf = STRBUF_INIT; struct strbuf uq = STRBUF_INIT; + strbuf_getline_fn getline_fn; - while (strbuf_getline(&buf, stdin, line_termination) != EOF) { + getline_fn = nul_term_line ? strbuf_getline_nul : strbuf_getline_lf; + while (getline_fn(&buf, stdin) != EOF) { char *ptr, *tab; char *path_name; - unsigned char sha1[20]; + struct object_id oid; unsigned int mode; unsigned long ul; int stage; @@ -505,7 +504,7 @@ static void read_index_info(int line_termination) mode = ul; tab = strchr(ptr, '\t'); - if (!tab || tab - ptr < 41) + if (!tab || tab - ptr < GIT_SHA1_HEXSZ + 1) goto bad_line; if (tab[-2] == ' ' && '0' <= tab[-1] && tab[-1] <= '3') { @@ -518,11 +517,12 @@ static void read_index_info(int line_termination) ptr = tab + 1; /* point at the head of path */ } - if (get_sha1_hex(tab - 40, sha1) || tab[-41] != ' ') + if (get_oid_hex(tab - GIT_SHA1_HEXSZ, &oid) || + tab[-(GIT_SHA1_HEXSZ + 1)] != ' ') goto bad_line; path_name = ptr; - if (line_termination && path_name[0] == '"') { + if (!nul_term_line && path_name[0] == '"') { strbuf_reset(&uq); if (unquote_c_style(&uq, path_name, NULL)) { die("git update-index: bad quoting of path name"); @@ -546,8 +546,8 @@ static void read_index_info(int line_termination) * ptr[-1] points at tab, * ptr[-41] is at the beginning of sha1 */ - ptr[-42] = ptr[-1] = 0; - if (add_cacheinfo(mode, sha1, path_name, stage)) + ptr[-(GIT_SHA1_HEXSZ + 2)] = ptr[-1] = 0; + if (add_cacheinfo(mode, &oid, path_name, stage)) die("git update-index: unable to update %s", path_name); } @@ -565,19 +565,19 @@ static const char * const update_index_usage[] = { NULL }; -static unsigned char head_sha1[20]; -static unsigned char merge_head_sha1[20]; +static struct object_id head_oid; +static struct object_id merge_head_oid; static struct cache_entry *read_one_ent(const char *which, - unsigned char *ent, const char *path, + struct object_id *ent, const char *path, int namelen, int stage) { unsigned mode; - unsigned char sha1[20]; + struct object_id oid; int size; struct cache_entry *ce; - if (get_tree_entry(ent, path, sha1, &mode)) { + if (get_tree_entry(ent->hash, path, oid.hash, &mode)) { if (which) error("%s: not in %s branch.", path, which); return NULL; @@ -590,7 +590,7 @@ static struct cache_entry *read_one_ent(const char *which, size = cache_entry_size(namelen); ce = xcalloc(1, size); - hashcpy(ce->sha1, sha1); + oidcpy(&ce->oid, &oid); memcpy(ce->name, path, namelen); ce->ce_flags = create_ce_flags(stage); ce->ce_namelen = namelen; @@ -640,14 +640,14 @@ static int unresolve_one(const char *path) * stuff HEAD version in stage #2, * stuff MERGE_HEAD version in stage #3. */ - ce_2 = read_one_ent("our", head_sha1, path, namelen, 2); - ce_3 = read_one_ent("their", merge_head_sha1, path, namelen, 3); + ce_2 = read_one_ent("our", &head_oid, path, namelen, 2); + ce_3 = read_one_ent("their", &merge_head_oid, path, namelen, 3); if (!ce_2 || !ce_3) { ret = -1; goto free_return; } - if (!hashcmp(ce_2->sha1, ce_3->sha1) && + if (!oidcmp(&ce_2->oid, &ce_3->oid) && ce_2->ce_mode == ce_3->ce_mode) { fprintf(stderr, "%s: identical in both, skipping.\n", path); @@ -672,9 +672,9 @@ static int unresolve_one(const char *path) static void read_head_pointers(void) { - if (read_ref("HEAD", head_sha1)) + if (read_ref("HEAD", head_oid.hash)) die("No HEAD -- no initial commit yet?"); - if (read_ref("MERGE_HEAD", merge_head_sha1)) { + if (read_ref("MERGE_HEAD", merge_head_oid.hash)) { fprintf(stderr, "Not in the middle of a merge.\n"); exit(0); } @@ -714,7 +714,7 @@ static int do_reupdate(int ac, const char **av, PATHSPEC_PREFER_CWD, prefix, av + 1); - if (read_ref("HEAD", head_sha1)) + if (read_ref("HEAD", head_oid.hash)) /* If there is no HEAD, that means it is an initial * commit. Update everything in the index. */ @@ -729,10 +729,10 @@ static int do_reupdate(int ac, const char **av, if (ce_stage(ce) || !ce_path_match(ce, &pathspec, NULL)) continue; if (has_head) - old = read_one_ent(NULL, head_sha1, + old = read_one_ent(NULL, &head_oid, ce->name, ce_namelen(ce), 0); if (old && ce->ce_mode == old->ce_mode && - !hashcmp(ce->sha1, old->sha1)) { + !oidcmp(&ce->oid, &old->oid)) { free(old); continue; /* unchanged */ } @@ -748,7 +748,7 @@ static int do_reupdate(int ac, const char **av, if (save_nr != active_nr) goto redo; } - free_pathspec(&pathspec); + clear_pathspec(&pathspec); return 0; } @@ -796,7 +796,7 @@ static int resolve_undo_clear_callback(const struct option *opt, static int parse_new_style_cacheinfo(const char *arg, unsigned int *mode, - unsigned char sha1[], + struct object_id *oid, const char **path) { unsigned long ul; @@ -811,21 +811,21 @@ static int parse_new_style_cacheinfo(const char *arg, return -1; /* not a new-style cacheinfo */ *mode = ul; endp++; - if (get_sha1_hex(endp, sha1) || endp[40] != ',') + if (get_oid_hex(endp, oid) || endp[GIT_SHA1_HEXSZ] != ',') return -1; - *path = endp + 41; + *path = endp + GIT_SHA1_HEXSZ + 1; return 0; } static int cacheinfo_callback(struct parse_opt_ctx_t *ctx, const struct option *opt, int unset) { - unsigned char sha1[20]; + struct object_id oid; unsigned int mode; const char *path; - if (!parse_new_style_cacheinfo(ctx->argv[1], &mode, sha1, &path)) { - if (add_cacheinfo(mode, sha1, path, 0)) + if (!parse_new_style_cacheinfo(ctx->argv[1], &mode, &oid, &path)) { + if (add_cacheinfo(mode, &oid, path, 0)) die("git update-index: --cacheinfo cannot add %s", path); ctx->argv++; ctx->argc--; @@ -834,8 +834,8 @@ static int cacheinfo_callback(struct parse_opt_ctx_t *ctx, if (ctx->argc <= 3) return error("option 'cacheinfo' expects <mode>,<sha1>,<path>"); if (strtoul_ui(*++ctx->argv, 8, &mode) || - get_sha1_hex(*++ctx->argv, sha1) || - add_cacheinfo(mode, sha1, *++ctx->argv, 0)) + get_oid_hex(*++ctx->argv, &oid) || + add_cacheinfo(mode, &oid, *++ctx->argv, 0)) die("git update-index: --cacheinfo cannot add %s", *ctx->argv); ctx->argc -= 3; return 0; @@ -844,12 +844,12 @@ static int cacheinfo_callback(struct parse_opt_ctx_t *ctx, static int stdin_cacheinfo_callback(struct parse_opt_ctx_t *ctx, const struct option *opt, int unset) { - int *line_termination = opt->value; + int *nul_term_line = 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); + read_index_info(*nul_term_line); return 0; } @@ -901,8 +901,8 @@ static int reupdate_callback(struct parse_opt_ctx_t *ctx, int cmd_update_index(int argc, const char **argv, const char *prefix) { - int newfd, entries, has_errors = 0, line_termination = '\n'; - int untracked_cache = -1; + int newfd, entries, has_errors = 0, nul_term_line = 0; + enum uc_mode untracked_cache = UC_UNSPECIFIED; int read_from_stdin = 0; int prefix_length = prefix ? strlen(prefix) : 0; int preferred_index_format = 0; @@ -912,6 +912,7 @@ int cmd_update_index(int argc, const char **argv, const char *prefix) int split_index = -1; struct lock_file *lock_file; struct parse_opt_ctx_t ctx; + strbuf_getline_fn getline_fn; int parseopt_state = PARSE_OPT_UNKNOWN; struct option options[] = { OPT_BIT('q', NULL, &refresh_args.flags, @@ -963,13 +964,13 @@ int cmd_update_index(int argc, const char **argv, const char *prefix) N_("add to index only; do not add content to object database"), 1), OPT_SET_INT(0, "force-remove", &force_remove, N_("remove named paths even if present in worktree"), 1), - OPT_SET_INT('z', NULL, &line_termination, - N_("with --stdin: input lines are terminated by null bytes"), '\0'), + OPT_BOOL('z', NULL, &nul_term_line, + N_("with --stdin: input lines are terminated by null bytes")), {OPTION_LOWLEVEL_CALLBACK, 0, "stdin", &read_from_stdin, NULL, N_("read list of paths to be updated from standard input"), PARSE_OPT_NONEG | PARSE_OPT_NOARG, (parse_opt_cb *) stdin_callback}, - {OPTION_LOWLEVEL_CALLBACK, 0, "index-info", &line_termination, NULL, + {OPTION_LOWLEVEL_CALLBACK, 0, "index-info", &nul_term_line, NULL, N_("add entries from standard input to the index"), PARSE_OPT_NONEG | PARSE_OPT_NOARG, (parse_opt_cb *) stdin_cacheinfo_callback}, @@ -996,8 +997,10 @@ int cmd_update_index(int argc, const char **argv, const char *prefix) N_("enable or disable split index")), OPT_BOOL(0, "untracked-cache", &untracked_cache, N_("enable/disable untracked cache")), + OPT_SET_INT(0, "test-untracked-cache", &untracked_cache, + N_("test if the filesystem supports untracked cache"), UC_TEST), OPT_SET_INT(0, "force-untracked-cache", &untracked_cache, - N_("enable untracked cache without testing the filesystem"), 2), + N_("enable untracked cache without testing the filesystem"), UC_FORCE), OPT_END() }; @@ -1057,6 +1060,8 @@ int cmd_update_index(int argc, const char **argv, const char *prefix) } } argc = parse_options_end(&ctx); + + getline_fn = nul_term_line ? strbuf_getline_nul : strbuf_getline_lf; if (preferred_index_format) { if (preferred_index_format < INDEX_FORMAT_LB || INDEX_FORMAT_UB < preferred_index_format) @@ -1070,16 +1075,17 @@ int cmd_update_index(int argc, const char **argv, const char *prefix) } if (read_from_stdin) { - struct strbuf buf = STRBUF_INIT, nbuf = STRBUF_INIT; + struct strbuf buf = STRBUF_INIT; + struct strbuf unquoted = STRBUF_INIT; setup_work_tree(); - while (strbuf_getline(&buf, stdin, line_termination) != EOF) { + while (getline_fn(&buf, stdin) != EOF) { char *p; - if (line_termination && buf.buf[0] == '"') { - strbuf_reset(&nbuf); - if (unquote_c_style(&nbuf, buf.buf, NULL)) + if (!nul_term_line && buf.buf[0] == '"') { + strbuf_reset(&unquoted); + if (unquote_c_style(&unquoted, buf.buf, NULL)) die("line is badly quoted"); - strbuf_swap(&buf, &nbuf); + strbuf_swap(&buf, &unquoted); } p = prefix_path(prefix, prefix_length, buf.buf); update_one(p); @@ -1087,7 +1093,7 @@ int cmd_update_index(int argc, const char **argv, const char *prefix) chmod_path(set_executable_bit, p); free(p); } - strbuf_release(&nbuf); + strbuf_release(&unquoted); strbuf_release(&buf); } @@ -1104,27 +1110,32 @@ int cmd_update_index(int argc, const char **argv, const char *prefix) the_index.split_index = NULL; the_index.cache_changed |= SOMETHING_CHANGED; } - if (untracked_cache > 0) { - struct untracked_cache *uc; - if (untracked_cache < 2) { - setup_work_tree(); - if (!test_if_untracked_cache_is_supported()) - return 1; - } - if (!the_index.untracked) { - uc = xcalloc(1, sizeof(*uc)); - strbuf_init(&uc->ident, 100); - uc->exclude_per_dir = ".gitignore"; - /* should be the same flags used by git-status */ - uc->dir_flags = DIR_SHOW_OTHER_DIRECTORIES | DIR_HIDE_EMPTY_DIRECTORIES; - the_index.untracked = uc; - } - add_untracked_ident(the_index.untracked); - the_index.cache_changed |= UNTRACKED_CHANGED; - } else if (!untracked_cache && the_index.untracked) { - the_index.untracked = NULL; - the_index.cache_changed |= UNTRACKED_CHANGED; + switch (untracked_cache) { + case UC_UNSPECIFIED: + break; + case UC_DISABLE: + if (git_config_get_untracked_cache() == 1) + warning(_("core.untrackedCache is set to true; " + "remove or change it, if you really want to " + "disable the untracked cache")); + remove_untracked_cache(&the_index); + report(_("Untracked cache disabled")); + break; + case UC_TEST: + setup_work_tree(); + return !test_if_untracked_cache_is_supported(); + case UC_ENABLE: + case UC_FORCE: + if (git_config_get_untracked_cache() == 0) + warning(_("core.untrackedCache is set to false; " + "remove or change it, if you really want to " + "enable the untracked cache")); + add_untracked_cache(&the_index); + report(_("Untracked cache enabled for '%s'"), get_git_work_tree()); + break; + default: + die("BUG: bad untracked_cache value: %d", untracked_cache); } if (active_cache_changed) { diff --git a/builtin/update-ref.c b/builtin/update-ref.c index 3d79a46b03..7f30d3a76f 100644 --- a/builtin/update-ref.c +++ b/builtin/update-ref.c @@ -14,6 +14,7 @@ static const char * const git_update_ref_usage[] = { static char line_termination = '\n'; static int update_flags; +static unsigned create_reflog_flag; static const char *msg; /* @@ -200,7 +201,8 @@ static const char *parse_cmd_update(struct ref_transaction *transaction, if (ref_transaction_update(transaction, refname, new_sha1, have_old ? old_sha1 : NULL, - update_flags, msg, &err)) + update_flags | create_reflog_flag, + msg, &err)) die("%s", err.buf); update_flags = 0; @@ -231,7 +233,8 @@ static const char *parse_cmd_create(struct ref_transaction *transaction, die("create %s: extra input: %s", refname, next); if (ref_transaction_create(transaction, refname, new_sha1, - update_flags, msg, &err)) + update_flags | create_reflog_flag, + msg, &err)) die("%s", err.buf); update_flags = 0; @@ -354,6 +357,7 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix) unsigned char sha1[20], oldsha1[20]; int delete = 0, no_deref = 0, read_stdin = 0, end_null = 0; unsigned int flags = 0; + int create_reflog = 0; struct option options[] = { OPT_STRING( 'm', NULL, &msg, N_("reason"), N_("reason of the update")), OPT_BOOL('d', NULL, &delete, N_("delete the reference")), @@ -361,6 +365,7 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix) N_("update <refname> not the one it points to")), OPT_BOOL('z', NULL, &end_null, N_("stdin has NUL-terminated arguments")), OPT_BOOL( 0 , "stdin", &read_stdin, N_("read updates from stdin")), + OPT_BOOL( 0 , "create-reflog", &create_reflog, N_("create a reflog")), OPT_END(), }; @@ -370,6 +375,8 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix) if (msg && !*msg) die("Refusing to perform update with empty message."); + create_reflog_flag = create_reflog ? REF_FORCE_CREATE_REFLOG : 0; + if (read_stdin) { struct strbuf err = STRBUF_INIT; struct ref_transaction *transaction; @@ -408,15 +415,29 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix) die("%s: not a valid SHA1", value); } - hashclr(oldsha1); /* all-zero hash in case oldval is the empty string */ - if (oldval && *oldval && get_sha1(oldval, oldsha1)) - die("%s: not a valid old SHA1", oldval); + if (oldval) { + if (!*oldval) + /* + * The empty string implies that the reference + * must not already exist: + */ + hashclr(oldsha1); + else if (get_sha1(oldval, oldsha1)) + die("%s: not a valid old SHA1", oldval); + } if (no_deref) flags = REF_NODEREF; if (delete) - return delete_ref(refname, oldval ? oldsha1 : NULL, flags); + /* + * For purposes of backwards compatibility, we treat + * NULL_SHA1 as "don't care" here: + */ + return delete_ref(refname, + (oldval && !is_null_sha1(oldsha1)) ? oldsha1 : NULL, + flags); else return update_ref(msg, refname, sha1, oldval ? oldsha1 : NULL, - flags, UPDATE_REFS_DIE_ON_ERR); + flags | create_reflog_flag, + UPDATE_REFS_DIE_ON_ERR); } diff --git a/builtin/upload-archive.c b/builtin/upload-archive.c index 32ab94cd06..cde06977b7 100644 --- a/builtin/upload-archive.c +++ b/builtin/upload-archive.c @@ -43,21 +43,20 @@ int cmd_upload_archive_writer(int argc, const char **argv, const char *prefix) } /* parse all options sent by the client */ - return write_archive(sent_argv.argc, sent_argv.argv, prefix, 0, NULL, 1); + return write_archive(sent_argv.argc, sent_argv.argv, prefix, NULL, 1); } __attribute__((format (printf, 1, 2))) static void error_clnt(const char *fmt, ...) { - char buf[1024]; + struct strbuf buf = STRBUF_INIT; va_list params; - int len; va_start(params, fmt); - len = vsprintf(buf, fmt, params); + strbuf_vaddf(&buf, fmt, params); va_end(params); - send_sideband(1, 3, buf, len, LARGE_PACKET_MAX); - die("sent error to the client: %s", buf); + send_sideband(1, 3, buf.buf, buf.len, LARGE_PACKET_MAX); + die("sent error to the client: %s", buf.buf); } static ssize_t process_input(int child_fd, int band) @@ -89,11 +88,11 @@ int cmd_upload_archive(int argc, const char **argv, const char *prefix) writer.git_cmd = 1; if (start_command(&writer)) { int err = errno; - packet_write(1, "NACK unable to spawn subprocess\n"); + packet_write_fmt(1, "NACK unable to spawn subprocess\n"); die("upload-archive: %s", strerror(err)); } - packet_write(1, "ACK\n"); + packet_write_fmt(1, "ACK\n"); packet_flush(1); while (1) { @@ -105,8 +104,7 @@ int cmd_upload_archive(int argc, const char **argv, const char *prefix) pfd[1].events = POLLIN; if (poll(pfd, 2, -1) < 0) { if (errno != EINTR) { - error("poll failed resuming: %s", - strerror(errno)); + error_errno("poll failed resuming"); sleep(1); } continue; diff --git a/builtin/verify-commit.c b/builtin/verify-commit.c index ec0c4e3d83..38bedf8f9f 100644 --- a/builtin/verify-commit.c +++ b/builtin/verify-commit.c @@ -18,25 +18,21 @@ static const char * const verify_commit_usage[] = { NULL }; -static int run_gpg_verify(const unsigned char *sha1, const char *buf, unsigned long size, int verbose) +static int run_gpg_verify(const unsigned char *sha1, const char *buf, unsigned long size, unsigned flags) { struct signature_check signature_check; + int ret; memset(&signature_check, 0, sizeof(signature_check)); - check_commit_signature(lookup_commit(sha1), &signature_check); - - if (verbose && signature_check.payload) - fputs(signature_check.payload, stdout); - - if (signature_check.gpg_output) - fputs(signature_check.gpg_output, stderr); + ret = check_commit_signature(lookup_commit(sha1), &signature_check); + print_signature_buffer(&signature_check, flags); signature_check_clear(&signature_check); - return signature_check.result != 'G'; + return ret; } -static int verify_commit(const char *name, int verbose) +static int verify_commit(const char *name, unsigned flags) { enum object_type type; unsigned char sha1[20]; @@ -54,7 +50,7 @@ static int verify_commit(const char *name, int verbose) return error("%s: cannot verify a non-commit object of type %s.", name, typename(type)); - ret = run_gpg_verify(sha1, buf, size, verbose); + ret = run_gpg_verify(sha1, buf, size, flags); free(buf); return ret; @@ -71,8 +67,10 @@ static int git_verify_commit_config(const char *var, const char *value, void *cb int cmd_verify_commit(int argc, const char **argv, const char *prefix) { int i = 1, verbose = 0, had_error = 0; + unsigned flags = 0; const struct option verify_commit_options[] = { OPT__VERBOSE(&verbose, N_("print commit contents")), + OPT_BIT(0, "raw", &flags, N_("print raw gpg status output"), GPG_VERIFY_RAW), OPT_END() }; @@ -83,11 +81,14 @@ int cmd_verify_commit(int argc, const char **argv, const char *prefix) if (argc <= i) usage_with_options(verify_commit_usage, verify_commit_options); + if (verbose) + flags |= GPG_VERIFY_VERBOSE; + /* sometimes the program was terminated because this signal * was received in the process of writing the gpg input: */ signal(SIGPIPE, SIG_IGN); while (i < argc) - if (verify_commit(argv[i++], verbose)) + if (verify_commit(argv[i++], flags)) had_error = 1; return had_error; } diff --git a/builtin/verify-tag.c b/builtin/verify-tag.c index 53c68fce3a..99f8148cf7 100644 --- a/builtin/verify-tag.c +++ b/builtin/verify-tag.c @@ -18,46 +18,6 @@ static const char * const verify_tag_usage[] = { NULL }; -static int run_gpg_verify(const char *buf, unsigned long size, int verbose) -{ - int len; - - len = parse_signature(buf, size); - if (verbose) - write_in_full(1, buf, len); - - if (size == len) - return error("no signature found"); - - return verify_signed_buffer(buf, len, buf + len, size - len, NULL, NULL); -} - -static int verify_tag(const char *name, int verbose) -{ - enum object_type type; - unsigned char sha1[20]; - char *buf; - unsigned long size; - int ret; - - if (get_sha1(name, sha1)) - return error("tag '%s' not found.", name); - - type = sha1_object_info(sha1, NULL); - if (type != OBJ_TAG) - return error("%s: cannot verify a non-tag object of type %s.", - name, typename(type)); - - buf = read_sha1_file(sha1, &type, &size); - if (!buf) - return error("%s: unable to read file.", name); - - ret = run_gpg_verify(buf, size, verbose); - - free(buf); - return ret; -} - static int git_verify_tag_config(const char *var, const char *value, void *cb) { int status = git_gpg_config(var, value, cb); @@ -69,8 +29,10 @@ static int git_verify_tag_config(const char *var, const char *value, void *cb) int cmd_verify_tag(int argc, const char **argv, const char *prefix) { int i = 1, verbose = 0, had_error = 0; + unsigned flags = 0; const struct option verify_tag_options[] = { OPT__VERBOSE(&verbose, N_("print tag contents")), + OPT_BIT(0, "raw", &flags, N_("print raw gpg status output"), GPG_VERIFY_RAW), OPT_END() }; @@ -81,11 +43,16 @@ int cmd_verify_tag(int argc, const char **argv, const char *prefix) if (argc <= i) usage_with_options(verify_tag_usage, verify_tag_options); - /* sometimes the program was terminated because this signal - * was received in the process of writing the gpg input: */ - signal(SIGPIPE, SIG_IGN); - while (i < argc) - if (verify_tag(argv[i++], verbose)) + if (verbose) + flags |= GPG_VERIFY_VERBOSE; + + while (i < argc) { + unsigned char sha1[20]; + const char *name = argv[i++]; + if (get_sha1(name, sha1)) + had_error = !!error("tag '%s' not found.", name); + else if (gpg_verify_tag(sha1, name, flags)) had_error = 1; + } return had_error; } diff --git a/builtin/worktree.c b/builtin/worktree.c new file mode 100644 index 0000000000..9a97e37a3f --- /dev/null +++ b/builtin/worktree.c @@ -0,0 +1,550 @@ +#include "cache.h" +#include "builtin.h" +#include "dir.h" +#include "parse-options.h" +#include "argv-array.h" +#include "branch.h" +#include "refs.h" +#include "run-command.h" +#include "sigchain.h" +#include "refs.h" +#include "utf8.h" +#include "worktree.h" + +static const char * const worktree_usage[] = { + N_("git worktree add [<options>] <path> [<branch>]"), + N_("git worktree list [<options>]"), + N_("git worktree lock [<options>] <path>"), + N_("git worktree prune [<options>]"), + N_("git worktree unlock <path>"), + NULL +}; + +struct add_opts { + int force; + int detach; + int checkout; + const char *new_branch; + int force_new_branch; +}; + +static int show_only; +static int verbose; +static unsigned long expire; + +static int prune_worktree(const char *id, struct strbuf *reason) +{ + struct stat st; + char *path; + int fd, len; + + if (!is_directory(git_path("worktrees/%s", id))) { + strbuf_addf(reason, _("Removing worktrees/%s: not a valid directory"), id); + return 1; + } + if (file_exists(git_path("worktrees/%s/locked", id))) + return 0; + if (stat(git_path("worktrees/%s/gitdir", id), &st)) { + strbuf_addf(reason, _("Removing worktrees/%s: gitdir file does not exist"), id); + return 1; + } + fd = open(git_path("worktrees/%s/gitdir", id), O_RDONLY); + if (fd < 0) { + strbuf_addf(reason, _("Removing worktrees/%s: unable to read gitdir file (%s)"), + id, strerror(errno)); + return 1; + } + len = st.st_size; + path = xmallocz(len); + read_in_full(fd, path, len); + close(fd); + while (len && (path[len - 1] == '\n' || path[len - 1] == '\r')) + len--; + if (!len) { + strbuf_addf(reason, _("Removing worktrees/%s: invalid gitdir file"), id); + free(path); + return 1; + } + path[len] = '\0'; + if (!file_exists(path)) { + struct stat st_link; + free(path); + /* + * the repo is moved manually and has not been + * accessed since? + */ + if (!stat(git_path("worktrees/%s/link", id), &st_link) && + st_link.st_nlink > 1) + return 0; + if (st.st_mtime <= expire) { + strbuf_addf(reason, _("Removing worktrees/%s: gitdir file points to non-existent location"), id); + return 1; + } else { + return 0; + } + } + free(path); + return 0; +} + +static void prune_worktrees(void) +{ + struct strbuf reason = STRBUF_INIT; + struct strbuf path = STRBUF_INIT; + DIR *dir = opendir(git_path("worktrees")); + struct dirent *d; + int ret; + if (!dir) + return; + while ((d = readdir(dir)) != NULL) { + if (is_dot_or_dotdot(d->d_name)) + continue; + strbuf_reset(&reason); + if (!prune_worktree(d->d_name, &reason)) + continue; + if (show_only || verbose) + printf("%s\n", reason.buf); + if (show_only) + continue; + strbuf_reset(&path); + strbuf_addstr(&path, git_path("worktrees/%s", d->d_name)); + ret = remove_dir_recursively(&path, 0); + if (ret < 0 && errno == ENOTDIR) + ret = unlink(path.buf); + if (ret) + error_errno(_("failed to remove '%s'"), path.buf); + } + closedir(dir); + if (!show_only) + rmdir(git_path("worktrees")); + strbuf_release(&reason); + strbuf_release(&path); +} + +static int prune(int ac, const char **av, const char *prefix) +{ + struct option options[] = { + OPT__DRY_RUN(&show_only, N_("do not remove, show only")), + OPT__VERBOSE(&verbose, N_("report pruned objects")), + OPT_EXPIRY_DATE(0, "expire", &expire, + N_("expire objects older than <time>")), + OPT_END() + }; + + expire = ULONG_MAX; + ac = parse_options(ac, av, prefix, options, worktree_usage, 0); + if (ac) + usage_with_options(worktree_usage, options); + prune_worktrees(); + return 0; +} + +static char *junk_work_tree; +static char *junk_git_dir; +static int is_junk; +static pid_t junk_pid; + +static void remove_junk(void) +{ + struct strbuf sb = STRBUF_INIT; + if (!is_junk || getpid() != junk_pid) + return; + if (junk_git_dir) { + strbuf_addstr(&sb, junk_git_dir); + remove_dir_recursively(&sb, 0); + strbuf_reset(&sb); + } + if (junk_work_tree) { + strbuf_addstr(&sb, junk_work_tree); + remove_dir_recursively(&sb, 0); + } + strbuf_release(&sb); +} + +static void remove_junk_on_signal(int signo) +{ + remove_junk(); + sigchain_pop(signo); + raise(signo); +} + +static const char *worktree_basename(const char *path, int *olen) +{ + const char *name; + int len; + + len = strlen(path); + while (len && is_dir_sep(path[len - 1])) + len--; + + for (name = path + len - 1; name > path; name--) + if (is_dir_sep(*name)) { + name++; + break; + } + + *olen = len; + return name; +} + +static int add_worktree(const char *path, const char *refname, + const struct add_opts *opts) +{ + struct strbuf sb_git = STRBUF_INIT, sb_repo = STRBUF_INIT; + struct strbuf sb = STRBUF_INIT; + const char *name; + struct stat st; + struct child_process cp = CHILD_PROCESS_INIT; + struct argv_array child_env = ARGV_ARRAY_INIT; + int counter = 0, len, ret; + struct strbuf symref = STRBUF_INIT; + struct commit *commit = NULL; + + if (file_exists(path) && !is_empty_dir(path)) + die(_("'%s' already exists"), path); + + /* is 'refname' a branch or commit? */ + if (!opts->detach && !strbuf_check_branch_ref(&symref, refname) && + ref_exists(symref.buf)) { /* it's a branch */ + if (!opts->force) + die_if_checked_out(symref.buf, 0); + } else { /* must be a commit */ + commit = lookup_commit_reference_by_name(refname); + if (!commit) + die(_("invalid reference: %s"), refname); + } + + name = worktree_basename(path, &len); + strbuf_addstr(&sb_repo, + git_path("worktrees/%.*s", (int)(path + len - name), name)); + len = sb_repo.len; + if (safe_create_leading_directories_const(sb_repo.buf)) + die_errno(_("could not create leading directories of '%s'"), + sb_repo.buf); + while (!stat(sb_repo.buf, &st)) { + counter++; + strbuf_setlen(&sb_repo, len); + strbuf_addf(&sb_repo, "%d", counter); + } + name = strrchr(sb_repo.buf, '/') + 1; + + junk_pid = getpid(); + atexit(remove_junk); + sigchain_push_common(remove_junk_on_signal); + + if (mkdir(sb_repo.buf, 0777)) + die_errno(_("could not create directory of '%s'"), sb_repo.buf); + junk_git_dir = xstrdup(sb_repo.buf); + is_junk = 1; + + /* + * lock the incomplete repo so prune won't delete it, unlock + * after the preparation is over. + */ + strbuf_addf(&sb, "%s/locked", sb_repo.buf); + write_file(sb.buf, "initializing"); + + strbuf_addf(&sb_git, "%s/.git", path); + if (safe_create_leading_directories_const(sb_git.buf)) + die_errno(_("could not create leading directories of '%s'"), + sb_git.buf); + junk_work_tree = xstrdup(path); + + strbuf_reset(&sb); + strbuf_addf(&sb, "%s/gitdir", sb_repo.buf); + write_file(sb.buf, "%s", real_path(sb_git.buf)); + write_file(sb_git.buf, "gitdir: %s/worktrees/%s", + real_path(get_git_common_dir()), name); + /* + * This is to keep resolve_ref() happy. We need a valid HEAD + * or is_git_directory() will reject the directory. Any value which + * looks like an object ID will do since it will be immediately + * replaced by the symbolic-ref or update-ref invocation in the new + * worktree. + */ + strbuf_reset(&sb); + strbuf_addf(&sb, "%s/HEAD", sb_repo.buf); + write_file(sb.buf, "%s", sha1_to_hex(null_sha1)); + strbuf_reset(&sb); + strbuf_addf(&sb, "%s/commondir", sb_repo.buf); + write_file(sb.buf, "../.."); + + fprintf_ln(stderr, _("Preparing %s (identifier %s)"), path, name); + + argv_array_pushf(&child_env, "%s=%s", GIT_DIR_ENVIRONMENT, sb_git.buf); + argv_array_pushf(&child_env, "%s=%s", GIT_WORK_TREE_ENVIRONMENT, path); + cp.git_cmd = 1; + + if (commit) + argv_array_pushl(&cp.args, "update-ref", "HEAD", + oid_to_hex(&commit->object.oid), NULL); + else + argv_array_pushl(&cp.args, "symbolic-ref", "HEAD", + symref.buf, NULL); + cp.env = child_env.argv; + ret = run_command(&cp); + if (ret) + goto done; + + if (opts->checkout) { + cp.argv = NULL; + argv_array_clear(&cp.args); + argv_array_pushl(&cp.args, "reset", "--hard", NULL); + cp.env = child_env.argv; + ret = run_command(&cp); + if (ret) + goto done; + } + + is_junk = 0; + free(junk_work_tree); + free(junk_git_dir); + junk_work_tree = NULL; + junk_git_dir = NULL; + +done: + strbuf_reset(&sb); + strbuf_addf(&sb, "%s/locked", sb_repo.buf); + unlink_or_warn(sb.buf); + argv_array_clear(&child_env); + strbuf_release(&sb); + strbuf_release(&symref); + strbuf_release(&sb_repo); + strbuf_release(&sb_git); + return ret; +} + +static int add(int ac, const char **av, const char *prefix) +{ + struct add_opts opts; + const char *new_branch_force = NULL; + const char *path, *branch; + struct option options[] = { + OPT__FORCE(&opts.force, N_("checkout <branch> even if already checked out in other worktree")), + OPT_STRING('b', NULL, &opts.new_branch, N_("branch"), + N_("create a new branch")), + OPT_STRING('B', NULL, &new_branch_force, N_("branch"), + N_("create or reset a branch")), + OPT_BOOL(0, "detach", &opts.detach, N_("detach HEAD at named commit")), + OPT_BOOL(0, "checkout", &opts.checkout, N_("populate the new working tree")), + OPT_END() + }; + + memset(&opts, 0, sizeof(opts)); + opts.checkout = 1; + ac = parse_options(ac, av, prefix, options, worktree_usage, 0); + if (!!opts.detach + !!opts.new_branch + !!new_branch_force > 1) + die(_("-b, -B, and --detach are mutually exclusive")); + if (ac < 1 || ac > 2) + usage_with_options(worktree_usage, options); + + path = prefix_filename(prefix, strlen(prefix), av[0]); + branch = ac < 2 ? "HEAD" : av[1]; + + if (!strcmp(branch, "-")) + branch = "@{-1}"; + + opts.force_new_branch = !!new_branch_force; + if (opts.force_new_branch) { + struct strbuf symref = STRBUF_INIT; + + opts.new_branch = new_branch_force; + + if (!opts.force && + !strbuf_check_branch_ref(&symref, opts.new_branch) && + ref_exists(symref.buf)) + die_if_checked_out(symref.buf, 0); + strbuf_release(&symref); + } + + if (ac < 2 && !opts.new_branch && !opts.detach) { + int n; + const char *s = worktree_basename(path, &n); + opts.new_branch = xstrndup(s, n); + } + + if (opts.new_branch) { + struct child_process cp = CHILD_PROCESS_INIT; + cp.git_cmd = 1; + argv_array_push(&cp.args, "branch"); + if (opts.force_new_branch) + argv_array_push(&cp.args, "--force"); + argv_array_push(&cp.args, opts.new_branch); + argv_array_push(&cp.args, branch); + if (run_command(&cp)) + return -1; + branch = opts.new_branch; + } + + return add_worktree(path, branch, &opts); +} + +static void show_worktree_porcelain(struct worktree *wt) +{ + printf("worktree %s\n", wt->path); + if (wt->is_bare) + printf("bare\n"); + else { + printf("HEAD %s\n", sha1_to_hex(wt->head_sha1)); + if (wt->is_detached) + printf("detached\n"); + else if (wt->head_ref) + printf("branch %s\n", wt->head_ref); + } + printf("\n"); +} + +static void show_worktree(struct worktree *wt, int path_maxlen, int abbrev_len) +{ + struct strbuf sb = STRBUF_INIT; + int cur_path_len = strlen(wt->path); + int path_adj = cur_path_len - utf8_strwidth(wt->path); + + strbuf_addf(&sb, "%-*s ", 1 + path_maxlen + path_adj, wt->path); + if (wt->is_bare) + strbuf_addstr(&sb, "(bare)"); + else { + strbuf_addf(&sb, "%-*s ", abbrev_len, + find_unique_abbrev(wt->head_sha1, DEFAULT_ABBREV)); + if (wt->is_detached) + strbuf_addstr(&sb, "(detached HEAD)"); + else if (wt->head_ref) + strbuf_addf(&sb, "[%s]", shorten_unambiguous_ref(wt->head_ref, 0)); + else + strbuf_addstr(&sb, "(error)"); + } + printf("%s\n", sb.buf); + + strbuf_release(&sb); +} + +static void measure_widths(struct worktree **wt, int *abbrev, int *maxlen) +{ + int i; + + for (i = 0; wt[i]; i++) { + int sha1_len; + int path_len = strlen(wt[i]->path); + + if (path_len > *maxlen) + *maxlen = path_len; + sha1_len = strlen(find_unique_abbrev(wt[i]->head_sha1, *abbrev)); + if (sha1_len > *abbrev) + *abbrev = sha1_len; + } +} + +static int list(int ac, const char **av, const char *prefix) +{ + int porcelain = 0; + + struct option options[] = { + OPT_BOOL(0, "porcelain", &porcelain, N_("machine-readable output")), + OPT_END() + }; + + ac = parse_options(ac, av, prefix, options, worktree_usage, 0); + if (ac) + usage_with_options(worktree_usage, options); + else { + struct worktree **worktrees = get_worktrees(GWT_SORT_LINKED); + int path_maxlen = 0, abbrev = DEFAULT_ABBREV, i; + + if (!porcelain) + measure_widths(worktrees, &abbrev, &path_maxlen); + + for (i = 0; worktrees[i]; i++) { + if (porcelain) + show_worktree_porcelain(worktrees[i]); + else + show_worktree(worktrees[i], path_maxlen, abbrev); + } + free_worktrees(worktrees); + } + return 0; +} + +static int lock_worktree(int ac, const char **av, const char *prefix) +{ + const char *reason = "", *old_reason; + struct option options[] = { + OPT_STRING(0, "reason", &reason, N_("string"), + N_("reason for locking")), + OPT_END() + }; + struct worktree **worktrees, *wt; + + ac = parse_options(ac, av, prefix, options, worktree_usage, 0); + if (ac != 1) + usage_with_options(worktree_usage, options); + + worktrees = get_worktrees(0); + wt = find_worktree(worktrees, prefix, av[0]); + if (!wt) + die(_("'%s' is not a working tree"), av[0]); + if (is_main_worktree(wt)) + die(_("The main working tree cannot be locked or unlocked")); + + old_reason = is_worktree_locked(wt); + if (old_reason) { + if (*old_reason) + die(_("'%s' is already locked, reason: %s"), + av[0], old_reason); + die(_("'%s' is already locked"), av[0]); + } + + write_file(git_common_path("worktrees/%s/locked", wt->id), + "%s", reason); + free_worktrees(worktrees); + return 0; +} + +static int unlock_worktree(int ac, const char **av, const char *prefix) +{ + struct option options[] = { + OPT_END() + }; + struct worktree **worktrees, *wt; + int ret; + + ac = parse_options(ac, av, prefix, options, worktree_usage, 0); + if (ac != 1) + usage_with_options(worktree_usage, options); + + worktrees = get_worktrees(0); + wt = find_worktree(worktrees, prefix, av[0]); + if (!wt) + die(_("'%s' is not a working tree"), av[0]); + if (is_main_worktree(wt)) + die(_("The main working tree cannot be locked or unlocked")); + if (!is_worktree_locked(wt)) + die(_("'%s' is not locked"), av[0]); + ret = unlink_or_warn(git_common_path("worktrees/%s/locked", wt->id)); + free_worktrees(worktrees); + return ret; +} + +int cmd_worktree(int ac, const char **av, const char *prefix) +{ + struct option options[] = { + OPT_END() + }; + + git_config(git_default_config, NULL); + + if (ac < 2) + usage_with_options(worktree_usage, options); + if (!prefix) + prefix = ""; + if (!strcmp(av[1], "add")) + return add(ac - 1, av + 1, prefix); + if (!strcmp(av[1], "prune")) + return prune(ac - 1, av + 1, prefix); + if (!strcmp(av[1], "list")) + return list(ac - 1, av + 1, prefix); + if (!strcmp(av[1], "lock")) + return lock_worktree(ac - 1, av + 1, prefix); + if (!strcmp(av[1], "unlock")) + return unlock_worktree(ac - 1, av + 1, prefix); + usage_with_options(worktree_usage, options); +} |