diff options
Diffstat (limited to 'builtin')
70 files changed, 5513 insertions, 4110 deletions
diff --git a/builtin/add.c b/builtin/add.c index 4bd98b799e..145f06ef97 100644 --- a/builtin/add.c +++ b/builtin/add.c @@ -336,14 +336,8 @@ 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; - } - 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); @@ -375,7 +369,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)); @@ -384,7 +377,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) diff --git a/builtin/am.c b/builtin/am.c new file mode 100644 index 0000000000..f1a25ab6ad --- /dev/null +++ b/builtin/am.c @@ -0,0 +1,2443 @@ +/* + * 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" + +/** + * 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; +} + +/** + * Like strbuf_getline(), but treats both '\n' and "\r\n" as line terminators. + */ +static int strbuf_getline_crlf(struct strbuf *sb, FILE *fp) +{ + if (strbuf_getwholeline(sb, fp, '\n')) + return EOF; + if (sb->buf[sb->len - 1] == '\n') { + strbuf_setlen(sb, sb->len - 1); + if (sb->len > 0 && sb->buf[sb->len - 1] == '\r') + strbuf_setlen(sb, sb->len - 1); + } + return 0; +} + +/** + * 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 +}; + +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 */ + unsigned char orig_commit[GIT_SHA1_RAWSZ]; + + /* 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 int write_state_text(const struct am_state *state, + const char *name, const char *string) +{ + return write_file(am_path(state, name), "%s", string); +} + +static int write_state_count(const struct am_state *state, + const char *name, int value) +{ + return write_file(am_path(state, name), "%d", value); +} + +static int write_state_bool(const struct am_state *state, + const char *name, int value) +{ + return 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)); +} + +/** + * Reads a KEY=VALUE shell variable assignment from `fp`, returning the VALUE + * as a newly-allocated string. VALUE must be a quoted string, and the KEY must + * match `key`. Returns NULL on failure. + * + * This is used by read_author_script() to read the GIT_AUTHOR_* variables from + * the author-script. + */ +static char *read_shell_var(FILE *fp, const char *key) +{ + struct strbuf sb = STRBUF_INIT; + const char *str; + + if (strbuf_getline(&sb, fp, '\n')) + goto fail; + + if (!skip_prefix(sb.buf, key, &str)) + goto fail; + + if (!skip_prefix(str, "=", &str)) + goto fail; + + strbuf_remove(&sb, 0, str - sb.buf); + + str = sq_dequote(sb.buf); + if (!str) + goto fail; + + return strbuf_detach(&sb, NULL); + +fail: + strbuf_release(&sb); + return NULL; +} + +/** + * 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"); + FILE *fp; + + assert(!state->author_name); + assert(!state->author_email); + assert(!state->author_date); + + fp = fopen(filename, "r"); + if (!fp) { + if (errno == ENOENT) + return 0; + die_errno(_("could not open '%s' for reading"), filename); + } + + state->author_name = read_shell_var(fp, "GIT_AUTHOR_NAME"); + if (!state->author_name) { + fclose(fp); + return -1; + } + + state->author_email = read_shell_var(fp, "GIT_AUTHOR_EMAIL"); + if (!state->author_email) { + fclose(fp); + return -1; + } + + state->author_date = read_shell_var(fp, "GIT_AUTHOR_DATE"); + if (!state->author_date) { + fclose(fp); + return -1; + } + + if (fgetc(fp) != EOF) { + fclose(fp); + return -1; + } + + fclose(fp); + return 0; +} + +/** + * 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) +{ + int fd; + const char *filename = am_path(state, "final-commit"); + + fd = xopen(filename, O_WRONLY | O_CREAT, 0666); + if (write_in_full(fd, state->msg, state->msg_len) < 0) + die_errno(_("could not write to %s"), filename); + close(fd); +} + +/** + * 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) + hashclr(state->orig_commit); + else if (get_sha1_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(&sb, fp, '\n')) { + unsigned char from_obj[GIT_SHA1_RAWSZ], to_obj[GIT_SHA1_RAWSZ]; + + if (sb.len != GIT_SHA1_HEXSZ * 2 + 1) { + ret = error(invalid_line, sb.buf); + goto finish; + } + + if (get_sha1_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_sha1_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, to_obj)) + ret = error(_("Failed to copy notes from '%s' to '%s'"), + sha1_to_hex(from_obj), sha1_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_crlf(&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_crlf(&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_crlf(&l2, fp); + strbuf_reset(&l3); + strbuf_getline_crlf(&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) +{ + 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"); + 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(_("could not open '%s' for reading: %s"), + *paths, strerror(errno)); + + mail = mkpath("%s/%0*d", state->dir, state->prec, i + 1); + + out = fopen(mail, "w"); + if (!out) + return error(_("could not open '%s' for writing: %s"), + mail, strerror(errno)); + + 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(&sb, in, '\n')) { + 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(_("could not open '%s' for reading: %s"), *paths, + strerror(errno)); + + while (!strbuf_getline(&sb, fp, '\n')) { + 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(&sb, in, '\n')) { + 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); + 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); + 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) +{ + unsigned char curr_head[GIT_SHA1_RAWSZ]; + 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_sha1("HEAD", curr_head)) { + write_state_text(state, "abort-safety", sha1_to_hex(curr_head)); + if (!state->rebasing) + update_ref("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) +{ + unsigned char head[GIT_SHA1_RAWSZ]; + + 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")); + + hashclr(state->orig_commit); + unlink(am_path(state, "original-commit")); + + if (!get_sha1("HEAD", head)) + write_state_text(state, "abort-safety", sha1_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) +{ + unsigned char head[GIT_SHA1_RAWSZ]; + int i; + + if (!get_sha1_tree("HEAD", head)) { + 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, &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(&sb, fp, '\n')) { + 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_sha1(unsigned char *commit_id, const char *mail) +{ + struct strbuf sb = STRBUF_INIT; + FILE *fp = xfopen(mail, "r"); + const char *x; + + if (strbuf_getline(&sb, fp, '\n')) + return -1; + + if (!skip_prefix(sb.buf, "From ", &x)) + return -1; + + if (get_sha1_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"), sha1_to_hex(commit->object.sha1)); + 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; + unsigned char head[GIT_SHA1_RAWSZ]; + struct rev_info rev_info; + FILE *fp; + + if (!get_sha1_tree("HEAD", head)) + tree = lookup_tree(head); + 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; + unsigned char commit_sha1[GIT_SHA1_RAWSZ]; + + if (get_mail_commit_sha1(commit_sha1, mail) < 0) + die(_("could not parse %s"), mail); + + commit = lookup_commit_or_die(commit_sha1, mail); + + get_commit_info(state, commit); + + write_commit_patch(state, commit); + + hashcpy(state->orig_commit, commit_sha1); + write_state_text(state, "original-commit", sha1_to_hex(commit_sha1)); + + 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 child_process cp = CHILD_PROCESS_INIT; + + cp.git_cmd = 1; + + if (index_file) + argv_array_pushf(&cp.env_array, "GIT_INDEX_FILE=%s", index_file); + + /* + * 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) { + cp.no_stdout = 1; + cp.no_stderr = 1; + } + + argv_array_push(&cp.args, "apply"); + + argv_array_pushv(&cp.args, state->git_apply_opts.argv); + + if (index_file) + argv_array_push(&cp.args, "--cached"); + else + argv_array_push(&cp.args, "--index"); + + argv_array_push(&cp.args, am_path(state, "patch")); + + if (run_command(&cp)) + return -1; + + /* Reload index as git-apply will have modified it. */ + discard_cache(); + read_cache_from(index_file ? index_file : get_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; +} + +/** + * Do the three-way merge using fake ancestor, his tree constructed + * from the fake ancestor and the postimage of the patch, and our + * state. + */ +static int run_fallback_merge_recursive(const struct am_state *state, + unsigned char *orig_tree, + unsigned char *our_tree, + unsigned char *his_tree) +{ + struct child_process cp = CHILD_PROCESS_INIT; + int status; + + cp.git_cmd = 1; + + argv_array_pushf(&cp.env_array, "GITHEAD_%s=%.*s", + sha1_to_hex(his_tree), linelen(state->msg), state->msg); + if (state->quiet) + argv_array_push(&cp.env_array, "GIT_MERGE_VERBOSITY=0"); + + argv_array_push(&cp.args, "merge-recursive"); + argv_array_push(&cp.args, sha1_to_hex(orig_tree)); + argv_array_push(&cp.args, "--"); + argv_array_push(&cp.args, sha1_to_hex(our_tree)); + argv_array_push(&cp.args, sha1_to_hex(his_tree)); + + status = run_command(&cp) ? (-1) : 0; + discard_cache(); + read_cache(); + return status; +} + +/** + * 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) +{ + unsigned char orig_tree[GIT_SHA1_RAWSZ], his_tree[GIT_SHA1_RAWSZ], + our_tree[GIT_SHA1_RAWSZ]; + + if (get_sha1("HEAD", our_tree) < 0) + hashcpy(our_tree, 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, &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); + add_pending_sha1(&rev_info, "HEAD", our_tree, 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(his_tree, &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 his_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. + */ + + if (run_fallback_merge_recursive(state, orig_tree, our_tree, his_tree)) { + rerere(state->allow_rerere_autoupdate); + return error(_("Failed to merge in the changes.")); + } + + 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) +{ + unsigned char tree[GIT_SHA1_RAWSZ], parent[GIT_SHA1_RAWSZ], + commit[GIT_SHA1_RAWSZ]; + unsigned char *ptr; + 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, 0, NULL)) + die(_("git write-tree failed to write a tree")); + + if (!get_sha1_commit("HEAD", parent)) { + ptr = parent; + commit_list_insert(lookup_commit(parent), &parents); + } else { + ptr = 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, parents, commit, + 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(sb.buf, "HEAD", commit, ptr, 0, UPDATE_REFS_DIE_ON_ERR); + + if (state->rebasing) { + FILE *fp = xfopen(am_path(state, "rewritten"), "a"); + + assert(!is_null_sha1(state->orig_commit)); + fprintf(fp, "%s ", sha1_to_hex(state->orig_commit)); + fprintf(fp, "%s\n", sha1_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"; + argv_array_push(&cp.args, 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; + + 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); + 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 unsigned char *head, const unsigned char *remote) +{ + struct tree *head_tree, *remote_tree, *index_tree; + unsigned char index[GIT_SHA1_RAWSZ]; + + head_tree = parse_tree_indirect(head); + if (!head_tree) + return error(_("Could not parse object '%s'."), sha1_to_hex(head)); + + remote_tree = parse_tree_indirect(remote); + if (!remote_tree) + return error(_("Could not parse object '%s'."), sha1_to_hex(remote)); + + read_cache_unmerged(); + + if (fast_forward_to(head_tree, head_tree, 1)) + return -1; + + if (write_cache_as_tree(index, 0, NULL)) + return -1; + + index_tree = parse_tree_indirect(index); + if (!index_tree) + return error(_("Could not parse object '%s'."), sha1_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) +{ + unsigned char head[GIT_SHA1_RAWSZ]; + + am_rerere_clear(); + + if (get_sha1("HEAD", head)) + hashcpy(head, 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; + unsigned char abort_safety[GIT_SHA1_RAWSZ], head[GIT_SHA1_RAWSZ]; + + if (file_exists(am_path(state, "dirtyindex"))) + return 0; + + if (read_state_file(&sb, state, "abort-safety", 1) > 0) { + if (get_sha1_hex(sb.buf, abort_safety)) + die(_("could not parse %s"), am_path(state, "abort_safety")); + } else + hashclr(abort_safety); + + if (get_sha1("HEAD", head)) + hashclr(head); + + if (!hashcmp(head, abort_safety)) + return 1; + + error(_("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) +{ + unsigned char curr_head[GIT_SHA1_RAWSZ], orig_head[GIT_SHA1_RAWSZ]; + 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, NULL); + has_curr_head = !is_null_sha1(curr_head); + if (!has_curr_head) + hashcpy(curr_head, EMPTY_TREE_SHA1_BIN); + + has_orig_head = !get_sha1("ORIG_HEAD", orig_head); + if (!has_orig_head) + hashcpy(orig_head, EMPTY_TREE_SHA1_BIN); + + clean_index(curr_head, orig_head); + + if (has_orig_head) + update_ref("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 + 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 54aba4e351..deb1364fa8 100644 --- a/builtin/apply.c +++ b/builtin/apply.c @@ -77,8 +77,7 @@ static enum ws_ignore { static const char *patch_input_file; -static const char *root; -static int root_len; +static struct strbuf root = STRBUF_INIT; static int read_stdin = 1; static int options; @@ -494,8 +493,8 @@ static char *find_name_gnu(const char *line, const char *def, int p_value) } strbuf_remove(&name, 0, cp - name.buf); - if (root) - strbuf_insert(&name, 0, root, root_len); + if (root.len) + strbuf_insert(&name, 0, root.buf, root.len); return squash_slash(strbuf_detach(&name, NULL)); } @@ -697,11 +696,8 @@ static char *find_name_common(const char *line, const char *def, 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'; + if (root.len) { + char *ret = xstrfmt("%s%.*s", root.buf, len, start); return squash_slash(ret); } @@ -785,7 +781,7 @@ static int guess_p_value(const char *nameline) } /* - * Does the ---/+++ line has the POSIX timestamp after the last HT? + * Does the ---/+++ line have the POSIX timestamp after the last HT? * GNU diff puts epoch there to signal a creation/deletion event. Is * this such a timestamp? */ @@ -1277,8 +1273,8 @@ static int parse_git_header(const char *line, int len, unsigned int size, struct * 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); + if (patch->def_name && root.len) { + char *s = xstrfmt("%s%s", root.buf, patch->def_name); free(patch->def_name); patch->def_name = s; } @@ -4501,14 +4497,9 @@ static int option_parse_whitespace(const struct option *opt, 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; + strbuf_reset(&root); + strbuf_addstr(&root, arg); + strbuf_complete(&root, '/'); return 0; } diff --git a/builtin/blame.c b/builtin/blame.c index a22ac17407..83612f5b64 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" @@ -50,7 +51,7 @@ static int xdl_opts; static int abbrev = -1; static int no_whole_file_rename; -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; @@ -458,12 +459,13 @@ 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); + size_t pathlen = strlen(path) + 1; + o = xcalloc(1, sizeof(*o) + pathlen); o->commit = commit; o->refcnt = 1; o->next = commit->util; commit->util = o; - strcpy(o->path, path); + memcpy(o->path, path, pathlen); /* includes NUL */ return o; } @@ -973,7 +975,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, 0, blame_chunk_cb, &d)) + die("unable to generate diff (%s -> %s)", + sha1_to_hex(parent->commit->object.sha1), + sha1_to_hex(target->commit->object.sha1)); /* 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 +1124,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, 1, handle_split_cb, &d)) + die("unable to generate diff (%s)", + sha1_to_hex(parent->commit->object.sha1)); /* remainder, if any, all match the preimage */ handle_split(sb, ent, d.tlno, d.plno, ent->num_lines, parent, split); } @@ -1365,8 +1372,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); } @@ -1827,7 +1841,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 @@ -1866,9 +1880,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.sha1); printf("%s %d %d %d\n", hex, ent->s_lno + 1, @@ -1904,11 +1918,11 @@ 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.sha1); cp = nth_line(sb, ent->lno); for (cnt = 0; cnt < ent->num_lines; cnt++) { @@ -2187,7 +2201,7 @@ static int git_blame_config(const char *var, const char *value, void *cb) 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; } @@ -2226,20 +2240,19 @@ static struct commit_list **append_parent(struct commit_list **tail, const unsig 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); + die("unknown line in '%s': %s", git_path_merge_head(), line.buf); tail = append_parent(tail, sha1); } close(merge_head); @@ -2389,16 +2402,11 @@ static struct commit *fake_working_tree_commit(struct diff_options *opt, return commit; } -static char *prepare_final(struct scoreboard *sb) +static struct object_array_entry *find_single_final(struct rev_info *revs) { int i; - const char *final_commit_name = NULL; - struct rev_info *revs = sb->revs; + struct object_array_entry *found = 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) @@ -2407,14 +2415,24 @@ static char *prepare_final(struct scoreboard *sb) 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; + found->name); + found = &(revs->pending.objects[i]); + } + return found; +} + +static char *prepare_final(struct scoreboard *sb) +{ + struct object_array_entry *found = find_single_final(sb->revs); + if (found) { + sb->final = (struct commit *) found->item; + return xstrdup(found->name); + } else { + return NULL; } - return xstrdup_or_null(final_commit_name); } static char *prepare_initial(struct scoreboard *sb) @@ -2490,6 +2508,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix) long dashdash_pos, lno; char *final_commit_name = NULL; enum object_type type; + struct commit *final_commit = NULL; static struct string_list range_list; static int output_option = 0, opt = 0; @@ -2569,13 +2588,13 @@ parse_done: 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; @@ -2600,10 +2619,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 */ @@ -2676,10 +2697,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) { @@ -2696,6 +2719,14 @@ parse_done: else if (contents_from) die("Cannot use --contents with final commit object name"); + if (reverse && revs.first_parent_only) { + struct object_array_entry *entry = find_single_final(sb.revs); + if (!entry) + die("--reverse and --first-parent together require specified latest commit"); + else + final_commit = (struct commit*) entry->item; + } + /* * If we have bottom, this will mark the ancestors of the * bottom commits we would reach while traversing as @@ -2704,6 +2735,25 @@ parse_done: if (prepare_revision_walk(&revs)) die(_("revision walk setup failed")); + if (reverse && revs.first_parent_only) { + struct commit *c = final_commit; + + sb.revs->children.name = "children"; + while (c->parents && + hashcmp(c->object.sha1, sb.final->object.sha1)) { + 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 (hashcmp(c->object.sha1, sb.final->object.sha1)) + die("--reverse --first-parent together require range along first-parent chain"); + } + if (is_null_sha1(sb.final->object.sha1)) { o = sb.final->util; sb.final_buf = xmemdupz(o->file.ptr, o->file.size); diff --git a/builtin/branch.c b/builtin/branch.c index b42e5b6dbc..b99a436ef3 100644 --- a/builtin/branch.c +++ b/builtin/branch.c @@ -19,18 +19,17 @@ #include "column.h" #include "utf8.h" #include "wt-status.h" +#include "ref-filter.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 +51,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 +113,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 +152,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 +191,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: @@ -223,7 +215,7 @@ static int delete_branches(int argc, const char **argv, int force, int kinds, int flags = 0; strbuf_branchname(&bname, argv[i]); - if (kinds == REF_LOCAL_BRANCH && !strcmp(head, bname.buf)) { + if (kinds == FILTER_REFS_BRANCHES && !strcmp(head, bname.buf)) { error(_("Cannot delete the branch '%s' " "which you are currently on."), bname.buf); ret = 1; @@ -253,7 +245,8 @@ static int delete_branches(int argc, const char **argv, int force, int kinds, continue; } - 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'"), @@ -278,147 +271,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 +333,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 +345,74 @@ 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.sha1, 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) { + /* 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); +} + +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 = ""; + const char *desc = item->refname; + char *to_free = NULL; switch (item->kind) { - case REF_LOCAL_BRANCH: - color = BRANCH_COLOR_LOCAL; + case FILTER_REFS_BRANCHES: + skip_prefix(desc, "refs/heads/", &desc); + if (!filter->detached && !strcmp(desc, head)) + current = 1; + else + color = BRANCH_COLOR_LOCAL; break; - case REF_REMOTE_BRANCH: + case FILTER_REFS_REMOTES: + skip_prefix(desc, "refs/remotes/", &desc); color = BRANCH_COLOR_REMOTE; + prefix = remote_prefix; + break; + case FILTER_REFS_DETACHED_HEAD: + desc = to_free = get_head_description(); + current = 1; break; default: color = BRANCH_COLOR_PLAIN; @@ -531,8 +425,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, 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 +435,82 @@ 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) { + skip_prefix(item->symref, "refs/remotes/", &desc); + strbuf_addf(&out, " -> %s", desc); + } + 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) -{ - 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; - } - return w; -} - -static char *get_head_description(void) -{ - struct strbuf desc = STRBUF_INIT; - struct wt_status_state state; - memset(&state, 0, sizeof(state)); - wt_status_get_state(&state, 1); - if (state.rebase_in_progress || - state.rebase_interactive_in_progress) - strbuf_addf(&desc, _("(no branch, rebasing %s)"), - state.branch); - else if (state.bisect_in_progress) - strbuf_addf(&desc, _("(no branch, bisect started on %s)"), - state.branch); - else if (state.detached_from) { - /* 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); -} - -static void show_detached(struct ref_list *ref_list) +static int calc_maxwidth(struct ref_array *refs, int remote_bonus) { - 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); + 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 max; } -static int print_ref_list(int kinds, int detached, int verbose, int abbrev, struct commit_list *with_commit, const char **pattern) +static void print_ref_list(struct ref_filter *filter, struct ref_sorting *sorting) { 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); - } + struct ref_array array; + int maxwidth = 0; + const char *remote_prefix = ""; - 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 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/"; - if (verbose) - ref_list.maxwidth = calc_maxwidth(&ref_list); - } + memset(&array, 0, sizeof(array)); - 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); - } + verify_ref_format("%(refname)%(symref)"); + filter_refs(&array, filter, filter->kind | FILTER_REFS_INCLUDE_BROKEN); - free_ref_list(&ref_list); + if (filter->verbose) + maxwidth = calc_maxwidth(&array, strlen(remote_prefix)); - if (cb.ret) - error(_("some refs could not be read")); + /* + * 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); - return cb.ret; + ref_array_clear(&array); } static void rename_branch(const char *oldname, const char *newname, int force) @@ -745,20 +566,6 @@ 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) @@ -775,7 +582,7 @@ static int edit_branch_description(const char *branch_name) " %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)) { + if (write_file_gently(git_path(edit_description), "%s", buf.buf)) { strbuf_release(&buf); return error(_("could not write branch description template: %s"), strerror(errno)); @@ -785,7 +592,7 @@ static int edit_branch_description(const char *branch_name) 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); @@ -798,17 +605,16 @@ static int edit_branch_description(const char *branch_name) 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))"), @@ -818,25 +624,15 @@ int cmd_branch(int argc, const char **argv, const char *prefix) 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__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 +642,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 +669,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 +679,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 +703,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) @@ -1010,7 +807,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) diff --git a/builtin/cat-file.c b/builtin/cat-file.c index 049a95f1f1..c0fd8dbb1c 100644 --- a/builtin/cat-file.c +++ b/builtin/cat-file.c @@ -9,6 +9,16 @@ #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; + const char *format; +}; static int cat_one_file(int opt, const char *exp_type, const char *obj_name, int unknown_type) @@ -204,14 +214,25 @@ 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; assert(data->info.typep); if (data->type == OBJ_BLOB) { - if (stream_blob_to_fd(fd, sha1, NULL, 0) < 0) + if (opt->buffer_output) + fflush(stdout); + if (stream_blob_to_fd(1, sha1, NULL, 0) < 0) die("unable to stream %s to stdout", sha1_to_hex(sha1)); } else { @@ -227,29 +248,40 @@ static void print_object_or_die(int fd, struct expand_data *data) if (data->info.sizep && size != data->size) die("object %s changed size!?", sha1_to_hex(sha1)); - 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 (sha1_object_info_extended(data->sha1, &data->info, LOOKUP_REPLACE_OBJECT) < 0) { + printf("%s missing\n", obj_name ? obj_name : sha1_to_hex(data->sha1)); + 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); if (result != FOUND) { switch (result) { @@ -274,7 +306,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 +314,38 @@ 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 void batch_object_cb(const unsigned char sha1[20], void *vdata) +{ + struct object_cb_data *data = vdata; + hashcpy(data->expand->sha1, sha1); + batch_object_write(NULL, data->opt, data->expand); +} + +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; } @@ -330,6 +376,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 @@ -355,9 +416,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); @@ -367,7 +426,7 @@ 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 (--batch | --batch-check) [--follow-symlinks]"), NULL }; @@ -412,8 +471,9 @@ 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_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,6 +482,8 @@ 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() }; @@ -446,7 +508,7 @@ int cmd_cat_file(int argc, const char **argv, const char *prefix) usage_with_options(cat_file_usage, options); } - if (batch.follow_symlinks && !batch.enabled) { + if ((batch.follow_symlinks || batch.all_objects) && !batch.enabled) { usage_with_options(cat_file_usage, options); } diff --git a/builtin/check-attr.c b/builtin/check-attr.c index 21d2bedcc9..265c9ba022 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 }; diff --git a/builtin/check-ignore.c b/builtin/check-ignore.c index dc8d97c56c..43f361797a 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 }; diff --git a/builtin/checkout.c b/builtin/checkout.c index f71844a23a..e346f521b0 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -18,6 +18,7 @@ #include "xdiff-interface.h" #include "ll-merge.h" #include "resolve-undo.h" +#include "submodule-config.h" #include "submodule.h" static const char * const checkout_usage[] = { @@ -36,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; @@ -48,8 +50,6 @@ struct checkout_opts { const char *prefix; struct pathspec pathspec; struct tree *source_tree; - - int new_worktree_mode; }; static int post_checkout_hook(struct commit *old, struct commit *new, @@ -282,7 +282,7 @@ static int checkout_paths(const struct checkout_opts *opts, 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 @@ -418,7 +418,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); @@ -502,14 +502,14 @@ 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 ? + tree = parse_tree_indirect(old->commit ? old->commit->object.sha1 : EMPTY_TREE_SHA1_BIN); init_tree_desc(&trees[0], tree->buffer, tree->size); @@ -612,22 +612,20 @@ 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 @@ -832,8 +830,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); @@ -898,71 +895,6 @@ 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, @@ -1170,14 +1102,14 @@ static int checkout_branch(struct checkout_opts *opts, die(_("Cannot switch branch to a non-commit '%s'"), new->name); - if (new->path && !opts->force_detach && !opts->new_branch) { + if (new->path && !opts->force_detach && !opts->new_branch && + !opts->ignore_other_worktrees) { 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); + (!(flag & REF_ISSYMREF) || strcmp(head_ref, new->path))) + die_if_checked_out(new->path); free(head_ref); } @@ -1225,6 +1157,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) N_("second guess 'git checkout <no-such-branch>'")), 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(), }; @@ -1232,6 +1165,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.show_progress = -1; gitmodules_config(); git_config(git_checkout_config, &opts); @@ -1241,7 +1175,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); - opts.new_worktree_mode = getenv("GIT_CHECKOUT_NEW_WORKTREE") != NULL; + 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 */ diff --git a/builtin/clean.c b/builtin/clean.c index 6dcb72e644..d7acb94a95 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" @@ -148,6 +147,30 @@ static int exclude_cb(const struct option *opt, const char *arg, int unset) return 0; } +/* + * Return 1 if the given path is the root of a git repository or + * submodule else 0. Will not return 1 for bare repositories with the + * exception of creating a bare repository in "foo/.git" and calling + * is_git_repository("foo"). + */ +static int is_git_repository(struct strbuf *path) +{ + int ret = 0; + int gitfile_error; + size_t orig_path_len = path->len; + assert(orig_path_len != 0); + strbuf_complete(path, '/'); + strbuf_addstr(path, ".git"); + if (read_gitfile_gently(path->buf, &gitfile_error) || is_git_directory(path->buf)) + ret = 1; + if (gitfile_error == READ_GITFILE_ERR_OPEN_FAILED || + gitfile_error == READ_GITFILE_ERR_READ_FAILED) + ret = 1; /* This could be a real .git file, take the + * safe option and avoid cleaning */ + strbuf_setlen(path, orig_path_len); + return ret; +} + static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag, int dry_run, int quiet, int *dir_gone) { @@ -155,13 +178,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_git_repository(path)) { if (!quiet) { quote_path_relative(path->buf, prefix, "ed); printf(dry_run ? _(msg_would_skip_git_dir) : _(msg_skip_git_dir), @@ -184,8 +205,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) { diff --git a/builtin/clone.c b/builtin/clone.c index a72ff7e009..caae43e7a3 100644 --- a/builtin/clone.c +++ b/builtin/clone.c @@ -99,82 +99,140 @@ 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. + */ + 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. */ - start = end; - while (repo < start && !is_dir_sep(start[-1]) && start[-1] != ':') - start--; + ptr = end; + while (start < ptr && !is_dir_sep(ptr[-1]) && ptr[-1] != ':') + ptr--; + start = ptr; /* * Strip .{bundle,git}. */ - strip_suffix(start, is_bundle ? ".bundle" : ".git" , &len); + len = end - start; + strip_suffix_mem(start, &len, is_bundle ? ".bundle" : ".git"); + + 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); @@ -236,9 +294,14 @@ static int add_one_reference(struct string_list_item *item, void *cb_data) 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))) + } else if (!is_directory(mkpath("%s/objects", ref_git))) { + struct strbuf sb = STRBUF_INIT; + if (get_common_dir(&sb, ref_git)) + die(_("reference repository '%s' as a linked checkout is not supported yet."), + item->string); 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); @@ -366,8 +429,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); @@ -484,16 +549,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_sha1, + 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) @@ -726,11 +801,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) @@ -879,10 +958,6 @@ int cmd_clone(int argc, const char **argv, const char *prefix) if (option_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); @@ -996,8 +1071,10 @@ int cmd_clone(int argc, const char **argv, const char *prefix) 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(); diff --git a/builtin/commit-tree.c b/builtin/commit-tree.c index 25aa2cdef3..8747c0f2fb 100644 --- a/builtin/commit-tree.c +++ b/builtin/commit-tree.c @@ -10,7 +10,7 @@ #include "utf8.h" #include "gpg-interface.h" -static const char commit_tree_usage[] = "git commit-tree [(-p <sha1>)...] [-S[<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; diff --git a/builtin/commit.c b/builtin/commit.c index 254477fd1d..dca09e2c3b 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -166,9 +166,9 @@ 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))) sequencer_in_use = 1; @@ -324,6 +324,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 +345,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 +356,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 +366,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 +389,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 +405,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 +474,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, @@ -725,12 +724,12 @@ 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)) { + 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) { @@ -776,7 +775,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 +855,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, @@ -1015,7 +1014,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 +1045,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); @@ -1684,10 +1683,10 @@ int cmd_commit(int argc, const char **argv, const char *prefix) if (!reflog_msg) reflog_msg = "commit (merge)"; pptr = &commit_list_insert(current_head, pptr)->next; - fp = fopen(git_path("MERGE_HEAD"), "r"); + fp = fopen(git_path_merge_head(), "r"); if (fp == NULL) die_errno(_("could not open '%s' for reading"), - git_path("MERGE_HEAD")); + git_path_merge_head()); while (strbuf_getline(&m, fp, '\n') != EOF) { struct commit *parent; @@ -1698,8 +1697,8 @@ int cmd_commit(int argc, const char **argv, const char *prefix) } 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; @@ -1727,7 +1726,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")); @@ -1775,12 +1774,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" diff --git a/builtin/config.c b/builtin/config.c index 7188405f7e..adc772786a 100644 --- a/builtin/config.c +++ b/builtin/config.c @@ -13,6 +13,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; @@ -78,6 +79,7 @@ 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_END(), }; @@ -91,7 +93,7 @@ static void check_argc(int argc, int min, int max) { static int show_all_config(const char *key_, const char *value_, void *cb) { - if (value_) + if (!omit_values && value_) printf("%s%c%s%c", key_, delim, value_, term); else printf("%s%c", key_, term); @@ -106,48 +108,40 @@ 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_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 +158,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 +246,6 @@ free_strings: static char *normalize_value(const char *key, const char *value) { - char *normalized; - if (!value) return NULL; @@ -263,27 +256,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; @@ -430,14 +417,11 @@ static int get_urlmatch(const char *var, const char *url) 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); @@ -549,7 +533,11 @@ 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 (actions == ACTION_LIST) { check_argc(argc, 0, 0); if (git_config_with_options(show_all_config, NULL, diff --git a/builtin/describe.c b/builtin/describe.c index a36c829e57..7df554326b 100644 --- a/builtin/describe.c +++ b/builtin/describe.c @@ -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/fast-export.c b/builtin/fast-export.c index b8182c241d..d23f3beba9 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" diff --git a/builtin/fetch.c b/builtin/fetch.c index 8d5b2dba2b..ed84963a57 100644 --- a/builtin/fetch.c +++ b/builtin/fetch.c @@ -11,6 +11,7 @@ #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" @@ -527,36 +528,38 @@ static int update_local_ref(struct ref *ref, } 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.sha1, DEFAULT_ABBREV); + strbuf_addstr(&quickref, ".."); + strbuf_add_unique_abbrev(&quickref, ref->new_sha1, DEFAULT_ABBREV); if ((recurse_submodules != RECURSE_SUBMODULES_OFF) && (recurse_submodules != RECURSE_SUBMODULES_ON)) check_for_new_submodule_commits(ref->new_sha1); r = s_update_ref("fast-forward", ref, 1); strbuf_addf(display, "%c %-*s %-*s -> %s%s", r ? '!' : ' ', - TRANSPORT_SUMMARY_WIDTH, quickref, + TRANSPORT_SUMMARY_WIDTH, quickref.buf, REFCOL_WIDTH, remote, pretty_ref, r ? _(" (unable to update local ref)") : ""); + 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.sha1, DEFAULT_ABBREV); + strbuf_addstr(&quickref, "..."); + strbuf_add_unique_abbrev(&quickref, ref->new_sha1, DEFAULT_ABBREV); if ((recurse_submodules != RECURSE_SUBMODULES_OFF) && (recurse_submodules != RECURSE_SUBMODULES_ON)) check_for_new_submodule_commits(ref->new_sha1); r = s_update_ref("forced-update", ref, 1); strbuf_addf(display, "%c %-*s %-*s -> %s (%s)", r ? '!' : '+', - TRANSPORT_SUMMARY_WIDTH, quickref, + TRANSPORT_SUMMARY_WIDTH, quickref.buf, REFCOL_WIDTH, remote, pretty_ref, r ? _("unable to update local ref") : _("forced update")); + strbuf_release(&quickref); return r; } else { strbuf_addf(display, "! %-*s %-*s -> %s %s", @@ -591,7 +594,7 @@ 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; fp = fopen(filename, "a"); @@ -636,8 +639,7 @@ 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); + ref = alloc_ref(rm->peer_ref->name); hashcpy(ref->old_sha1, rm->peer_ref->old_sha1); hashcpy(ref->new_sha1, rm->old_sha1); ref->force = rm->peer_ref->force; @@ -790,20 +792,29 @@ 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) { + 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); + string_list_clear(&refnames, 0); + } + + if (verbosity >= 0) { + for (ref = stale_refs; ref; ref = ref->next) { + if (!shown_url) { + fprintf(stderr, _("From %.*s\n"), url_len, url); + shown_url = 1; + } fprintf(stderr, " x %-*s %-*s -> %s\n", TRANSPORT_SUMMARY(_("[deleted]")), REFCOL_WIDTH, _("(none)"), prettify_refname(ref->name)); warn_dangling_symref(stderr, dangling_msg, ref->name); } } + free(url); free_refs(stale_refs); return result; @@ -825,7 +836,7 @@ static void check_not_current_branch(struct ref *ref_map) static int truncate_fetch_head(void) { - const char *filename = git_path("FETCH_HEAD"); + const char *filename = git_path_fetch_head(); FILE *fp = fopen(filename, "w"); if (!fp) @@ -979,17 +990,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) { + size_t wordlen = strcspn(value, " \t\n"); + + if (wordlen >= 1) string_list_append(g->list, - xstrndup(value, space)); - } - value += space + (value[space] != '\0'); - space = strcspn(value, " \t\n"); + xstrndup(value, wordlen)); + value += wordlen + (value[wordlen] != '\0'); } } @@ -1148,11 +1157,8 @@ int cmd_fetch(int argc, const char **argv, const char *prefix) 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 */ diff --git a/builtin/fmt-merge-msg.c b/builtin/fmt-merge-msg.c index 05f4c26311..846004b833 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" @@ -536,7 +537,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; @@ -575,13 +576,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.sha1)) 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 cb7db230d3..4e9f6c29bf 100644 --- a/builtin/for-each-ref.c +++ b/builtin/for-each-ref.c @@ -2,1096 +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 (flag & REF_ISBROKEN) { - warning("ignoring broken ref %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, @@ -1106,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); @@ -1120,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 2679793049..8b8bb42c51 100644 --- a/builtin/fsck.c +++ b/builtin/fsck.c @@ -23,8 +23,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; @@ -35,48 +38,54 @@ static int show_dangling = 1; #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 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), sha1_to_hex(obj->sha1), 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 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; @@ -119,7 +128,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 +141,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 +167,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; @@ -179,6 +188,8 @@ static void check_reachable_object(struct object *obj) if (!(obj->flags & HAS_OBJ)) { if (has_sha1_pack(obj->sha1)) return; /* it is in pack - forget about it */ + if (connectivity_only && has_sha1_file(obj->sha1)) + return; printf("missing %s %s\n", typename(obj->type), sha1_to_hex(obj->sha1)); errors_found |= ERROR_REACHABLE; return; @@ -225,13 +236,14 @@ static void check_unreachable_object(struct object *obj) printf("dangling %s %s\n", typename(obj->type), sha1_to_hex(obj->sha1)); 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)); FILE *f; if (safe_create_leading_directories_const(filename)) { error("Could not create lost-found"); + free(filename); return; } if (!(f = fopen(filename, "w"))) @@ -244,6 +256,7 @@ static void check_unreachable_object(struct object *obj) if (fclose(f)) die_errno("Could not finish '%s'", filename); + free(filename); } return; } @@ -296,9 +309,9 @@ static int fsck_obj(struct object *obj) fprintf(stderr, "Checking %s %s\n", typename(obj->type), sha1_to_hex(obj->sha1)); - 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) { @@ -353,102 +366,6 @@ 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 void fsck_dir(int i, char *path) -{ - 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]; - - 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 (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 void fsck_handle_reflog_sha1(const char *refname, unsigned char *sha1) @@ -501,8 +418,10 @@ 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; mark_object_reachable(obj); @@ -536,9 +455,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) @@ -546,14 +484,11 @@ 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) @@ -565,17 +500,23 @@ static int fsck_head_link(void) fprintf(stderr, "Checking HEAD link\n"); head_points_at = resolve_ref_unsafe("HEAD", 0, head_oid.hash, &flag); - if (!head_points_at) + 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); } @@ -595,6 +536,7 @@ 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; @@ -621,6 +563,7 @@ 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")), @@ -638,6 +581,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) @@ -648,16 +597,21 @@ int cmd_fsck(int argc, const char **argv, const char *prefix) include_reflogs = 0; } + 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) { + /* directory name, minus trailing slash */ + size_t namelen = alt->name - alt->base - 1; + struct strbuf name = STRBUF_INIT; + strbuf_add(&name, alt->base, namelen); + fsck_object_dir(name.buf); + strbuf_release(&name); + } } if (check_full) { diff --git a/builtin/gc.c b/builtin/gc.c index 203265db76..c583aad6ec 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" @@ -42,20 +43,8 @@ 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 void remove_pidfile(void) -{ - if (pidfile) - unlink(pidfile); -} - -static void remove_pidfile_on_signal(int signo) -{ - remove_pidfile(); - sigchain_pop(signo); - raise(signo); -} +static struct tempfile pidfile; +static struct lock_file log_lock; static struct string_list pack_garbage = STRING_LIST_INIT_DUP; @@ -84,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; @@ -101,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); } @@ -215,20 +226,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 && @@ -243,7 +256,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) @@ -252,6 +265,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; } } @@ -261,14 +275,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)) @@ -290,6 +319,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")), @@ -346,13 +376,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(); @@ -365,18 +398,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) { 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/hash-object.c b/builtin/hash-object.c index 07fef3cc6b..43b098b76c 100644 --- a/builtin/hash-object.c +++ b/builtin/hash-object.c @@ -78,7 +78,7 @@ 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; diff --git a/builtin/help.c b/builtin/help.c index 3422e73079..1cd0c1ee44 100644 --- a/builtin/help.c +++ b/builtin/help.c @@ -140,17 +140,10 @@ 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); @@ -183,7 +176,7 @@ static void add_man_viewer(const char *name) while (*p) p = &((*p)->next); *p = xcalloc(1, (sizeof(**p) + len + 1)); - strncpy((*p)->name, name, len); + memcpy((*p)->name, name, len); /* NUL-terminated by xcalloc */ } static int supported_man_viewer(const char *name, size_t len) @@ -199,7 +192,7 @@ static void do_add_man_viewer_info(const char *name, { struct man_viewer_info_list *new = xcalloc(1, sizeof(*new) + len + 1); - strncpy(new->name, name, len); + memcpy(new->name, name, len); /* NUL-terminated by xcalloc */ new->info = xstrdup(value); new->next = man_viewer_info_list; man_viewer_info_list = new; @@ -295,16 +288,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 +295,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) diff --git a/builtin/index-pack.c b/builtin/index-pack.c index f07bc66ed6..1ad1bde696 100644 --- a/builtin/index-pack.c +++ b/builtin/index-pack.c @@ -75,6 +75,7 @@ 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_stat; static int check_self_contained_and_connected; @@ -192,7 +193,7 @@ 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; @@ -440,7 +441,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 @@ -838,10 +839,9 @@ 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)) + if (fsck_walk(obj, NULL, &fsck_options)) die(_("Not all child objects of %s are reachable"), sha1_to_hex(obj->sha1)); if (obj->type == OBJ_TREE) { @@ -1421,7 +1421,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 +1432,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); @@ -1615,6 +1615,7 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix) 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 +1633,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; diff --git a/builtin/init-db.c b/builtin/init-db.c index 4335738135..f59f40768e 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" @@ -35,10 +36,11 @@ static void safe_create_dir(const char *dir, int share) 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 +50,64 @@ 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; DIR *dir; - const char *git_dir = get_git_dir(); - int len = strlen(git_dir); char *to_free = NULL; if (!template_dir) @@ -131,26 +120,23 @@ 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"); + strbuf_addstr(&template_path, "config"); repository_format_version = 0; git_config_from_file(check_repository_format_version, - template_path, NULL); - template_path[template_len] = 0; + template_path.buf, NULL); + strbuf_setlen(&template_path, template_len); if (repository_format_version && repository_format_version != GIT_REPO_VERSION) { @@ -161,17 +147,15 @@ static void copy_templates(const char *template_dir) 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_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) @@ -198,28 +182,20 @@ static int needs_work_tree_config(const char *git_dir, const char *work_tree) static int create_default_files(const char *template_path) { - 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); + safe_create_dir(git_path_buf(&buf, "refs"), 1); + safe_create_dir(git_path_buf(&buf, "refs/heads"), 1); + safe_create_dir(git_path_buf(&buf, "refs/tags"), 1); /* Just look for `init.templatedir` */ git_config(git_init_db_config, NULL); @@ -243,16 +219,16 @@ static int create_default_files(const char *template_path) */ if (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")); + adjust_shared_perm(git_path_buf(&buf, "refs")); + adjust_shared_perm(git_path_buf(&buf, "refs/heads")); + adjust_shared_perm(git_path_buf(&buf, "refs/tags")); } /* * 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 +237,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; @@ -288,14 +263,13 @@ static int create_default_files(const char *template_path) /* 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)) + if (needs_work_tree_config(get_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,31 +280,35 @@ 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); + struct strbuf path = STRBUF_INIT; + size_t baselen; + + strbuf_addstr(&path, get_object_directory()); + baselen = path.len; + + safe_create_dir(path.buf, 1); - memcpy(path, object_directory, len); + strbuf_setlen(&path, baselen); + strbuf_addstr(&path, "/pack"); + safe_create_dir(path.buf, 1); - 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_setlen(&path, baselen); + strbuf_addstr(&path, "/info"); + safe_create_dir(path.buf, 1); - free(path); + strbuf_release(&path); } int set_git_dir_init(const char *git_dir, const char *real_git_dir, @@ -377,7 +355,7 @@ 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) @@ -413,13 +391,13 @@ int init_db(const char *template_dir, unsigned int flags) */ if (shared_repository < 0) /* force to the mode value */ - sprintf(buf, "0%o", -shared_repository); + xsnprintf(buf, sizeof(buf), "0%o", -shared_repository); else if (shared_repository == PERM_GROUP) - sprintf(buf, "%d", OLD_PERM_GROUP); + xsnprintf(buf, sizeof(buf), "%d", OLD_PERM_GROUP); else if (shared_repository == PERM_EVERYBODY) - sprintf(buf, "%d", OLD_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"); } diff --git a/builtin/log.c b/builtin/log.c index 878104943f..dda671d975 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,7 @@ static const char *default_date_mode = NULL; static int default_abbrev_commit; static int default_show_root = 1; +static int default_follow; static int decoration_style; static int decoration_given; static int use_mailmap_config; @@ -102,6 +104,8 @@ 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 */ @@ -112,7 +116,7 @@ static void cmd_log_init_defaults(struct rev_info *rev) 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; } @@ -338,8 +342,7 @@ static int cmd_log_walk(struct rev_info *rev) * retain that state information if replacing rev->diffopt in this loop */ while ((commit = get_revision(rev)) != NULL) { - 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. @@ -390,6 +393,10 @@ 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")) { @@ -496,7 +503,8 @@ static int show_tree_object(const unsigned char *sha1, 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 */ @@ -531,7 +539,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) @@ -618,6 +626,22 @@ 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; @@ -631,6 +655,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); } @@ -771,8 +796,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) @@ -939,7 +963,7 @@ 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); @@ -1438,8 +1462,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++; diff --git a/builtin/ls-files.c b/builtin/ls-files.c index 6fa2205734..b6a7cb0c7c 100644 --- a/builtin/ls-files.c +++ b/builtin/ls-files.c @@ -516,7 +516,7 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix) /* 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..a31024900b 100644 --- a/builtin/ls-remote.c +++ b/builtin/ls-remote.c @@ -4,7 +4,7 @@ #include "remote.h" static const char ls_remote_usage[] = -"git ls-remote [--heads] [--tags] [-u <exec> | --upload-pack <exec>]\n" +"git ls-remote [--heads] [--tags] [--upload-pack=<exec>]\n" " [-q | --quiet] [--exit-code] [--get-url] [<repository> [<refs>...]]"; /* @@ -93,12 +93,8 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix) 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; - } + for (j = i; j < argc; j++) + pattern[j - i] = xstrfmt("*/%s", argv[j]); } remote = remote_get(dest); if (!remote) { 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..f6df274111 100644 --- a/builtin/mailinfo.c +++ b/builtin/mailinfo.c @@ -6,1029 +6,7 @@ #include "builtin.h" #include "utf8.h" #include "strbuf.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 int git_mailinfo_config(const char *var, const char *value, void *unused) -{ - 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; -} +#include "mailinfo.h" static const char mailinfo_usage[] = "git mailinfo [-k | -b] [-m | --message-id] [-u | --encoding=<encoding> | -n] [--scissors | --no-scissors] <msg> <patch> < mail >info"; @@ -1036,34 +14,36 @@ static const char mailinfo_usage[] = int cmd_mailinfo(int argc, const char **argv, const char *prefix) { const char *def_charset; + struct mailinfo mi; + int status; /* 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 +52,10 @@ 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; + status = !!mailinfo(&mi, argv[1], argv[2]); + clear_mailinfo(&mi); + + return status; } diff --git a/builtin/mailsplit.c b/builtin/mailsplit.c index 8e02ea109a..104277acc4 100644 --- a/builtin/mailsplit.c +++ b/builtin/mailsplit.c @@ -98,30 +98,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; + 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 +155,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,8 +167,11 @@ 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)); @@ -173,14 +183,19 @@ static int split_maildir(const char *maildir, const char *dir, 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 +203,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; @@ -215,8 +229,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) diff --git a/builtin/merge-file.c b/builtin/merge-file.c index ea8093f676..55447053f2 100644 --- a/builtin/merge-file.c +++ b/builtin/merge-file.c @@ -75,7 +75,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]); } @@ -103,5 +104,8 @@ int cmd_merge_file(int argc, const char **argv, const char *prefix) free(result.ptr); } + if (ret > 127) + ret = 127; + return ret; } diff --git a/builtin/merge-index.c b/builtin/merge-index.c index 1a1eafa6fd..1c3427c36c 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->sha1); + 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..491efd556e 100644 --- a/builtin/merge-recursive.c +++ b/builtin/merge-recursive.c @@ -14,7 +14,7 @@ static const char *better_branch_name(const char *branch) if (strlen(branch) != 40) 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; } diff --git a/builtin/merge-tree.c b/builtin/merge-tree.c index f9ab48597e..2a4aafec6a 100644 --- a/builtin/merge-tree.c +++ b/builtin/merge-tree.c @@ -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); } diff --git a/builtin/merge.c b/builtin/merge.c index 85c54dcd5a..bbf3110f88 100644 --- a/builtin/merge.c +++ b/builtin/merge.c @@ -231,9 +231,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) @@ -338,7 +338,7 @@ static void squash_message(struct commit *commit, struct commit_list *remotehead struct pretty_print_context ctx = {0}; printf(_("Squash commit -- not updating HEAD\n")); - filename = git_path("SQUASH_MSG"); + filename = git_path_squash_msg(); fd = open(filename, O_WRONLY | O_CREAT, 0666); if (fd < 0) die_errno(_("Could not write to '%s'"), filename); @@ -754,7 +754,7 @@ static void add_strategies(const char *string, unsigned attr) static void write_merge_msg(struct strbuf *msg) { - const char *filename = git_path("MERGE_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"), @@ -766,7 +766,7 @@ static void write_merge_msg(struct strbuf *msg) 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); @@ -799,14 +799,14 @@ static void prepare_to_commit(struct commit_list *remoteheads) strbuf_commented_addf(&msg, _(merge_editor_comment), comment_line_char); write_merge_msg(&msg); 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); @@ -865,7 +865,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); @@ -967,7 +967,7 @@ static void write_merge_state(struct commit_list *remoteheads) } strbuf_addf(&buf, "%s\n", sha1_to_hex(sha1)); } - filename = git_path("MERGE_HEAD"); + 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); @@ -977,7 +977,7 @@ static void write_merge_state(struct commit_list *remoteheads) strbuf_addch(&merge_msg, '\n'); write_merge_msg(&merge_msg); - filename = git_path("MERGE_MODE"); + 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); @@ -1019,7 +1019,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 +1033,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 +1068,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); @@ -1204,7 +1202,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 +1213,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 +1224,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.")); @@ -1319,13 +1317,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.sha1, DEFAULT_ABBREV); switch (signature_check.result) { case 'G': break; @@ -1415,15 +1413,15 @@ int cmd_merge(int argc, const char **argv, const char *prefix) /* 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) - printf(_("Updating %s..%s\n"), - hex, - find_unique_abbrev(remoteheads->item->object.sha1, - DEFAULT_ABBREV)); + if (verbosity >= 0) { + char from[GIT_SHA1_HEXSZ + 1], to[GIT_SHA1_HEXSZ + 1]; + find_unique_abbrev_r(from, head_commit->object.sha1, + DEFAULT_ABBREV); + find_unique_abbrev_r(to, remoteheads->item->object.sha1, + DEFAULT_ABBREV); + printf(_("Updating %s..%s\n"), from, to); + } strbuf_addstr(&msg, "Fast-forward"); if (have_message) strbuf_addstr(&msg, 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/name-rev.c b/builtin/name-rev.c index 248a3eb260..0377fc1142 100644 --- a/builtin/name-rev.c +++ b/builtin/name-rev.c @@ -55,20 +55,16 @@ 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, distance + MERGE_TRAVERSAL_WEIGHT, 0); diff --git a/builtin/notes.c b/builtin/notes.c index 63f95fc554..515cebbeb8 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>]]"), @@ -191,7 +192,7 @@ static void prepare_note_data(const unsigned char *object, struct note_data *d, if (launch_editor(d->edit_path, &d->buf, NULL)) { die(_("Please supply the note contents using either -m or -F option")); } - stripspace(&d->buf, 1); + strbuf_stripspace(&d->buf, 1); } } @@ -214,7 +215,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 +232,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; @@ -737,6 +738,19 @@ 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) +{ + const char *value; + + if (git_config_get_string_const(key, &value)) + return 1; + if (parse_notes_merge_strategy(value, strategy)) + git_die_config(key, "unknown notes merge strategy %s", value); + + return 0; +} + static int merge(int argc, const char **argv, const char *prefix) { struct strbuf remote_ref = STRBUF_INIT, msg = STRBUF_INIT; @@ -795,24 +809,28 @@ static int merge(int argc, const char **argv, const char *prefix) expand_notes_ref(&remote_ref); o.remote_ref = remote_ref.buf; + t = init_notes_check("merge"); + 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 { + 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; - t = init_notes_check("merge"); + 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); + + 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,10 +843,15 @@ 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 */ + char *existing; /* 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 */ + existing = find_shared_symref("NOTES_MERGE_REF", default_notes_ref()); + if (existing) + die(_("A notes merge into %s is already in-progress at %s"), + default_notes_ref(), existing); if (create_symref("NOTES_MERGE_REF", default_notes_ref(), NULL)) die("Failed to store link to current notes ref (%s)", default_notes_ref()); diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c index 80fe8c7dc1..1c63f8f28c 100644 --- a/builtin/pack-objects.c +++ b/builtin/pack-objects.c @@ -25,8 +25,8 @@ #include "argv-array.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 }; @@ -2588,23 +2588,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 +2610,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, diff --git a/builtin/patch-id.c b/builtin/patch-id.c index ba34dac4d2..366ce5a5d4 100644 --- a/builtin/patch-id.c +++ b/builtin/patch-id.c @@ -165,7 +165,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 10b03d3e4c..8f4f052285 100644 --- a/builtin/prune.c +++ b/builtin/prune.c @@ -119,6 +119,9 @@ int cmd_prune(int argc, const char **argv, const char *prefix) argc = parse_options(argc, argv, prefix, options, prune_usage, 0); + if (repository_format_precious_objects) + die(_("cannot prune in a precious-objects repo")); + while (argc--) { unsigned char sha1[20]; const char *name = *argv++; diff --git a/builtin/pull.c b/builtin/pull.c new file mode 100644 index 0000000000..bf3fd3f9c8 --- /dev/null +++ b/builtin/pull.c @@ -0,0 +1,887 @@ +/* + * 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" + +enum rebase_type { + REBASE_INVALID = -1, + REBASE_FALSE = 0, + REBASE_TRUE, + REBASE_PRESERVE +}; + +/** + * 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; + + 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 struct argv_array opt_strategies = ARGV_ARRAY_INIT; +static struct argv_array opt_strategy_opts = ARGV_ARRAY_INIT; +static char *opt_gpg_sign; + +/* 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 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", + 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_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), + + /* 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_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; +} + +/** + * Returns 1 if there are unstaged changes, 0 otherwise. + */ +static int has_unstaged_changes(const char *prefix) +{ + struct rev_info rev_info; + int result; + + init_revisions(&rev_info, prefix); + DIFF_OPT_SET(&rev_info.diffopt, IGNORE_SUBMODULES); + DIFF_OPT_SET(&rev_info.diffopt, QUICK); + diff_setup_done(&rev_info.diffopt); + result = run_diff_files(&rev_info, 0); + return diff_result_code(&rev_info.diffopt, result); +} + +/** + * Returns 1 if there are uncommitted changes, 0 otherwise. + */ +static int has_uncommitted_changes(const char *prefix) +{ + struct rev_info rev_info; + int result; + + if (is_cache_unborn()) + return 0; + + init_revisions(&rev_info, prefix); + DIFF_OPT_SET(&rev_info.diffopt, IGNORE_SUBMODULES); + DIFF_OPT_SET(&rev_info.diffopt, QUICK); + add_head_to_pending(&rev_info); + diff_setup_done(&rev_info.diffopt); + result = run_diff_index(&rev_info, 1); + return diff_result_code(&rev_info.diffopt, result); +} + +/** + * If the work tree has unstaged or uncommitted changes, dies with the + * appropriate message. + */ +static void die_on_unclean_work_tree(const char *prefix) +{ + struct lock_file *lock_file = xcalloc(1, sizeof(*lock_file)); + int do_die = 0; + + hold_locked_index(lock_file, 0); + refresh_cache(REFRESH_QUIET); + update_index_if_able(&the_index, lock_file); + rollback_lock_file(lock_file); + + if (has_unstaged_changes(prefix)) { + error(_("Cannot pull with rebase: You have unstaged changes.")); + do_die = 1; + } + + if (has_uncommitted_changes(prefix)) { + if (do_die) + error(_("Additionally, your index contains uncommitted changes.")); + else + error(_("Cannot pull with rebase: Your index contains uncommitted changes.")); + do_die = 1; + } + + if (do_die) + exit(1); +} + +/** + * 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(&sb, fp, '\n') != 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 <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 <remote> <branch>"); + fprintf(stderr, "\n"); + fprintf_ln(stderr, _("If you wish to set tracking information for this branch you can do so with:\n" + "\n" + " git branch --set-upstream-to=%s/<branch> %s\n"), + remote_name, 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 (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); + + 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.sha1); + 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"); + 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); + + 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_default_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) { + int autostash = 0; + + if (is_null_sha1(orig_head) && !is_cache_unborn()) + die(_("Updating an unborn branch with changes added to the index.")); + + git_config_get_bool("rebase.autostash", &autostash); + if (!autostash) + die_on_unclean_work_tree(prefix); + + 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); + } else if (opt_rebase) { + if (merge_heads.nr > 1) + die(_("Cannot rebase onto multiple branches.")); + 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..3bda430b6b 100644 --- a/builtin/push.c +++ b/builtin/push.c @@ -9,6 +9,7 @@ #include "transport.h" #include "parse-options.h" #include "submodule.h" +#include "send-pack.h" static const char * const push_usage[] = { N_("git push [<options>] [<repository> [<refspec>...]]"), @@ -471,6 +472,24 @@ static int option_parse_recurse_submodules(const struct option *opt, 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 +505,23 @@ 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); + } + } } return git_default_config(k, v, NULL); @@ -495,6 +531,7 @@ 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 */ struct option options[] = { @@ -526,7 +563,9 @@ 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_END() }; @@ -534,6 +573,7 @@ int cmd_push(int argc, const char **argv, const char *prefix) 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")); diff --git a/builtin/read-tree.c b/builtin/read-tree.c index 43b47f72f1..8c693e7568 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 }; @@ -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..bcb624bc05 100644 --- a/builtin/receive-pack.c +++ b/builtin/receive-pack.c @@ -19,6 +19,7 @@ #include "tag.h" #include "gpg-interface.h" #include "sigchain.h" +#include "fsck.h" static const char receive_pack_usage[] = "git receive-pack <git-dir>"; @@ -36,6 +37,7 @@ 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; @@ -115,6 +117,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; @@ -258,10 +280,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; @@ -911,7 +933,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; @@ -1049,8 +1071,11 @@ 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]; + char cmd_oldh[GIT_SHA1_HEXSZ + 1], + cmd_newh[GIT_SHA1_HEXSZ + 1], + dst_oldh[GIT_SHA1_HEXSZ + 1], + dst_newh[GIT_SHA1_HEXSZ + 1]; int flag; strbuf_addf(&buf, "%s%s", get_git_namespace(), cmd->ref_name); @@ -1081,10 +1106,10 @@ 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)); + find_unique_abbrev_r(cmd_oldh, cmd->old_sha1, DEFAULT_ABBREV); + find_unique_abbrev_r(cmd_newh, cmd->new_sha1, DEFAULT_ABBREV); + find_unique_abbrev_r(dst_oldh, dst_cmd->old_sha1, DEFAULT_ABBREV); + find_unique_abbrev_r(dst_newh, 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, @@ -1490,7 +1515,8 @@ static const char *unpack(int err_fd, struct shallow_info *si) 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); child.no_stdout = 1; child.err = err_fd; child.git_cmd = 1; @@ -1498,17 +1524,21 @@ 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 (fsck_objects) - argv_array_push(&child.args, "--strict"); + argv_array_pushf(&child.args, "--strict%s", + fsck_msg_types.buf); if (fix_thin) argv_array_push(&child.args, "--fix-thin"); child.out = -1; diff --git a/builtin/reflog.c b/builtin/reflog.c index c2eb8ff840..cf1145e635 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; @@ -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)) @@ -427,7 +425,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 +697,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 +748,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..e3cd25d580 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: @@ -142,36 +143,11 @@ static const char **parse_argv(const char *arg, const char *service) 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(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(stdin_fd, "%s %s%chost=%s%c", serv, repo, 0, + vhost, 0); } static int run_child(const char *arg, const char *service) diff --git a/builtin/remote.c b/builtin/remote.c index f4a6ec9f13..e4c3ea130c 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>"), @@ -581,7 +587,6 @@ 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++) @@ -601,11 +606,9 @@ static int migrate_file(struct remote *remote) return error(_("Could not append '%s' to '%s'"), remote->fetch_refspec[i], buf.buf); if (remote->origin == REMOTE_REMOTES) - path = git_path("remotes/%s", remote->name); + 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; } @@ -746,26 +749,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[] = { @@ -822,7 +805,7 @@ static int rm(int argc, const char **argv) strbuf_release(&buf); if (!result) - result = remove_branches(&branches); + result = delete_refs(&branches); string_list_clear(&branches, 0); if (skipped.nr) { @@ -1334,19 +1317,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); 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/")); @@ -1497,6 +1473,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]; + + if (!remote_is_configured(remotename)) + die(_("No such remote '%s'"), remotename); + remote = remote_get(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; @@ -1606,6 +1633,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..945611006a 100644 --- a/builtin/repack.c +++ b/builtin/repack.c @@ -193,6 +193,9 @@ 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 (pack_kept_objects < 0) pack_kept_objects = write_bitmaps; @@ -285,8 +288,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 +296,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 +304,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 +318,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 +372,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); + free(fname); } } diff --git a/builtin/replace.c b/builtin/replace.c index 0d52e7fa1d..6b3c469a33 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)) 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..c503e75a59 100644 --- a/builtin/reset.c +++ b/builtin/reset.c @@ -36,7 +36,7 @@ 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) diff --git a/builtin/rev-list.c b/builtin/rev-list.c index c0b4b53652..491d298fa2 100644 --- a/builtin/rev-list.c +++ b/builtin/rev-list.c @@ -217,7 +217,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; @@ -242,7 +242,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.sha1); if (flags & BISECT_SHOW_ALL) { traverse_commit_list(revs, show_commit, show_object, info); @@ -350,6 +350,9 @@ 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); diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c index b6232390a6..e92a782f77 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.sha1, NULL); } } *dotdot = '.'; @@ -371,6 +368,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; @@ -400,7 +398,7 @@ 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) { const char *s; - const char *end; + const char *help; struct option *o; if (!sb.len) @@ -410,54 +408,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); diff --git a/builtin/send-pack.c b/builtin/send-pack.c index b961e5ae78..f6e5d643c1 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; @@ -242,7 +227,7 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix) */ if ((refspecs && (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..007cc66a03 100644 --- a/builtin/shortlog.c +++ b/builtin/shortlog.c @@ -138,7 +138,7 @@ void shortlog_add_commit(struct shortlog *log, struct commit *commit) ctx.abbrev = log->abbrev; ctx.subject = ""; ctx.after_subject = ""; - ctx.date_mode = DATE_NORMAL; + ctx.date_mode.type = DATE_NORMAL; ctx.output_encoding = get_log_output_encoding(); pretty_print_commit(&ctx, commit, &ufbuf); buffer = ufbuf.buf; diff --git a/builtin/show-branch.c b/builtin/show-branch.c index 323f857463..e17744bc0d 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) @@ -504,7 +492,7 @@ 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)) { @@ -567,16 +555,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 +677,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, @@ -730,7 +711,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,6 +724,8 @@ 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"); @@ -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); } @@ -924,7 +910,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..264c392007 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 }; diff --git a/builtin/stripspace.c b/builtin/stripspace.c index 1259ed708b..7ff8434f7c 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,45 @@ 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 blank 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) 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..f4c3eff179 --- /dev/null +++ b/builtin/submodule--helper.c @@ -0,0 +1,282 @@ +#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" + +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 *max_prefix, *ps_matched = NULL; + int max_prefix_len; + parse_pathspec(pathspec, 0, + PATHSPEC_PREFER_FULL | + PATHSPEC_STRIP_SUBMODULE_SLASH_CHEAP, + prefix, argv); + + /* Find common prefix for all pathspec's */ + max_prefix = common_prefix(pathspec); + max_prefix_len = max_prefix ? strlen(max_prefix) : 0; + + 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 (!S_ISGITLINK(ce->ce_mode) || + !match_pathspec(pathspec, ce->name, ce_namelen(ce), + max_prefix_len, ps_matched, 1)) + 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++; + } + free(max_prefix); + + 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) { + printf("#unmatched\n"); + 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, sha1_to_hex(ce->sha1), ce_stage(ce)); + + utf8_fprintf(stdout, "%s\n", ce->name); + } + 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, const char *reference, int quiet) +{ + struct child_process cp; + child_process_init(&cp); + + argv_array_push(&cp.args, "clone"); + argv_array_push(&cp.args, "--no-checkout"); + if (quiet) + argv_array_push(&cp.args, "--quiet"); + if (depth && *depth) + argv_array_pushl(&cp.args, "--depth", depth, NULL); + if (reference && *reference) + argv_array_pushl(&cp.args, "--reference", reference, 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; + cp.env = local_repo_env; + cp.no_stdin = 1; + + return run_command(&cp); +} + +static int module_clone(int argc, const char **argv, const char *prefix) +{ + const char *path = NULL, *name = NULL, *url = NULL; + const char *reference = NULL, *depth = NULL; + int quiet = 0; + FILE *submodule_dot_git; + char *sm_gitdir, *cwd, *p; + struct strbuf rel_path = STRBUF_INIT; + struct strbuf sb = STRBUF_INIT; + + 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(0, "reference", &reference, + N_("string"), + 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_END() + }; + + const char *const git_submodule_helper_usage[] = { + N_("git submodule--helper clone [--prefix=<path>] [--quiet] " + "[--reference <repository>] [--name <name>] [--url <url>]" + "[--depth <depth>] [--] [<path>...]"), + NULL + }; + + argc = parse_options(argc, argv, prefix, module_clone_options, + git_submodule_helper_usage, 0); + + strbuf_addf(&sb, "%s/modules/%s", get_git_dir(), name); + sm_gitdir = strbuf_detach(&sb, NULL); + + if (!file_exists(sm_gitdir)) { + if (safe_create_leading_directories_const(sm_gitdir) < 0) + die(_("could not create directory '%s'"), sm_gitdir); + if (clone_submodule(path, sm_gitdir, url, depth, reference, quiet)) + 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. */ + if (safe_create_leading_directories_const(path) < 0) + die(_("could not create directory '%s'"), path); + + if (path && *path) + strbuf_addf(&sb, "%s/.git", path); + else + strbuf_addstr(&sb, ".git"); + + 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(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); + + cwd = xgetcwd(); + /* Redirect the worktree of the submodule in the superproject's config */ + if (!is_absolute_path(sm_gitdir)) { + strbuf_addf(&sb, "%s/%s", cwd, sm_gitdir); + free(sm_gitdir); + sm_gitdir = strbuf_detach(&sb, NULL); + } + + strbuf_addf(&sb, "%s/%s", cwd, path); + 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(sb.buf, sm_gitdir, &rel_path)); + strbuf_release(&sb); + strbuf_release(&rel_path); + free(sm_gitdir); + free(cwd); + free(p); + 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}, +}; + +int cmd_submodule__helper(int argc, const char **argv, const char *prefix) +{ + int i; + if (argc < 2) + die(_("fatal: 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(_("fatal: '%s' is not a valid submodule--helper " + "subcommand"), argv[1]); +} diff --git a/builtin/tag.c b/builtin/tag.c index 5f6cdc5a03..8db8c87e57 100644 --- a/builtin/tag.c +++ b/builtin/tag.c @@ -17,271 +17,50 @@ #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 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)); - - /* 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; + memset(&array, 0, sizeof(array)); - 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 == -1) + filter->lines = 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:short)%(end)", + filter->lines); + format = to_free; + } else + format = "%(refname:short)"; } - 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; } @@ -348,35 +127,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,11 +154,12 @@ 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; } @@ -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 create_reflog = 0; + int annotate = 0, force = 0; int cmdmode = 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); @@ -650,11 +394,13 @@ int cmd_tag(int argc, const char **argv, const char *prefix) 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') @@ -733,7 +480,8 @@ int cmd_tag(int argc, const char **argv, const char *prefix) 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..7c3e79c48d 100644 --- a/builtin/unpack-objects.c +++ b/builtin/unpack-objects.c @@ -13,13 +13,14 @@ #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 git_SHA_CTX ctx; +static struct fsck_options fsck_options = FSCK_OPTIONS_STRICT; /* * When running under --strict mode, objects whose reachability are @@ -178,7 +179,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; @@ -203,10 +204,10 @@ 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)) + if (fsck_object(obj, obj_buf->buffer, obj_buf->size, &fsck_options)) die("Error in object"); - if (fsck_walk(obj, check_object, NULL)) + fsck_options.walk = check_object; + if (fsck_walk(obj, NULL, &fsck_options)) die("Error on reachable objects of %s", sha1_to_hex(obj->sha1)); write_cached_object(obj, obj_buf); return 0; @@ -217,7 +218,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); } } @@ -529,6 +530,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; 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..dbfe14f3fe 100644 --- a/builtin/upload-archive.c +++ b/builtin/upload-archive.c @@ -49,15 +49,14 @@ int cmd_upload_archive_writer(int argc, const char **argv, const char *prefix) __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) 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..00663f6a30 100644 --- a/builtin/verify-tag.c +++ b/builtin/verify-tag.c @@ -18,21 +18,30 @@ static const char * const verify_tag_usage[] = { NULL }; -static int run_gpg_verify(const char *buf, unsigned long size, int verbose) +static int run_gpg_verify(const char *buf, unsigned long size, unsigned flags) { + struct signature_check sigc; int len; + int ret; + + memset(&sigc, 0, sizeof(sigc)); len = parse_signature(buf, size); - if (verbose) - write_in_full(1, buf, len); - if (size == len) + if (size == len) { + if (flags & GPG_VERIFY_VERBOSE) + write_in_full(1, buf, len); return error("no signature found"); + } + + ret = check_signature(buf, len, buf + len, size - len, &sigc); + print_signature_buffer(&sigc, flags); - return verify_signed_buffer(buf, len, buf + len, size - len, NULL, NULL); + signature_check_clear(&sigc); + return ret; } -static int verify_tag(const char *name, int verbose) +static int verify_tag(const char *name, unsigned flags) { enum object_type type; unsigned char sha1[20]; @@ -52,7 +61,7 @@ static int verify_tag(const char *name, int verbose) if (!buf) return error("%s: unable to read file.", name); - ret = run_gpg_verify(buf, size, verbose); + ret = run_gpg_verify(buf, size, flags); free(buf); return ret; @@ -69,8 +78,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 +92,14 @@ int cmd_verify_tag(int argc, const char **argv, const char *prefix) if (argc <= i) usage_with_options(verify_tag_usage, verify_tag_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_tag(argv[i++], verbose)) + if (verify_tag(argv[i++], flags)) had_error = 1; return had_error; } diff --git a/builtin/worktree.c b/builtin/worktree.c index 6a264ee749..d281f6d887 100644 --- a/builtin/worktree.c +++ b/builtin/worktree.c @@ -3,16 +3,28 @@ #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 add [<options>] <path> [<branch>]"), N_("git worktree prune [<options>]"), + N_("git worktree list [<options>]"), NULL }; +struct add_opts { + int force; + int detach; + const char *new_branch; + int force_new_branch; +}; + static int show_only; static int verbose; static unsigned long expire; @@ -172,19 +184,35 @@ static const char *worktree_basename(const char *path, int *olen) return name; } -static int add_worktree(const char *path, const char **child_argv) +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; + struct argv_array child_env = ARGV_ARRAY_INIT; int counter = 0, len, ret; - unsigned char rev[20]; + 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->force_new_branch) /* definitely a branch */ + ; + else 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); + } 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)); @@ -213,7 +241,7 @@ static int add_worktree(const char *path, const char **child_argv) * after the preparation is over. */ strbuf_addf(&sb, "%s/locked", sb_repo.buf); - write_file(sb.buf, 1, "initializing\n"); + write_file(sb.buf, "initializing"); strbuf_addf(&sb_git, "%s/.git", path); if (safe_create_leading_directories_const(sb_git.buf)) @@ -223,37 +251,45 @@ static int add_worktree(const char *path, const char **child_argv) 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", + 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. Moreover, HEAD - * in the new worktree must resolve to the same value as HEAD in - * the current tree since the command invoked to populate the new - * worktree will be handed the branch/ref specified by the user. - * For instance, if the user asks for the new worktree to be based - * at HEAD~5, then the resolved HEAD~5 in the new worktree must - * match the resolved HEAD~5 in the current tree in order to match - * the user's expectation. + * 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. */ - if (!resolve_ref_unsafe("HEAD", 0, rev, NULL)) - die(_("unable to resolve HEAD")); strbuf_reset(&sb); strbuf_addf(&sb, "%s/HEAD", sb_repo.buf); - write_file(sb.buf, 1, "%s\n", sha1_to_hex(rev)); + write_file(sb.buf, "0000000000000000000000000000000000000000"); strbuf_reset(&sb); strbuf_addf(&sb, "%s/commondir", sb_repo.buf); - write_file(sb.buf, 1, "../..\n"); + write_file(sb.buf, "../.."); - fprintf_ln(stderr, _("Enter %s (identifier %s)"), path, name); + fprintf_ln(stderr, _("Preparing %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); + 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); memset(&cp, 0, sizeof(cp)); cp.git_cmd = 1; - cp.argv = child_argv; + + if (commit) + argv_array_pushl(&cp.args, "update-ref", "HEAD", + sha1_to_hex(commit->object.sha1), 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; + + 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) { is_junk = 0; @@ -262,10 +298,13 @@ static int add_worktree(const char *path, const char **child_argv) 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; @@ -273,47 +312,137 @@ static int add_worktree(const char *path, const char **child_argv) static int add(int ac, const char **av, const char *prefix) { - int force = 0, detach = 0; - const char *new_branch = NULL, *new_branch_force = NULL; + struct add_opts opts; + const char *new_branch_force = NULL; const char *path, *branch; - struct argv_array cmd = ARGV_ARRAY_INIT; struct option options[] = { - OPT__FORCE(&force, N_("checkout <branch> even if already checked out in other worktree")), - OPT_STRING('b', NULL, &new_branch, N_("branch"), + 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", &detach, N_("detach HEAD at named commit")), + OPT_BOOL(0, "detach", &opts.detach, N_("detach HEAD at named commit")), OPT_END() }; + memset(&opts, 0, sizeof(opts)); ac = parse_options(ac, av, prefix, options, worktree_usage, 0); - if (new_branch && new_branch_force) - die(_("-b and -B are mutually exclusive")); + 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 ? prefix_filename(prefix, strlen(prefix), av[0]) : av[0]; branch = ac < 2 ? "HEAD" : av[1]; - if (ac < 2 && !new_branch && !new_branch_force) { + opts.force_new_branch = !!new_branch_force; + if (opts.force_new_branch) + opts.new_branch = new_branch_force; + + if (ac < 2 && !opts.new_branch && !opts.detach) { int n; const char *s = worktree_basename(path, &n); - new_branch = xstrndup(s, n); + opts.new_branch = xstrndup(s, n); + } + + if (opts.new_branch) { + struct child_process cp; + memset(&cp, 0, sizeof(cp)); + 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 + 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_addf(&sb, "[%s]", shorten_unambiguous_ref(wt->head_ref, 0)); + else + strbuf_addstr(&sb, "(detached HEAD)"); } + printf("%s\n", sb.buf); - argv_array_push(&cmd, "checkout"); - if (force) - argv_array_push(&cmd, "--ignore-other-worktrees"); - if (new_branch) - argv_array_pushl(&cmd, "-b", new_branch, NULL); - if (new_branch_force) - argv_array_pushl(&cmd, "-B", new_branch_force, NULL); - if (detach) - argv_array_push(&cmd, "--detach"); - argv_array_push(&cmd, branch); - - return add_worktree(path, cmd.argv); + 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(); + 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; } int cmd_worktree(int ac, const char **av, const char *prefix) @@ -328,5 +457,7 @@ int cmd_worktree(int ac, const char **av, const char *prefix) 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); usage_with_options(worktree_usage, options); } |