From 043a4492b3b7da6496617201c369cff6ab7c26f2 Mon Sep 17 00:00:00 2001 From: Ramkumar Ramachandra Date: Wed, 11 Jan 2012 23:45:57 +0530 Subject: sequencer: factor code out of revert builtin Expose the cherry-picking machinery through a public sequencer_pick_revisions() (renamed from pick_revisions() in builtin/revert.c), so that cherry-picking and reverting are special cases of a general sequencer operation. The cherry-pick builtin is now a thin wrapper that does command-line argument parsing before calling into sequencer_pick_revisions(). In the future, we can write a new "foo" builtin that calls into the sequencer like: memset(&opts, 0, sizeof(opts)); opts.action = REPLAY_FOO; opts.revisions = xmalloc(sizeof(*opts.revs)); parse_args_populate_opts(argc, argv, &opts); init_revisions(opts.revs); sequencer_pick_revisions(&opts); This patch does not intend to make any functional changes. Check with: $ git blame -s -C HEAD^..HEAD -- sequencer.c | grep -C3 '^[^^]' Signed-off-by: Ramkumar Ramachandra Reviewed-by: Jonathan Nieder Signed-off-by: Junio C Hamano --- sequencer.c | 918 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 917 insertions(+), 1 deletion(-) (limited to 'sequencer.c') diff --git a/sequencer.c b/sequencer.c index d1f28a6945..54771196c3 100644 --- a/sequencer.c +++ b/sequencer.c @@ -1,7 +1,20 @@ #include "cache.h" #include "sequencer.h" -#include "strbuf.h" #include "dir.h" +#include "object.h" +#include "commit.h" +#include "tag.h" +#include "run-command.h" +#include "exec_cmd.h" +#include "utf8.h" +#include "cache-tree.h" +#include "diff.h" +#include "revision.h" +#include "rerere.h" +#include "merge-recursive.h" +#include "refs.h" + +#define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION" void remove_sequencer_state(void) { @@ -11,3 +24,906 @@ void remove_sequencer_state(void) remove_dir_recursively(&seq_dir, 0); strbuf_release(&seq_dir); } + +static const char *action_name(const struct replay_opts *opts) +{ + return opts->action == REPLAY_REVERT ? "revert" : "cherry-pick"; +} + +static char *get_encoding(const char *message); + +struct commit_message { + char *parent_label; + const char *label; + const char *subject; + char *reencoded_message; + const char *message; +}; + +static int get_message(struct commit *commit, struct commit_message *out) +{ + const char *encoding; + const char *abbrev, *subject; + int abbrev_len, subject_len; + char *q; + + if (!commit->buffer) + return -1; + encoding = get_encoding(commit->buffer); + if (!encoding) + encoding = "UTF-8"; + if (!git_commit_encoding) + git_commit_encoding = "UTF-8"; + + out->reencoded_message = NULL; + out->message = commit->buffer; + if (strcmp(encoding, git_commit_encoding)) + out->reencoded_message = reencode_string(commit->buffer, + git_commit_encoding, encoding); + if (out->reencoded_message) + out->message = out->reencoded_message; + + abbrev = find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV); + abbrev_len = strlen(abbrev); + + subject_len = find_commit_subject(out->message, &subject); + + out->parent_label = xmalloc(strlen("parent of ") + abbrev_len + + strlen("... ") + subject_len + 1); + q = out->parent_label; + q = mempcpy(q, "parent of ", strlen("parent of ")); + out->label = q; + q = mempcpy(q, abbrev, abbrev_len); + q = mempcpy(q, "... ", strlen("... ")); + out->subject = q; + q = mempcpy(q, subject, subject_len); + *q = '\0'; + return 0; +} + +static void free_message(struct commit_message *msg) +{ + free(msg->parent_label); + free(msg->reencoded_message); +} + +static char *get_encoding(const char *message) +{ + const char *p = message, *eol; + + while (*p && *p != '\n') { + for (eol = p + 1; *eol && *eol != '\n'; eol++) + ; /* do nothing */ + if (!prefixcmp(p, "encoding ")) { + char *result = xmalloc(eol - 8 - p); + strlcpy(result, p + 9, eol - 8 - p); + return result; + } + p = eol; + if (*p == '\n') + p++; + } + return NULL; +} + +static void write_cherry_pick_head(struct commit *commit, const char *pseudoref) +{ + const char *filename; + int fd; + struct strbuf buf = STRBUF_INIT; + + strbuf_addf(&buf, "%s\n", sha1_to_hex(commit->object.sha1)); + + filename = git_path("%s", pseudoref); + fd = open(filename, O_WRONLY | O_CREAT, 0666); + if (fd < 0) + die_errno(_("Could not open '%s' for writing"), filename); + if (write_in_full(fd, buf.buf, buf.len) != buf.len || close(fd)) + die_errno(_("Could not write to '%s'"), filename); + strbuf_release(&buf); +} + +static void print_advice(int show_hint) +{ + char *msg = getenv("GIT_CHERRY_PICK_HELP"); + + if (msg) { + fprintf(stderr, "%s\n", msg); + /* + * A conflict has occured but the porcelain + * (typically rebase --interactive) wants to take care + * of the commit itself so remove CHERRY_PICK_HEAD + */ + unlink(git_path("CHERRY_PICK_HEAD")); + return; + } + + if (show_hint) { + advise("after resolving the conflicts, mark the corrected paths"); + advise("with 'git add ' or 'git rm '"); + advise("and commit the result with 'git commit'"); + } +} + +static void write_message(struct strbuf *msgbuf, const char *filename) +{ + static struct lock_file msg_file; + + int msg_fd = hold_lock_file_for_update(&msg_file, filename, + LOCK_DIE_ON_ERROR); + if (write_in_full(msg_fd, msgbuf->buf, msgbuf->len) < 0) + die_errno(_("Could not write to %s"), filename); + strbuf_release(msgbuf); + if (commit_lock_file(&msg_file) < 0) + die(_("Error wrapping up %s"), filename); +} + +static struct tree *empty_tree(void) +{ + return lookup_tree((const unsigned char *)EMPTY_TREE_SHA1_BIN); +} + +static int error_dirty_index(struct replay_opts *opts) +{ + if (read_cache_unmerged()) + return error_resolve_conflict(action_name(opts)); + + /* Different translation strings for cherry-pick and revert */ + if (opts->action == REPLAY_PICK) + error(_("Your local changes would be overwritten by cherry-pick.")); + else + error(_("Your local changes would be overwritten by revert.")); + + if (advice_commit_before_merge) + advise(_("Commit your changes or stash them to proceed.")); + return -1; +} + +static int fast_forward_to(const unsigned char *to, const unsigned char *from) +{ + struct ref_lock *ref_lock; + + read_cache(); + if (checkout_fast_forward(from, to)) + exit(1); /* the callee should have complained already */ + ref_lock = lock_any_ref_for_update("HEAD", from, 0); + return write_ref_sha1(ref_lock, to, "cherry-pick"); +} + +static int do_recursive_merge(struct commit *base, struct commit *next, + const char *base_label, const char *next_label, + unsigned char *head, struct strbuf *msgbuf, + struct replay_opts *opts) +{ + struct merge_options o; + struct tree *result, *next_tree, *base_tree, *head_tree; + int clean, index_fd; + const char **xopt; + static struct lock_file index_lock; + + index_fd = hold_locked_index(&index_lock, 1); + + read_cache(); + + init_merge_options(&o); + o.ancestor = base ? base_label : "(empty tree)"; + o.branch1 = "HEAD"; + o.branch2 = next ? next_label : "(empty tree)"; + + head_tree = parse_tree_indirect(head); + next_tree = next ? next->tree : empty_tree(); + base_tree = base ? base->tree : empty_tree(); + + for (xopt = opts->xopts; xopt != opts->xopts + opts->xopts_nr; xopt++) + parse_merge_opt(&o, *xopt); + + clean = merge_trees(&o, + head_tree, + next_tree, base_tree, &result); + + if (active_cache_changed && + (write_cache(index_fd, active_cache, active_nr) || + commit_locked_index(&index_lock))) + /* TRANSLATORS: %s will be "revert" or "cherry-pick" */ + die(_("%s: Unable to write new index file"), action_name(opts)); + rollback_lock_file(&index_lock); + + if (!clean) { + int i; + strbuf_addstr(msgbuf, "\nConflicts:\n\n"); + for (i = 0; i < active_nr;) { + struct cache_entry *ce = active_cache[i++]; + if (ce_stage(ce)) { + strbuf_addch(msgbuf, '\t'); + strbuf_addstr(msgbuf, ce->name); + strbuf_addch(msgbuf, '\n'); + while (i < active_nr && !strcmp(ce->name, + active_cache[i]->name)) + i++; + } + } + } + + return !clean; +} + +/* + * If we are cherry-pick, and if the merge did not result in + * hand-editing, we will hit this commit and inherit the original + * author date and name. + * If we are revert, or if our cherry-pick results in a hand merge, + * we had better say that the current user is responsible for that. + */ +static int run_git_commit(const char *defmsg, struct replay_opts *opts) +{ + /* 6 is max possible length of our args array including NULL */ + const char *args[6]; + int i = 0; + + args[i++] = "commit"; + args[i++] = "-n"; + if (opts->signoff) + args[i++] = "-s"; + if (!opts->edit) { + args[i++] = "-F"; + args[i++] = defmsg; + } + args[i] = NULL; + + return run_command_v_opt(args, RUN_GIT_CMD); +} + +static int do_pick_commit(struct commit *commit, struct replay_opts *opts) +{ + unsigned char head[20]; + struct commit *base, *next, *parent; + const char *base_label, *next_label; + struct commit_message msg = { NULL, NULL, NULL, NULL, NULL }; + char *defmsg = NULL; + struct strbuf msgbuf = STRBUF_INIT; + int res; + + if (opts->no_commit) { + /* + * We do not intend to commit immediately. We just want to + * merge the differences in, so let's compute the tree + * that represents the "current" state for merge-recursive + * to work on. + */ + if (write_cache_as_tree(head, 0, NULL)) + die (_("Your index file is unmerged.")); + } else { + if (get_sha1("HEAD", head)) + return error(_("You do not have a valid HEAD")); + if (index_differs_from("HEAD", 0)) + return error_dirty_index(opts); + } + discard_cache(); + + if (!commit->parents) { + parent = NULL; + } + else if (commit->parents->next) { + /* Reverting or cherry-picking a merge commit */ + int cnt; + struct commit_list *p; + + if (!opts->mainline) + return error(_("Commit %s is a merge but no -m option was given."), + sha1_to_hex(commit->object.sha1)); + + for (cnt = 1, p = commit->parents; + cnt != opts->mainline && p; + cnt++) + p = p->next; + if (cnt != opts->mainline || !p) + return error(_("Commit %s does not have parent %d"), + sha1_to_hex(commit->object.sha1), opts->mainline); + parent = p->item; + } else if (0 < opts->mainline) + return error(_("Mainline was specified but commit %s is not a merge."), + sha1_to_hex(commit->object.sha1)); + else + parent = commit->parents->item; + + if (opts->allow_ff && parent && !hashcmp(parent->object.sha1, head)) + return fast_forward_to(commit->object.sha1, head); + + if (parent && parse_commit(parent) < 0) + /* TRANSLATORS: The first %s will be "revert" or + "cherry-pick", the second %s a SHA1 */ + return error(_("%s: cannot parse parent commit %s"), + action_name(opts), sha1_to_hex(parent->object.sha1)); + + if (get_message(commit, &msg) != 0) + return error(_("Cannot get commit message for %s"), + sha1_to_hex(commit->object.sha1)); + + /* + * "commit" is an existing commit. We would want to apply + * the difference it introduces since its first parent "prev" + * on top of the current HEAD if we are cherry-pick. Or the + * reverse of it if we are revert. + */ + + defmsg = git_pathdup("MERGE_MSG"); + + if (opts->action == REPLAY_REVERT) { + base = commit; + base_label = msg.label; + next = parent; + next_label = msg.parent_label; + strbuf_addstr(&msgbuf, "Revert \""); + strbuf_addstr(&msgbuf, msg.subject); + strbuf_addstr(&msgbuf, "\"\n\nThis reverts commit "); + strbuf_addstr(&msgbuf, sha1_to_hex(commit->object.sha1)); + + if (commit->parents && commit->parents->next) { + strbuf_addstr(&msgbuf, ", reversing\nchanges made to "); + strbuf_addstr(&msgbuf, sha1_to_hex(parent->object.sha1)); + } + strbuf_addstr(&msgbuf, ".\n"); + } else { + const char *p; + + base = parent; + base_label = msg.parent_label; + next = commit; + next_label = msg.label; + + /* + * Append the commit log message to msgbuf; it starts + * after the tree, parent, author, committer + * information followed by "\n\n". + */ + p = strstr(msg.message, "\n\n"); + if (p) { + p += 2; + strbuf_addstr(&msgbuf, p); + } + + if (opts->record_origin) { + strbuf_addstr(&msgbuf, "(cherry picked from commit "); + strbuf_addstr(&msgbuf, sha1_to_hex(commit->object.sha1)); + strbuf_addstr(&msgbuf, ")\n"); + } + } + + if (!opts->strategy || !strcmp(opts->strategy, "recursive") || opts->action == REPLAY_REVERT) { + res = do_recursive_merge(base, next, base_label, next_label, + head, &msgbuf, opts); + write_message(&msgbuf, defmsg); + } else { + struct commit_list *common = NULL; + struct commit_list *remotes = NULL; + + write_message(&msgbuf, defmsg); + + commit_list_insert(base, &common); + commit_list_insert(next, &remotes); + res = try_merge_command(opts->strategy, opts->xopts_nr, opts->xopts, + common, sha1_to_hex(head), remotes); + free_commit_list(common); + free_commit_list(remotes); + } + + /* + * If the merge was clean or if it failed due to conflict, we write + * CHERRY_PICK_HEAD for the subsequent invocation of commit to use. + * However, if the merge did not even start, then we don't want to + * write it at all. + */ + if (opts->action == REPLAY_PICK && !opts->no_commit && (res == 0 || res == 1)) + write_cherry_pick_head(commit, "CHERRY_PICK_HEAD"); + if (opts->action == REPLAY_REVERT && ((opts->no_commit && res == 0) || res == 1)) + write_cherry_pick_head(commit, "REVERT_HEAD"); + + if (res) { + error(opts->action == REPLAY_REVERT + ? _("could not revert %s... %s") + : _("could not apply %s... %s"), + find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV), + msg.subject); + print_advice(res == 1); + rerere(opts->allow_rerere_auto); + } else { + if (!opts->no_commit) + res = run_git_commit(defmsg, opts); + } + + free_message(&msg); + free(defmsg); + + return res; +} + +static void prepare_revs(struct replay_opts *opts) +{ + if (opts->action != REPLAY_REVERT) + opts->revs->reverse ^= 1; + + if (prepare_revision_walk(opts->revs)) + die(_("revision walk setup failed")); + + if (!opts->revs->commits) + die(_("empty commit set passed")); +} + +static void read_and_refresh_cache(struct replay_opts *opts) +{ + static struct lock_file index_lock; + int index_fd = hold_locked_index(&index_lock, 0); + if (read_index_preload(&the_index, NULL) < 0) + die(_("git %s: failed to read the index"), action_name(opts)); + refresh_index(&the_index, REFRESH_QUIET|REFRESH_UNMERGED, NULL, NULL, NULL); + if (the_index.cache_changed) { + if (write_index(&the_index, index_fd) || + commit_locked_index(&index_lock)) + die(_("git %s: failed to refresh the index"), action_name(opts)); + } + rollback_lock_file(&index_lock); +} + +/* + * Append a commit to the end of the commit_list. + * + * next starts by pointing to the variable that holds the head of an + * empty commit_list, and is updated to point to the "next" field of + * the last item on the list as new commits are appended. + * + * Usage example: + * + * struct commit_list *list; + * struct commit_list **next = &list; + * + * next = commit_list_append(c1, next); + * next = commit_list_append(c2, next); + * assert(commit_list_count(list) == 2); + * return list; + */ +static struct commit_list **commit_list_append(struct commit *commit, + struct commit_list **next) +{ + struct commit_list *new = xmalloc(sizeof(struct commit_list)); + new->item = commit; + *next = new; + new->next = NULL; + return &new->next; +} + +static int format_todo(struct strbuf *buf, struct commit_list *todo_list, + struct replay_opts *opts) +{ + struct commit_list *cur = NULL; + const char *sha1_abbrev = NULL; + const char *action_str = opts->action == REPLAY_REVERT ? "revert" : "pick"; + const char *subject; + int subject_len; + + for (cur = todo_list; cur; cur = cur->next) { + sha1_abbrev = find_unique_abbrev(cur->item->object.sha1, DEFAULT_ABBREV); + subject_len = find_commit_subject(cur->item->buffer, &subject); + strbuf_addf(buf, "%s %s %.*s\n", action_str, sha1_abbrev, + subject_len, subject); + } + return 0; +} + +static struct commit *parse_insn_line(char *bol, char *eol, struct replay_opts *opts) +{ + unsigned char commit_sha1[20]; + enum replay_action action; + char *end_of_object_name; + int saved, status, padding; + + if (!prefixcmp(bol, "pick")) { + action = REPLAY_PICK; + bol += strlen("pick"); + } else if (!prefixcmp(bol, "revert")) { + action = REPLAY_REVERT; + bol += strlen("revert"); + } else + return NULL; + + /* Eat up extra spaces/ tabs before object name */ + padding = strspn(bol, " \t"); + if (!padding) + return NULL; + bol += padding; + + end_of_object_name = bol + strcspn(bol, " \t\n"); + saved = *end_of_object_name; + *end_of_object_name = '\0'; + status = get_sha1(bol, commit_sha1); + *end_of_object_name = saved; + + /* + * Verify that the action matches up with the one in + * opts; we don't support arbitrary instructions + */ + if (action != opts->action) { + const char *action_str; + action_str = action == REPLAY_REVERT ? "revert" : "cherry-pick"; + error(_("Cannot %s during a %s"), action_str, action_name(opts)); + return NULL; + } + + if (status < 0) + return NULL; + + return lookup_commit_reference(commit_sha1); +} + +static int parse_insn_buffer(char *buf, struct commit_list **todo_list, + struct replay_opts *opts) +{ + struct commit_list **next = todo_list; + struct commit *commit; + char *p = buf; + int i; + + for (i = 1; *p; i++) { + char *eol = strchrnul(p, '\n'); + commit = parse_insn_line(p, eol, opts); + if (!commit) + return error(_("Could not parse line %d."), i); + next = commit_list_append(commit, next); + p = *eol ? eol + 1 : eol; + } + if (!*todo_list) + return error(_("No commits parsed.")); + return 0; +} + +static void read_populate_todo(struct commit_list **todo_list, + struct replay_opts *opts) +{ + const char *todo_file = git_path(SEQ_TODO_FILE); + struct strbuf buf = STRBUF_INIT; + int fd, res; + + fd = open(todo_file, O_RDONLY); + if (fd < 0) + die_errno(_("Could not open %s"), todo_file); + if (strbuf_read(&buf, fd, 0) < 0) { + close(fd); + strbuf_release(&buf); + die(_("Could not read %s."), todo_file); + } + close(fd); + + res = parse_insn_buffer(buf.buf, todo_list, opts); + strbuf_release(&buf); + if (res) + die(_("Unusable instruction sheet: %s"), todo_file); +} + +static int populate_opts_cb(const char *key, const char *value, void *data) +{ + struct replay_opts *opts = data; + int error_flag = 1; + + if (!value) + error_flag = 0; + else if (!strcmp(key, "options.no-commit")) + opts->no_commit = git_config_bool_or_int(key, value, &error_flag); + else if (!strcmp(key, "options.edit")) + opts->edit = git_config_bool_or_int(key, value, &error_flag); + else if (!strcmp(key, "options.signoff")) + opts->signoff = git_config_bool_or_int(key, value, &error_flag); + else if (!strcmp(key, "options.record-origin")) + opts->record_origin = git_config_bool_or_int(key, value, &error_flag); + else if (!strcmp(key, "options.allow-ff")) + opts->allow_ff = git_config_bool_or_int(key, value, &error_flag); + else if (!strcmp(key, "options.mainline")) + opts->mainline = git_config_int(key, value); + else if (!strcmp(key, "options.strategy")) + git_config_string(&opts->strategy, key, value); + else if (!strcmp(key, "options.strategy-option")) { + ALLOC_GROW(opts->xopts, opts->xopts_nr + 1, opts->xopts_alloc); + opts->xopts[opts->xopts_nr++] = xstrdup(value); + } else + return error(_("Invalid key: %s"), key); + + if (!error_flag) + return error(_("Invalid value for %s: %s"), key, value); + + return 0; +} + +static void read_populate_opts(struct replay_opts **opts_ptr) +{ + const char *opts_file = git_path(SEQ_OPTS_FILE); + + if (!file_exists(opts_file)) + return; + if (git_config_from_file(populate_opts_cb, opts_file, *opts_ptr) < 0) + die(_("Malformed options sheet: %s"), opts_file); +} + +static void walk_revs_populate_todo(struct commit_list **todo_list, + struct replay_opts *opts) +{ + struct commit *commit; + struct commit_list **next; + + prepare_revs(opts); + + next = todo_list; + while ((commit = get_revision(opts->revs))) + next = commit_list_append(commit, next); +} + +static int create_seq_dir(void) +{ + const char *seq_dir = git_path(SEQ_DIR); + + if (file_exists(seq_dir)) { + error(_("a cherry-pick or revert is already in progress")); + advise(_("try \"git cherry-pick (--continue | --quit | --abort)\"")); + return -1; + } + else if (mkdir(seq_dir, 0777) < 0) + die_errno(_("Could not create sequencer directory %s"), seq_dir); + return 0; +} + +static void save_head(const char *head) +{ + const char *head_file = git_path(SEQ_HEAD_FILE); + static struct lock_file head_lock; + struct strbuf buf = STRBUF_INIT; + int fd; + + fd = hold_lock_file_for_update(&head_lock, head_file, LOCK_DIE_ON_ERROR); + strbuf_addf(&buf, "%s\n", head); + if (write_in_full(fd, buf.buf, buf.len) < 0) + die_errno(_("Could not write to %s"), head_file); + if (commit_lock_file(&head_lock) < 0) + die(_("Error wrapping up %s."), head_file); +} + +static int reset_for_rollback(const unsigned char *sha1) +{ + const char *argv[4]; /* reset --merge + NULL */ + argv[0] = "reset"; + argv[1] = "--merge"; + argv[2] = sha1_to_hex(sha1); + argv[3] = NULL; + return run_command_v_opt(argv, RUN_GIT_CMD); +} + +static int rollback_single_pick(void) +{ + unsigned char head_sha1[20]; + + if (!file_exists(git_path("CHERRY_PICK_HEAD")) && + !file_exists(git_path("REVERT_HEAD"))) + return error(_("no cherry-pick or revert in progress")); + if (read_ref_full("HEAD", head_sha1, 0, NULL)) + return error(_("cannot resolve HEAD")); + if (is_null_sha1(head_sha1)) + return error(_("cannot abort from a branch yet to be born")); + return reset_for_rollback(head_sha1); +} + +static int sequencer_rollback(struct replay_opts *opts) +{ + const char *filename; + FILE *f; + unsigned char sha1[20]; + struct strbuf buf = STRBUF_INIT; + + filename = git_path(SEQ_HEAD_FILE); + f = fopen(filename, "r"); + if (!f && errno == ENOENT) { + /* + * There is no multiple-cherry-pick in progress. + * If CHERRY_PICK_HEAD or REVERT_HEAD indicates + * a single-cherry-pick in progress, abort that. + */ + return rollback_single_pick(); + } + if (!f) + return error(_("cannot open %s: %s"), filename, + strerror(errno)); + if (strbuf_getline(&buf, f, '\n')) { + error(_("cannot read %s: %s"), filename, ferror(f) ? + strerror(errno) : _("unexpected end of file")); + fclose(f); + goto fail; + } + fclose(f); + if (get_sha1_hex(buf.buf, sha1) || buf.buf[40] != '\0') { + error(_("stored pre-cherry-pick HEAD file '%s' is corrupt"), + filename); + goto fail; + } + if (reset_for_rollback(sha1)) + goto fail; + remove_sequencer_state(); + strbuf_release(&buf); + return 0; +fail: + strbuf_release(&buf); + return -1; +} + +static void save_todo(struct commit_list *todo_list, struct replay_opts *opts) +{ + const char *todo_file = git_path(SEQ_TODO_FILE); + static struct lock_file todo_lock; + struct strbuf buf = STRBUF_INIT; + int fd; + + fd = hold_lock_file_for_update(&todo_lock, todo_file, LOCK_DIE_ON_ERROR); + if (format_todo(&buf, todo_list, opts) < 0) + die(_("Could not format %s."), todo_file); + if (write_in_full(fd, buf.buf, buf.len) < 0) { + strbuf_release(&buf); + die_errno(_("Could not write to %s"), todo_file); + } + if (commit_lock_file(&todo_lock) < 0) { + strbuf_release(&buf); + die(_("Error wrapping up %s."), todo_file); + } + strbuf_release(&buf); +} + +static void save_opts(struct replay_opts *opts) +{ + const char *opts_file = git_path(SEQ_OPTS_FILE); + + if (opts->no_commit) + git_config_set_in_file(opts_file, "options.no-commit", "true"); + if (opts->edit) + git_config_set_in_file(opts_file, "options.edit", "true"); + if (opts->signoff) + git_config_set_in_file(opts_file, "options.signoff", "true"); + if (opts->record_origin) + git_config_set_in_file(opts_file, "options.record-origin", "true"); + if (opts->allow_ff) + git_config_set_in_file(opts_file, "options.allow-ff", "true"); + if (opts->mainline) { + struct strbuf buf = STRBUF_INIT; + strbuf_addf(&buf, "%d", opts->mainline); + git_config_set_in_file(opts_file, "options.mainline", buf.buf); + strbuf_release(&buf); + } + if (opts->strategy) + git_config_set_in_file(opts_file, "options.strategy", opts->strategy); + if (opts->xopts) { + int i; + for (i = 0; i < opts->xopts_nr; i++) + git_config_set_multivar_in_file(opts_file, + "options.strategy-option", + opts->xopts[i], "^$", 0); + } +} + +static int pick_commits(struct commit_list *todo_list, struct replay_opts *opts) +{ + struct commit_list *cur; + int res; + + setenv(GIT_REFLOG_ACTION, action_name(opts), 0); + if (opts->allow_ff) + assert(!(opts->signoff || opts->no_commit || + opts->record_origin || opts->edit)); + read_and_refresh_cache(opts); + + for (cur = todo_list; cur; cur = cur->next) { + save_todo(cur, opts); + res = do_pick_commit(cur->item, opts); + if (res) + return res; + } + + /* + * Sequence of picks finished successfully; cleanup by + * removing the .git/sequencer directory + */ + remove_sequencer_state(); + return 0; +} + +static int continue_single_pick(void) +{ + const char *argv[] = { "commit", NULL }; + + if (!file_exists(git_path("CHERRY_PICK_HEAD")) && + !file_exists(git_path("REVERT_HEAD"))) + return error(_("no cherry-pick or revert in progress")); + return run_command_v_opt(argv, RUN_GIT_CMD); +} + +static int sequencer_continue(struct replay_opts *opts) +{ + struct commit_list *todo_list = NULL; + + if (!file_exists(git_path(SEQ_TODO_FILE))) + return continue_single_pick(); + read_populate_opts(&opts); + read_populate_todo(&todo_list, opts); + + /* Verify that the conflict has been resolved */ + if (file_exists(git_path("CHERRY_PICK_HEAD")) || + file_exists(git_path("REVERT_HEAD"))) { + int ret = continue_single_pick(); + if (ret) + return ret; + } + if (index_differs_from("HEAD", 0)) + return error_dirty_index(opts); + todo_list = todo_list->next; + return pick_commits(todo_list, opts); +} + +static int single_pick(struct commit *cmit, struct replay_opts *opts) +{ + setenv(GIT_REFLOG_ACTION, action_name(opts), 0); + return do_pick_commit(cmit, opts); +} + +int sequencer_pick_revisions(struct replay_opts *opts) +{ + struct commit_list *todo_list = NULL; + unsigned char sha1[20]; + + if (opts->subcommand == REPLAY_NONE) + assert(opts->revs); + + read_and_refresh_cache(opts); + + /* + * Decide what to do depending on the arguments; a fresh + * cherry-pick should be handled differently from an existing + * one that is being continued + */ + if (opts->subcommand == REPLAY_REMOVE_STATE) { + remove_sequencer_state(); + return 0; + } + if (opts->subcommand == REPLAY_ROLLBACK) + return sequencer_rollback(opts); + if (opts->subcommand == REPLAY_CONTINUE) + return sequencer_continue(opts); + + /* + * If we were called as "git cherry-pick ", just + * cherry-pick/revert it, set CHERRY_PICK_HEAD / + * REVERT_HEAD, and don't touch the sequencer state. + * This means it is possible to cherry-pick in the middle + * of a cherry-pick sequence. + */ + if (opts->revs->cmdline.nr == 1 && + opts->revs->cmdline.rev->whence == REV_CMD_REV && + opts->revs->no_walk && + !opts->revs->cmdline.rev->flags) { + struct commit *cmit; + if (prepare_revision_walk(opts->revs)) + die(_("revision walk setup failed")); + cmit = get_revision(opts->revs); + if (!cmit || get_revision(opts->revs)) + die("BUG: expected exactly one commit from walk"); + return single_pick(cmit, opts); + } + + /* + * Start a new cherry-pick/ revert sequence; but + * first, make sure that an existing one isn't in + * progress + */ + + walk_revs_populate_todo(&todo_list, opts); + if (create_seq_dir() < 0) + return -1; + if (get_sha1("HEAD", sha1)) { + if (opts->action == REPLAY_REVERT) + return error(_("Can't revert as initial commit")); + return error(_("Can't cherry-pick into empty head")); + } + save_head(sha1_to_hex(sha1)); + save_opts(opts); + return pick_commits(todo_list, opts); +} -- cgit v1.2.1 From ed727b192bb3506ee7904c1c9e8370d6b4ecaf5d Mon Sep 17 00:00:00 2001 From: Phil Hord Date: Tue, 21 Feb 2012 19:44:17 -0500 Subject: cherry-pick: No advice to commit if --no-commit When cherry-pick fails it offers a helpful hint about how to proceed. The hint tells the user how to do the cleanup needed by the conflicted cherry-pick and finish the job after conflict resolution. In case of cherry-pick --no-commit, the hint goes too far. It tells the user to finish up with 'git commit'. That is not what this git-cherry-pick was trying to do in the first place. Restrict the hint in case of --no-commit to avoid giving this extra advice. Also, add a test verifying the reduced hint for the --no-commit version of cherry-pick. Signed-off-by: Phil Hord Acked-by: Jonathan Nieder Signed-off-by: Junio C Hamano --- sequencer.c | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) (limited to 'sequencer.c') diff --git a/sequencer.c b/sequencer.c index 5fcbcb8875..a37846a594 100644 --- a/sequencer.c +++ b/sequencer.c @@ -123,7 +123,7 @@ static void write_cherry_pick_head(struct commit *commit, const char *pseudoref) strbuf_release(&buf); } -static void print_advice(int show_hint) +static void print_advice(int show_hint, struct replay_opts *opts) { char *msg = getenv("GIT_CHERRY_PICK_HELP"); @@ -138,10 +138,15 @@ static void print_advice(int show_hint) return; } - if (show_hint) - advise(_("after resolving the conflicts, mark the corrected paths\n" - "with 'git add ' or 'git rm '\n" - "and commit the result with 'git commit'")); + if (show_hint) { + if (opts->no_commit) + advise(_("after resolving the conflicts, mark the corrected paths\n" + "with 'git add ' or 'git rm '")); + else + advise(_("after resolving the conflicts, mark the corrected paths\n" + "with 'git add ' or 'git rm '\n" + "and commit the result with 'git commit'")); + } } static void write_message(struct strbuf *msgbuf, const char *filename) @@ -423,7 +428,7 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts) : _("could not apply %s... %s"), find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV), msg.subject); - print_advice(res == 1); + print_advice(res == 1, opts); rerere(opts->allow_rerere_auto); } else { if (!opts->no_commit) -- cgit v1.2.1 From cba595bd21fd0731e37898dbd6c5b55f65fd8aea Mon Sep 17 00:00:00 2001 From: Jeff King Date: Thu, 22 Mar 2012 14:53:24 -0400 Subject: drop casts from users EMPTY_TREE_SHA1_BIN This macro already evaluates to the correct type, as it casts the string literal to "unsigned char *" itself (and callers who want the literal can use the _LITERAL form). Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- sequencer.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'sequencer.c') diff --git a/sequencer.c b/sequencer.c index a37846a594..4307364b26 100644 --- a/sequencer.c +++ b/sequencer.c @@ -164,7 +164,7 @@ static void write_message(struct strbuf *msgbuf, const char *filename) static struct tree *empty_tree(void) { - return lookup_tree((const unsigned char *)EMPTY_TREE_SHA1_BIN); + return lookup_tree(EMPTY_TREE_SHA1_BIN); } static int error_dirty_index(struct replay_opts *opts) -- cgit v1.2.1 From df478b744cee9821eb2abbe2202d262b87a34289 Mon Sep 17 00:00:00 2001 From: Neil Horman Date: Wed, 11 Apr 2012 16:21:53 -0400 Subject: git-cherry-pick: add allow-empty option git cherry-pick fails when picking a non-ff commit that is empty. The advice given with the failure is that a git-commit --allow-empty should be issued to explicitly add the empty commit during the cherry pick. This option allows a user to specify before hand that they want to keep the empty commit. This eliminates the need to issue both a cherry pick and a commit operation. Signed-off-by: Neil Horman Signed-off-by: Junio C Hamano --- sequencer.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'sequencer.c') diff --git a/sequencer.c b/sequencer.c index a37846a594..71929ba8cd 100644 --- a/sequencer.c +++ b/sequencer.c @@ -260,8 +260,8 @@ static int do_recursive_merge(struct commit *base, struct commit *next, */ static int run_git_commit(const char *defmsg, struct replay_opts *opts) { - /* 6 is max possible length of our args array including NULL */ - const char *args[6]; + /* 7 is max possible length of our args array including NULL */ + const char *args[7]; int i = 0; args[i++] = "commit"; @@ -272,6 +272,9 @@ static int run_git_commit(const char *defmsg, struct replay_opts *opts) args[i++] = "-F"; args[i++] = defmsg; } + if (opts->allow_empty) + args[i++] = "--allow-empty"; + args[i] = NULL; return run_command_v_opt(args, RUN_GIT_CMD); -- cgit v1.2.1 From 51120683ec8664c8b34b8541a0518a4902ee9d94 Mon Sep 17 00:00:00 2001 From: Ralf Thielow Date: Wed, 18 Apr 2012 23:07:25 +0200 Subject: sequencer: remove additional blank line Remove an additional blank line between the headline and the list of conflicted files after doing a recursive merge. Signed-off-by: Ralf Thielow Signed-off-by: Junio C Hamano --- sequencer.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'sequencer.c') diff --git a/sequencer.c b/sequencer.c index a37846a594..f6e44b8be0 100644 --- a/sequencer.c +++ b/sequencer.c @@ -234,7 +234,7 @@ static int do_recursive_merge(struct commit *base, struct commit *next, if (!clean) { int i; - strbuf_addstr(msgbuf, "\nConflicts:\n\n"); + strbuf_addstr(msgbuf, "\nConflicts:\n"); for (i = 0; i < active_nr;) { struct cache_entry *ce = active_cache[i++]; if (ce_stage(ce)) { -- cgit v1.2.1 From b27cfb0d8d4cbb6d079c70ffeadac9c0dcfff250 Mon Sep 17 00:00:00 2001 From: Neil Horman Date: Fri, 20 Apr 2012 10:36:15 -0400 Subject: git-cherry-pick: Add keep-redundant-commits option The git-cherry-pick --allow-empty command by default only preserves empty commits that were originally empty, i.e only those commits for which ^{tree} and ^^{tree} are equal. By default commits which are non-empty, but were made empty by the inclusion of a prior commit on the current history are filtered out. This option allows us to override that behavior and include redundant commits as empty commits in the change history. Note that this patch changes the default behavior of git cherry-pick slightly. Prior to this patch all commits in a cherry-pick sequence were applied and git commit was run. The implication here was that, if a commit was redundant, and the commit did not trigger the fast forward logic, the git commit operation, and therefore the git cherry-pick operation would fail, displaying the cherry pick advice (i.e. run git commit --allow-empty). With this patch however, such redundant commits are automatically skipped without stopping, unless --keep-redundant-commits is specified, in which case, they are automatically applied as empty commits. Signed-off-by: Neil Horman Signed-off-by: Junio C Hamano --- sequencer.c | 94 +++++++++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 83 insertions(+), 11 deletions(-) (limited to 'sequencer.c') diff --git a/sequencer.c b/sequencer.c index 71929ba8cd..4e3af82442 100644 --- a/sequencer.c +++ b/sequencer.c @@ -13,6 +13,7 @@ #include "rerere.h" #include "merge-recursive.h" #include "refs.h" +#include "argv-array.h" #define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION" @@ -251,6 +252,30 @@ static int do_recursive_merge(struct commit *base, struct commit *next, return !clean; } +static int is_index_unchanged(void) +{ + unsigned char head_sha1[20]; + struct commit *head_commit; + + if (!resolve_ref_unsafe("HEAD", head_sha1, 1, NULL)) + return error(_("Could not resolve HEAD commit\n")); + + head_commit = lookup_commit(head_sha1); + if (!head_commit || parse_commit(head_commit)) + return error(_("could not parse commit %s\n"), + sha1_to_hex(head_commit->object.sha1)); + + if (!active_cache_tree) + active_cache_tree = cache_tree(); + + if (!cache_tree_fully_valid(active_cache_tree)) + if (cache_tree_update(active_cache_tree, active_cache, + active_nr, 0)) + return error(_("Unable to update cache tree\n")); + + return !hashcmp(active_cache_tree->sha1, head_commit->tree->object.sha1); +} + /* * If we are cherry-pick, and if the merge did not result in * hand-editing, we will hit this commit and inherit the original @@ -260,24 +285,46 @@ static int do_recursive_merge(struct commit *base, struct commit *next, */ static int run_git_commit(const char *defmsg, struct replay_opts *opts) { - /* 7 is max possible length of our args array including NULL */ - const char *args[7]; - int i = 0; + struct argv_array array; + int rc; + + argv_array_init(&array); + argv_array_push(&array, "commit"); + argv_array_push(&array, "-n"); - args[i++] = "commit"; - args[i++] = "-n"; if (opts->signoff) - args[i++] = "-s"; + argv_array_push(&array, "-s"); if (!opts->edit) { - args[i++] = "-F"; - args[i++] = defmsg; + argv_array_push(&array, "-F"); + argv_array_push(&array, defmsg); } + if (opts->allow_empty) - args[i++] = "--allow-empty"; + argv_array_push(&array, "--allow-empty"); - args[i] = NULL; + rc = run_command_v_opt(array.argv, RUN_GIT_CMD); + argv_array_clear(&array); + return rc; +} + +static int is_original_commit_empty(struct commit *commit) +{ + const unsigned char *ptree_sha1; + + if (parse_commit(commit)) + return error(_("Could not parse commit %s\n"), + sha1_to_hex(commit->object.sha1)); + if (commit->parents) { + struct commit *parent = commit->parents->item; + if (parse_commit(parent)) + return error(_("Could not parse parent commit %s\n"), + sha1_to_hex(parent->object.sha1)); + ptree_sha1 = parent->tree->object.sha1; + } else { + ptree_sha1 = EMPTY_TREE_SHA1_BIN; /* commit is root */ + } - return run_command_v_opt(args, RUN_GIT_CMD); + return !hashcmp(ptree_sha1, commit->tree->object.sha1); } static int do_pick_commit(struct commit *commit, struct replay_opts *opts) @@ -289,6 +336,8 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts) char *defmsg = NULL; struct strbuf msgbuf = STRBUF_INIT; int res; + int empty_commit; + int index_unchanged; if (opts->no_commit) { /* @@ -414,6 +463,10 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts) free_commit_list(remotes); } + empty_commit = is_original_commit_empty(commit); + if (empty_commit < 0) + return empty_commit; + /* * If the merge was clean or if it failed due to conflict, we write * CHERRY_PICK_HEAD for the subsequent invocation of commit to use. @@ -434,6 +487,25 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts) print_advice(res == 1, opts); rerere(opts->allow_rerere_auto); } else { + index_unchanged = is_index_unchanged(); + /* + * If index_unchanged is less than 0, that indicates we either + * couldn't parse HEAD or the index, so error out here. + */ + if (index_unchanged < 0) + return index_unchanged; + + if (!empty_commit && !opts->keep_redundant_commits && index_unchanged) + /* + * The head tree and the index match + * meaning the commit is empty. Since it wasn't created + * empty (based on the previous test), we can conclude + * the commit has been made redundant. Since we don't + * want to keep redundant commits, we can just return + * here, skipping this commit + */ + return 0; + if (!opts->no_commit) res = run_git_commit(defmsg, opts); } -- cgit v1.2.1 From 89b5f1d9c5e64c9e232fa1ebe0ea407c8773b79a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Scharfe?= Date: Wed, 25 Apr 2012 22:35:27 +0200 Subject: sequencer: export commit_list_append() This function can be used in other parts of git. Give it a new home in commit.c. Signed-off-by: Rene Scharfe Signed-off-by: Junio C Hamano --- sequencer.c | 27 --------------------------- 1 file changed, 27 deletions(-) (limited to 'sequencer.c') diff --git a/sequencer.c b/sequencer.c index 4307364b26..ac6c8238e5 100644 --- a/sequencer.c +++ b/sequencer.c @@ -468,33 +468,6 @@ static void read_and_refresh_cache(struct replay_opts *opts) rollback_lock_file(&index_lock); } -/* - * Append a commit to the end of the commit_list. - * - * next starts by pointing to the variable that holds the head of an - * empty commit_list, and is updated to point to the "next" field of - * the last item on the list as new commits are appended. - * - * Usage example: - * - * struct commit_list *list; - * struct commit_list **next = &list; - * - * next = commit_list_append(c1, next); - * next = commit_list_append(c2, next); - * assert(commit_list_count(list) == 2); - * return list; - */ -static struct commit_list **commit_list_append(struct commit *commit, - struct commit_list **next) -{ - struct commit_list *new = xmalloc(sizeof(struct commit_list)); - new->item = commit; - *next = new; - new->next = NULL; - return &new->next; -} - static int format_todo(struct strbuf *buf, struct commit_list *todo_list, struct replay_opts *opts) { -- cgit v1.2.1 From 4b580061b350ae9d27ab7c3936a45828a33624c6 Mon Sep 17 00:00:00 2001 From: Neil Horman Date: Thu, 3 May 2012 08:10:22 -0400 Subject: git cherry-pick: do not dereference a potential NULL pointer In the case the pointer could be NULL, the function that gave the caller the NULL pointer would already have issued an error message, so simply returning early with an error status without issuing a new message is sufficient. The same for parse_commit() that will show necessary error message when the argument is not NULL, and will return error silently when the argument is NULL. Noticed-by: Michael Mueller Signed-off-by: Neil Horman Signed-off-by: Junio C Hamano --- sequencer.c | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) (limited to 'sequencer.c') diff --git a/sequencer.c b/sequencer.c index 4e3af82442..72cb4ff82c 100644 --- a/sequencer.c +++ b/sequencer.c @@ -261,9 +261,17 @@ static int is_index_unchanged(void) return error(_("Could not resolve HEAD commit\n")); head_commit = lookup_commit(head_sha1); - if (!head_commit || parse_commit(head_commit)) - return error(_("could not parse commit %s\n"), - sha1_to_hex(head_commit->object.sha1)); + + /* + * If head_commit is NULL, check_commit, called from + * lookup_commit, would have indicated that head_commit is not + * a commit object already. parse_commit() will return failure + * without further complaints in such a case. Otherwise, if + * the commit is invalid, parse_commit() will complain. So + * there is nothing for us to say here. Just return failure. + */ + if (parse_commit(head_commit)) + return -1; if (!active_cache_tree) active_cache_tree = cache_tree(); -- cgit v1.2.1 From ac2b0e8fb77d02f480af3f45a44cb9ea611008a4 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 29 May 2012 17:14:41 -0700 Subject: cherry-pick: regression fix for empty commits The earlier "--keep-redundant-commit" series broke "cherry-pick" that is given a commit whose change is already in the current history. Such a cherry-pick would result in an empty change, and should stop with an error, telling the user that conflict resolution may have made the result empty (which is exactly what is happening), but we silently dropped the change on the floor without any message nor non-zero exit code. Signed-off-by: Junio C Hamano --- sequencer.c | 73 +++++++++++++++++++++++++++++++++++++------------------------ 1 file changed, 45 insertions(+), 28 deletions(-) (limited to 'sequencer.c') diff --git a/sequencer.c b/sequencer.c index 72cb4ff82c..1b2168c1c2 100644 --- a/sequencer.c +++ b/sequencer.c @@ -291,7 +291,8 @@ static int is_index_unchanged(void) * If we are revert, or if our cherry-pick results in a hand merge, * we had better say that the current user is responsible for that. */ -static int run_git_commit(const char *defmsg, struct replay_opts *opts) +static int run_git_commit(const char *defmsg, struct replay_opts *opts, + int allow_empty) { struct argv_array array; int rc; @@ -307,7 +308,7 @@ static int run_git_commit(const char *defmsg, struct replay_opts *opts) argv_array_push(&array, defmsg); } - if (opts->allow_empty) + if (allow_empty) argv_array_push(&array, "--allow-empty"); rc = run_command_v_opt(array.argv, RUN_GIT_CMD); @@ -335,6 +336,44 @@ static int is_original_commit_empty(struct commit *commit) return !hashcmp(ptree_sha1, commit->tree->object.sha1); } +/* + * Do we run "git commit" with "--allow-empty"? + */ +static int allow_empty(struct replay_opts *opts, struct commit *commit) +{ + int index_unchanged, empty_commit; + + /* + * Three cases: + * + * (1) we do not allow empty at all and error out. + * + * (2) we allow ones that were initially empty, but + * forbid the ones that become empty; + * + * (3) we allow both. + */ + if (!opts->allow_empty) + return 0; /* let "git commit" barf as necessary */ + + index_unchanged = is_index_unchanged(); + if (index_unchanged < 0) + return index_unchanged; + if (!index_unchanged) + return 0; /* we do not have to say --allow-empty */ + + if (opts->keep_redundant_commits) + return 1; + + empty_commit = is_original_commit_empty(commit); + if (empty_commit < 0) + return empty_commit; + if (!empty_commit) + return 0; + else + return 1; +} + static int do_pick_commit(struct commit *commit, struct replay_opts *opts) { unsigned char head[20]; @@ -344,8 +383,6 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts) char *defmsg = NULL; struct strbuf msgbuf = STRBUF_INIT; int res; - int empty_commit; - int index_unchanged; if (opts->no_commit) { /* @@ -471,10 +508,6 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts) free_commit_list(remotes); } - empty_commit = is_original_commit_empty(commit); - if (empty_commit < 0) - return empty_commit; - /* * If the merge was clean or if it failed due to conflict, we write * CHERRY_PICK_HEAD for the subsequent invocation of commit to use. @@ -495,27 +528,11 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts) print_advice(res == 1, opts); rerere(opts->allow_rerere_auto); } else { - index_unchanged = is_index_unchanged(); - /* - * If index_unchanged is less than 0, that indicates we either - * couldn't parse HEAD or the index, so error out here. - */ - if (index_unchanged < 0) - return index_unchanged; - - if (!empty_commit && !opts->keep_redundant_commits && index_unchanged) - /* - * The head tree and the index match - * meaning the commit is empty. Since it wasn't created - * empty (based on the previous test), we can conclude - * the commit has been made redundant. Since we don't - * want to keep redundant commits, we can just return - * here, skipping this commit - */ - return 0; - + int allow = allow_empty(opts, commit); + if (allow < 0) + return allow; if (!opts->no_commit) - res = run_git_commit(defmsg, opts); + res = run_git_commit(defmsg, opts, allow); } free_message(&msg); -- cgit v1.2.1 From 4bee958479bed1d3a8f20a75f33422ee49939dbf Mon Sep 17 00:00:00 2001 From: Chris Webb Date: Thu, 2 Aug 2012 11:38:51 +0100 Subject: cherry-pick: add --allow-empty-message option Scripts such as "git rebase -i" cannot currently cherry-pick commits which have an empty commit message, as git cherry-pick calls git commit without the --allow-empty-message option. Add an --allow-empty-message option to git cherry-pick which is passed through to git commit, so this behaviour can be overridden. Signed-off-by: Chris Webb Reviewed-by: Neil Horman Signed-off-by: Junio C Hamano --- sequencer.c | 3 +++ 1 file changed, 3 insertions(+) (limited to 'sequencer.c') diff --git a/sequencer.c b/sequencer.c index bf078f274b..1ea5293f6e 100644 --- a/sequencer.c +++ b/sequencer.c @@ -311,6 +311,9 @@ static int run_git_commit(const char *defmsg, struct replay_opts *opts, if (allow_empty) argv_array_push(&array, "--allow-empty"); + if (opts->allow_empty_message) + argv_array_push(&array, "--allow-empty-message"); + rc = run_command_v_opt(array.argv, RUN_GIT_CMD); argv_array_clear(&array); return rc; -- cgit v1.2.1 From a73e22e96350c9bcb5c0e31e0298ce67bec0a527 Mon Sep 17 00:00:00 2001 From: Martin von Zweigbergk Date: Tue, 28 Aug 2012 23:15:56 -0700 Subject: cherry-pick/revert: respect order of revisions to pick When giving multiple individual revisions to cherry-pick or revert, as in 'git cherry-pick A B' or 'git revert B A', one would expect them to be picked/reverted in the order given on the command line. They are instead ordered by their commit timestamp -- in chronological order for "cherry-pick" and in reverse chronological order for "revert". This matches the order in which one would usually give them on the command line, making this bug somewhat hard to notice. Still, it has been reported at least once before [1]. It seems like the chronological sorting happened by accident because the revision walker has traditionally always sorted commits in reverse chronological order when rev_info.no_walk was enabled. In the case of 'git revert B A' where B is newer than A, this sorting is a no-op. For 'git cherry-pick A B', the sorting would reverse the arguments, but because the sequencer also flips the rev_info.reverse flag when picking (as opposed to reverting), the end result is a chronological order. The rev_info.reverse flag was probably flipped so that the revision walker emits B before C in 'git cherry-pick A..C'; that it happened to effectively undo the unexpected sorting done when not walking, was probably a coincidence that allowed this bug to happen at all. Fix the bug by telling the revision walker not to sort the commits when not walking. The only case we want to reverse the order is now when cherry-picking and walking revisions (rev_info.no_walk = 0). [1] http://thread.gmane.org/gmane.comp.version-control.git/164794 Signed-off-by: Martin von Zweigbergk Signed-off-by: Junio C Hamano --- sequencer.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'sequencer.c') diff --git a/sequencer.c b/sequencer.c index bf078f274b..bd626806d6 100644 --- a/sequencer.c +++ b/sequencer.c @@ -543,7 +543,11 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts) static void prepare_revs(struct replay_opts *opts) { - if (opts->action != REPLAY_REVERT) + /* + * picking (but not reverting) ranges (but not individual revisions) + * should be done in reverse + */ + if (opts->action == REPLAY_PICK && !opts->revs->no_walk) opts->revs->reverse ^= 1; if (prepare_revision_walk(opts->revs)) -- cgit v1.2.1 From 5ed75e2a3fb30f93fea7772e481ec6091e9a2c5f Mon Sep 17 00:00:00 2001 From: Miklos Vajna Date: Fri, 14 Sep 2012 08:52:03 +0200 Subject: cherry-pick: don't forget -s on failure In case 'git cherry-pick -s ' failed, the user had to use 'git commit -s' (i.e. state the -s option again), which is easy to forget about. Instead, write the signed-off-by line early, so plain 'git commit' will have the same result. Also update 'git commit -s', so that in case there is already a relevant Signed-off-by line before the Conflicts: line, it won't add one more at the end of the message. If there is no such line, then add it before the the Conflicts: line. Signed-off-by: Miklos Vajna Signed-off-by: Junio C Hamano --- sequencer.c | 65 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) (limited to 'sequencer.c') diff --git a/sequencer.c b/sequencer.c index f86f116a15..dbef5cea02 100644 --- a/sequencer.c +++ b/sequencer.c @@ -17,6 +17,8 @@ #define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION" +const char sign_off_header[] = "Signed-off-by: "; + void remove_sequencer_state(void) { struct strbuf seq_dir = STRBUF_INIT; @@ -233,6 +235,9 @@ static int do_recursive_merge(struct commit *base, struct commit *next, die(_("%s: Unable to write new index file"), action_name(opts)); rollback_lock_file(&index_lock); + if (opts->signoff) + append_signoff(msgbuf, 0); + if (!clean) { int i; strbuf_addstr(msgbuf, "\nConflicts:\n"); @@ -1011,3 +1016,63 @@ int sequencer_pick_revisions(struct replay_opts *opts) save_opts(opts); return pick_commits(todo_list, opts); } + +static int ends_rfc2822_footer(struct strbuf *sb, int ignore_footer) +{ + int ch; + int hit = 0; + int i, j, k; + int len = sb->len - ignore_footer; + int first = 1; + const char *buf = sb->buf; + + for (i = len - 1; i > 0; i--) { + if (hit && buf[i] == '\n') + break; + hit = (buf[i] == '\n'); + } + + while (i < len - 1 && buf[i] == '\n') + i++; + + for (; i < len; i = k) { + for (k = i; k < len && buf[k] != '\n'; k++) + ; /* do nothing */ + k++; + + if ((buf[k] == ' ' || buf[k] == '\t') && !first) + continue; + + first = 0; + + for (j = 0; i + j < len; j++) { + ch = buf[i + j]; + if (ch == ':') + break; + if (isalnum(ch) || + (ch == '-')) + continue; + return 0; + } + } + return 1; +} + +void append_signoff(struct strbuf *msgbuf, int ignore_footer) +{ + struct strbuf sob = STRBUF_INIT; + int i; + + strbuf_addstr(&sob, sign_off_header); + strbuf_addstr(&sob, fmt_name(getenv("GIT_COMMITTER_NAME"), + getenv("GIT_COMMITTER_EMAIL"))); + strbuf_addch(&sob, '\n'); + for (i = msgbuf->len - 1 - ignore_footer; i > 0 && msgbuf->buf[i - 1] != '\n'; i--) + ; /* do nothing */ + if (prefixcmp(msgbuf->buf + i, sob.buf)) { + if (!i || !ends_rfc2822_footer(msgbuf, ignore_footer)) + strbuf_splice(msgbuf, msgbuf->len - ignore_footer, 0, "\n", 1); + strbuf_splice(msgbuf, msgbuf->len - ignore_footer, 0, sob.buf, sob.len); + } + strbuf_release(&sob); +} -- cgit v1.2.1 From 250f2492a408be1ed79c563f304a0d43752d032c Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 15 Sep 2012 23:20:40 -0700 Subject: sequencer.c: mark a private file-scope symbol as static Signed-off-by: Junio C Hamano --- sequencer.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'sequencer.c') diff --git a/sequencer.c b/sequencer.c index f86f116a15..ce8a59f576 100644 --- a/sequencer.c +++ b/sequencer.c @@ -17,7 +17,7 @@ #define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION" -void remove_sequencer_state(void) +static void remove_sequencer_state(void) { struct strbuf seq_dir = STRBUF_INIT; -- cgit v1.2.1 From db699a8a1f2c8063620efae41e66786c11a31d8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Fri, 26 Oct 2012 22:53:49 +0700 Subject: Move try_merge_command and checkout_fast_forward to libgit.a MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit These functions are called in sequencer.c, which is part of libgit.a. This makes libgit.a potentially require builtin/merge.c for external git commands. Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Jeff King --- sequencer.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'sequencer.c') diff --git a/sequencer.c b/sequencer.c index e3723d2095..be0cb8b107 100644 --- a/sequencer.c +++ b/sequencer.c @@ -191,7 +191,7 @@ static int fast_forward_to(const unsigned char *to, const unsigned char *from) struct ref_lock *ref_lock; read_cache(); - if (checkout_fast_forward(from, to)) + if (checkout_fast_forward(from, to, 1)) exit(1); /* the callee should have complained already */ ref_lock = lock_any_ref_for_update("HEAD", from, 0); return write_ref_sha1(ref_lock, to, "cherry-pick"); -- cgit v1.2.1 From 0e18bcd5e937b9e5857892e7f5c80c3c5b1da5d4 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 18 Oct 2012 22:41:56 -0700 Subject: reencode_string(): introduce and use same_encoding() Callers of reencode_string() that re-encodes a string from one encoding to another all used ad-hoc way to bypass the case where the input and the output encodings are the same. Some did strcmp(), some did strcasecmp(), yet some others when converting to UTF-8 used is_encoding_utf8(). Introduce same_encoding() helper function to make these callers use the same logic. Notably, is_encoding_utf8() has a work-around for common misconfiguration to use "utf8" to name UTF-8 encoding, which does not match "UTF-8" hence strcasecmp() would not consider the same. Make use of it in this helper function. Signed-off-by: Junio C Hamano --- sequencer.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'sequencer.c') diff --git a/sequencer.c b/sequencer.c index bd626806d6..f2f5b137ea 100644 --- a/sequencer.c +++ b/sequencer.c @@ -58,7 +58,7 @@ static int get_message(struct commit *commit, struct commit_message *out) out->reencoded_message = NULL; out->message = commit->buffer; - if (strcmp(encoding, git_commit_encoding)) + if (same_encoding(encoding, git_commit_encoding)) out->reencoded_message = reencode_string(commit->buffer, git_commit_encoding, encoding); if (out->reencoded_message) -- cgit v1.2.1 From 334ae397452170dacfcd8e69e4a41f6a64f54eec Mon Sep 17 00:00:00 2001 From: Martin von Zweigbergk Date: Fri, 21 Dec 2012 11:10:11 -0800 Subject: learn to pick/revert into unborn branch cherry-picking into an unborn branch should work, so make it work, with or without --ff. Cherry-picking anything other than a commit that only adds files, will naturally result in conflicts. Similarly, revert also works, but will result in conflicts unless the specified revision only deletes files. Signed-off-by: Martin von Zweigbergk Signed-off-by: Junio C Hamano --- sequencer.c | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) (limited to 'sequencer.c') diff --git a/sequencer.c b/sequencer.c index 22604902aa..aef5e8a017 100644 --- a/sequencer.c +++ b/sequencer.c @@ -186,14 +186,15 @@ static int error_dirty_index(struct replay_opts *opts) return -1; } -static int fast_forward_to(const unsigned char *to, const unsigned char *from) +static int fast_forward_to(const unsigned char *to, const unsigned char *from, + int unborn) { struct ref_lock *ref_lock; read_cache(); if (checkout_fast_forward(from, to, 1)) exit(1); /* the callee should have complained already */ - ref_lock = lock_any_ref_for_update("HEAD", from, 0); + ref_lock = lock_any_ref_for_update("HEAD", unborn ? null_sha1 : from, 0); return write_ref_sha1(ref_lock, to, "cherry-pick"); } @@ -390,7 +391,7 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts) struct commit_message msg = { NULL, NULL, NULL, NULL, NULL }; char *defmsg = NULL; struct strbuf msgbuf = STRBUF_INIT; - int res; + int res, unborn = 0; if (opts->no_commit) { /* @@ -402,9 +403,10 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts) if (write_cache_as_tree(head, 0, NULL)) die (_("Your index file is unmerged.")); } else { - if (get_sha1("HEAD", head)) - return error(_("You do not have a valid HEAD")); - if (index_differs_from("HEAD", 0)) + unborn = get_sha1("HEAD", head); + if (unborn) + hashcpy(head, EMPTY_TREE_SHA1_BIN); + if (index_differs_from(unborn ? EMPTY_TREE_SHA1_HEX : "HEAD", 0)) return error_dirty_index(opts); } discard_cache(); @@ -435,8 +437,10 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts) else parent = commit->parents->item; - if (opts->allow_ff && parent && !hashcmp(parent->object.sha1, head)) - return fast_forward_to(commit->object.sha1, head); + if (opts->allow_ff && + ((parent && !hashcmp(parent->object.sha1, head)) || + (!parent && unborn))) + return fast_forward_to(commit->object.sha1, head, unborn); if (parent && parse_commit(parent) < 0) /* TRANSLATORS: The first %s will be "revert" or -- cgit v1.2.1 From fa1727fb210bd8d0079af124222420d46fb70f81 Mon Sep 17 00:00:00 2001 From: Jonathan Nieder Date: Tue, 12 Feb 2013 02:17:28 -0800 Subject: sequencer.c: rework search for start of footer to improve clarity This code sequence is somewhat difficult to read. Let's rewrite it and add some comments to improve clarity. Signed-off-by: Brandon Casey Signed-off-by: Junio C Hamano --- sequencer.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) (limited to 'sequencer.c') diff --git a/sequencer.c b/sequencer.c index 22604902aa..776ddb1962 100644 --- a/sequencer.c +++ b/sequencer.c @@ -1019,19 +1019,21 @@ int sequencer_pick_revisions(struct replay_opts *opts) static int ends_rfc2822_footer(struct strbuf *sb, int ignore_footer) { - int ch; - int hit = 0; + char ch, prev; int i, j, k; int len = sb->len - ignore_footer; int first = 1; const char *buf = sb->buf; + prev = '\0'; for (i = len - 1; i > 0; i--) { - if (hit && buf[i] == '\n') + ch = buf[i]; + if (prev == '\n' && ch == '\n') /* paragraph break */ break; - hit = (buf[i] == '\n'); + prev = ch; } + /* advance to start of last paragraph */ while (i < len - 1 && buf[i] == '\n') i++; -- cgit v1.2.1 From 9b1515220912b4e436145babf0b5887ecbfb0ac4 Mon Sep 17 00:00:00 2001 From: Brandon Casey Date: Tue, 12 Feb 2013 02:17:29 -0800 Subject: commit, cherry-pick -s: remove broken support for multiline rfc2822 fields Starting with c1e01b0c (commit: More generous accepting of RFC-2822 footer lines, 2009-10-28), "git commit -s" carefully parses the last paragraph of each commit message to check if it consists only of RFC2822-style headers, in which case the signoff will be added as a new line in the same list: Reported-by: Reporter Signed-off-by: Author Acked-by: Lieutenant It even included support for accepting indented continuation lines for multiline fields. Unfortunately the multiline field support is broken because it checks whether buf[k] (the first character of the *next* line) instead of buf[i] is a whitespace character. The result is that any footer with a continuation line is not accepted, since the last continuation line neither starts with an RFC2822 field name nor is followed by a continuation line. That this has remained broken for so long is good evidence that nobody actually needed multiline fields. Rip out the broken continuation support. There should be no functional change. [Thanks to Jonathan Nieder for the excellent commit message] Signed-off-by: Brandon Casey Reviewed-by: Jonathan Nieder Signed-off-by: Junio C Hamano --- sequencer.c | 6 ------ 1 file changed, 6 deletions(-) (limited to 'sequencer.c') diff --git a/sequencer.c b/sequencer.c index 776ddb1962..1a80dac6c5 100644 --- a/sequencer.c +++ b/sequencer.c @@ -1022,7 +1022,6 @@ static int ends_rfc2822_footer(struct strbuf *sb, int ignore_footer) char ch, prev; int i, j, k; int len = sb->len - ignore_footer; - int first = 1; const char *buf = sb->buf; prev = '\0'; @@ -1042,11 +1041,6 @@ static int ends_rfc2822_footer(struct strbuf *sb, int ignore_footer) ; /* do nothing */ k++; - if ((buf[k] == ' ' || buf[k] == '\t') && !first) - continue; - - first = 0; - for (j = 0; i + j < len; j++) { ch = buf[i + j]; if (ch == ':') -- cgit v1.2.1 From cd650a4eee148dadb0943f415e88c210f18e70df Mon Sep 17 00:00:00 2001 From: Brandon Casey Date: Tue, 12 Feb 2013 02:17:32 -0800 Subject: sequencer.c: recognize "(cherry picked from ..." as part of s-o-b footer When 'cherry-pick -s' is used to append a signed-off-by line to a cherry picked commit, it does not currently detect the "(cherry picked from..." that may have been appended by a previous 'cherry-pick -x' as part of the s-o-b footer and it will insert a blank line before appending a new s-o-b. Let's detect "(cherry picked from...)" as part of the footer so that we will produce this: Signed-off-by: A U Thor (cherry picked from da39a3ee5e6b4b0d3255bfef95601890afd80709) Signed-off-by: C O Mmitter instead of this: Signed-off-by: A U Thor (cherry picked from da39a3ee5e6b4b0d3255bfef95601890afd80709) Signed-off-by: C O Mmitter [with improvements from Jonathan Nieder] Signed-off-by: Brandon Casey Acked-by: Jonathan Nieder Signed-off-by: Junio C Hamano --- sequencer.c | 51 +++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 37 insertions(+), 14 deletions(-) (limited to 'sequencer.c') diff --git a/sequencer.c b/sequencer.c index 1a80dac6c5..7889b6bc19 100644 --- a/sequencer.c +++ b/sequencer.c @@ -18,6 +18,7 @@ #define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION" const char sign_off_header[] = "Signed-off-by: "; +static const char cherry_picked_prefix[] = "(cherry picked from commit "; static void remove_sequencer_state(void) { @@ -492,7 +493,7 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts) } if (opts->record_origin) { - strbuf_addstr(&msgbuf, "(cherry picked from commit "); + strbuf_addstr(&msgbuf, cherry_picked_prefix); strbuf_addstr(&msgbuf, sha1_to_hex(commit->object.sha1)); strbuf_addstr(&msgbuf, ")\n"); } @@ -1017,16 +1018,44 @@ int sequencer_pick_revisions(struct replay_opts *opts) return pick_commits(todo_list, opts); } -static int ends_rfc2822_footer(struct strbuf *sb, int ignore_footer) +static int is_rfc2822_line(const char *buf, int len) { - char ch, prev; - int i, j, k; + int i; + + for (i = 0; i < len; i++) { + int ch = buf[i]; + if (ch == ':') + return 1; + if (!isalnum(ch) && ch != '-') + break; + } + + return 0; +} + +static int is_cherry_picked_from_line(const char *buf, int len) +{ + /* + * We only care that it looks roughly like (cherry picked from ...) + */ + return len > strlen(cherry_picked_prefix) + 1 && + !prefixcmp(buf, cherry_picked_prefix) && buf[len - 1] == ')'; +} + +static int has_conforming_footer(struct strbuf *sb, int ignore_footer) +{ + char prev; + int i, k; int len = sb->len - ignore_footer; const char *buf = sb->buf; + /* footer must end with newline */ + if (!len || buf[len - 1] != '\n') + return 0; + prev = '\0'; for (i = len - 1; i > 0; i--) { - ch = buf[i]; + char ch = buf[i]; if (prev == '\n' && ch == '\n') /* paragraph break */ break; prev = ch; @@ -1041,15 +1070,9 @@ static int ends_rfc2822_footer(struct strbuf *sb, int ignore_footer) ; /* do nothing */ k++; - for (j = 0; i + j < len; j++) { - ch = buf[i + j]; - if (ch == ':') - break; - if (isalnum(ch) || - (ch == '-')) - continue; + if (!is_rfc2822_line(buf + i, k - i - 1) && + !is_cherry_picked_from_line(buf + i, k - i - 1)) return 0; - } } return 1; } @@ -1066,7 +1089,7 @@ void append_signoff(struct strbuf *msgbuf, int ignore_footer) for (i = msgbuf->len - 1 - ignore_footer; i > 0 && msgbuf->buf[i - 1] != '\n'; i--) ; /* do nothing */ if (prefixcmp(msgbuf->buf + i, sob.buf)) { - if (!i || !ends_rfc2822_footer(msgbuf, ignore_footer)) + if (!i || !has_conforming_footer(msgbuf, ignore_footer)) strbuf_splice(msgbuf, msgbuf->len - ignore_footer, 0, "\n", 1); strbuf_splice(msgbuf, msgbuf->len - ignore_footer, 0, sob.buf, sob.len); } -- cgit v1.2.1 From 2cdccad1600171e3c53e2e9c22e1675389a9d61a Mon Sep 17 00:00:00 2001 From: Brandon Casey Date: Tue, 12 Feb 2013 02:17:33 -0800 Subject: sequencer.c: require a conforming footer to be preceded by a blank line Currently, append_signoff() performs a search for the last line of the commit buffer by searching back from the end until it hits a newline. If it reaches the beginning of the buffer without finding a newline, that means either the commit message was empty, or there was only one line in it. In this case, append_signoff will skip the call to has_conforming_footer since it already knows that it is necessary to append a newline before appending the sob. Let's perform this function inside of has_conforming_footer where it appropriately belongs and generalize it so that we require that the footer paragraph be an actual distinct paragraph separated by a blank line. Signed-off-by: Brandon Casey Signed-off-by: Junio C Hamano --- sequencer.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'sequencer.c') diff --git a/sequencer.c b/sequencer.c index 7889b6bc19..5a9c43d806 100644 --- a/sequencer.c +++ b/sequencer.c @@ -1061,6 +1061,10 @@ static int has_conforming_footer(struct strbuf *sb, int ignore_footer) prev = ch; } + /* require at least one blank line */ + if (prev != '\n' || buf[i] != '\n') + return 0; + /* advance to start of last paragraph */ while (i < len - 1 && buf[i] == '\n') i++; @@ -1089,7 +1093,7 @@ void append_signoff(struct strbuf *msgbuf, int ignore_footer) for (i = msgbuf->len - 1 - ignore_footer; i > 0 && msgbuf->buf[i - 1] != '\n'; i--) ; /* do nothing */ if (prefixcmp(msgbuf->buf + i, sob.buf)) { - if (!i || !has_conforming_footer(msgbuf, ignore_footer)) + if (!has_conforming_footer(msgbuf, ignore_footer)) strbuf_splice(msgbuf, msgbuf->len - ignore_footer, 0, "\n", 1); strbuf_splice(msgbuf, msgbuf->len - ignore_footer, 0, sob.buf, sob.len); } -- cgit v1.2.1 From b971e04f54e73a5a2af54ad62540273bb90ee7bb Mon Sep 17 00:00:00 2001 From: Brandon Casey Date: Tue, 12 Feb 2013 02:17:34 -0800 Subject: sequencer.c: always separate "(cherry picked from" from commit body Start treating the "(cherry picked from" line added by cherry-pick -x the same way that the s-o-b lines are treated. Namely, separate them from the main commit message body with an empty line. Introduce tests to test this functionality. Signed-off-by: Brandon Casey Reviewed-by: Jonathan Nieder Signed-off-by: Junio C Hamano --- sequencer.c | 128 ++++++++++++++++++++++++++++++------------------------------ 1 file changed, 65 insertions(+), 63 deletions(-) (limited to 'sequencer.c') diff --git a/sequencer.c b/sequencer.c index 5a9c43d806..f9f9f8a772 100644 --- a/sequencer.c +++ b/sequencer.c @@ -20,6 +20,69 @@ const char sign_off_header[] = "Signed-off-by: "; static const char cherry_picked_prefix[] = "(cherry picked from commit "; +static int is_rfc2822_line(const char *buf, int len) +{ + int i; + + for (i = 0; i < len; i++) { + int ch = buf[i]; + if (ch == ':') + return 1; + if (!isalnum(ch) && ch != '-') + break; + } + + return 0; +} + +static int is_cherry_picked_from_line(const char *buf, int len) +{ + /* + * We only care that it looks roughly like (cherry picked from ...) + */ + return len > strlen(cherry_picked_prefix) + 1 && + !prefixcmp(buf, cherry_picked_prefix) && buf[len - 1] == ')'; +} + +static int has_conforming_footer(struct strbuf *sb, int ignore_footer) +{ + char prev; + int i, k; + int len = sb->len - ignore_footer; + const char *buf = sb->buf; + + /* footer must end with newline */ + if (!len || buf[len - 1] != '\n') + return 0; + + prev = '\0'; + for (i = len - 1; i > 0; i--) { + char ch = buf[i]; + if (prev == '\n' && ch == '\n') /* paragraph break */ + break; + prev = ch; + } + + /* require at least one blank line */ + if (prev != '\n' || buf[i] != '\n') + return 0; + + /* advance to start of last paragraph */ + while (i < len - 1 && buf[i] == '\n') + i++; + + for (; i < len; i = k) { + for (k = i; k < len && buf[k] != '\n'; k++) + ; /* do nothing */ + k++; + + if (!is_rfc2822_line(buf + i, k - i - 1) && + !is_cherry_picked_from_line(buf + i, k - i - 1)) + return 0; + } + return 1; +} + static void remove_sequencer_state(void) { struct strbuf seq_dir = STRBUF_INIT; @@ -493,6 +556,8 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts) } if (opts->record_origin) { + if (!has_conforming_footer(&msgbuf, 0)) + strbuf_addch(&msgbuf, '\n'); strbuf_addstr(&msgbuf, cherry_picked_prefix); strbuf_addstr(&msgbuf, sha1_to_hex(commit->object.sha1)); strbuf_addstr(&msgbuf, ")\n"); @@ -1018,69 +1083,6 @@ int sequencer_pick_revisions(struct replay_opts *opts) return pick_commits(todo_list, opts); } -static int is_rfc2822_line(const char *buf, int len) -{ - int i; - - for (i = 0; i < len; i++) { - int ch = buf[i]; - if (ch == ':') - return 1; - if (!isalnum(ch) && ch != '-') - break; - } - - return 0; -} - -static int is_cherry_picked_from_line(const char *buf, int len) -{ - /* - * We only care that it looks roughly like (cherry picked from ...) - */ - return len > strlen(cherry_picked_prefix) + 1 && - !prefixcmp(buf, cherry_picked_prefix) && buf[len - 1] == ')'; -} - -static int has_conforming_footer(struct strbuf *sb, int ignore_footer) -{ - char prev; - int i, k; - int len = sb->len - ignore_footer; - const char *buf = sb->buf; - - /* footer must end with newline */ - if (!len || buf[len - 1] != '\n') - return 0; - - prev = '\0'; - for (i = len - 1; i > 0; i--) { - char ch = buf[i]; - if (prev == '\n' && ch == '\n') /* paragraph break */ - break; - prev = ch; - } - - /* require at least one blank line */ - if (prev != '\n' || buf[i] != '\n') - return 0; - - /* advance to start of last paragraph */ - while (i < len - 1 && buf[i] == '\n') - i++; - - for (; i < len; i = k) { - for (k = i; k < len && buf[k] != '\n'; k++) - ; /* do nothing */ - k++; - - if (!is_rfc2822_line(buf + i, k - i - 1) && - !is_cherry_picked_from_line(buf + i, k - i - 1)) - return 0; - } - return 1; -} - void append_signoff(struct strbuf *msgbuf, int ignore_footer) { struct strbuf sob = STRBUF_INIT; -- cgit v1.2.1 From bab4d1097c8be7d688a53b992232063dbf300fd4 Mon Sep 17 00:00:00 2001 From: Brandon Casey Date: Tue, 12 Feb 2013 02:17:35 -0800 Subject: sequencer.c: teach append_signoff how to detect duplicate s-o-b Teach append_signoff how to detect a duplicate s-o-b in the commit footer. This is in preparation to unify the append_signoff implementations in log-tree.c and sequencer.c. Fixes test in t3511. Signed-off-by: Brandon Casey Signed-off-by: Junio C Hamano --- sequencer.c | 59 +++++++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 45 insertions(+), 14 deletions(-) (limited to 'sequencer.c') diff --git a/sequencer.c b/sequencer.c index f9f9f8a772..965b954baf 100644 --- a/sequencer.c +++ b/sequencer.c @@ -44,12 +44,20 @@ static int is_cherry_picked_from_line(const char *buf, int len) !prefixcmp(buf, cherry_picked_prefix) && buf[len - 1] == ')'; } -static int has_conforming_footer(struct strbuf *sb, int ignore_footer) +/* + * Returns 0 for non-conforming footer + * Returns 1 for conforming footer + * Returns 2 when sob exists within conforming footer + * Returns 3 when sob exists within conforming footer as last entry + */ +static int has_conforming_footer(struct strbuf *sb, struct strbuf *sob, + int ignore_footer) { char prev; int i, k; int len = sb->len - ignore_footer; const char *buf = sb->buf; + int found_sob = 0; /* footer must end with newline */ if (!len || buf[len - 1] != '\n') @@ -72,14 +80,25 @@ static int has_conforming_footer(struct strbuf *sb, int ignore_footer) i++; for (; i < len; i = k) { + int found_rfc2822; + for (k = i; k < len && buf[k] != '\n'; k++) ; /* do nothing */ k++; - if (!is_rfc2822_line(buf + i, k - i - 1) && - !is_cherry_picked_from_line(buf + i, k - i - 1)) + found_rfc2822 = is_rfc2822_line(buf + i, k - i - 1); + if (found_rfc2822 && sob && + !strncmp(buf + i, sob->buf, sob->len)) + found_sob = k; + + if (!(found_rfc2822 || + is_cherry_picked_from_line(buf + i, k - i - 1))) return 0; } + if (found_sob == i) + return 3; + if (found_sob) + return 2; return 1; } @@ -300,7 +319,7 @@ static int do_recursive_merge(struct commit *base, struct commit *next, rollback_lock_file(&index_lock); if (opts->signoff) - append_signoff(msgbuf, 0); + append_signoff(msgbuf, 0, 0); if (!clean) { int i; @@ -556,7 +575,7 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts) } if (opts->record_origin) { - if (!has_conforming_footer(&msgbuf, 0)) + if (!has_conforming_footer(&msgbuf, NULL, 0)) strbuf_addch(&msgbuf, '\n'); strbuf_addstr(&msgbuf, cherry_picked_prefix); strbuf_addstr(&msgbuf, sha1_to_hex(commit->object.sha1)); @@ -1083,21 +1102,33 @@ int sequencer_pick_revisions(struct replay_opts *opts) return pick_commits(todo_list, opts); } -void append_signoff(struct strbuf *msgbuf, int ignore_footer) +void append_signoff(struct strbuf *msgbuf, int ignore_footer, unsigned flag) { + unsigned no_dup_sob = flag & APPEND_SIGNOFF_DEDUP; struct strbuf sob = STRBUF_INIT; - int i; + int has_footer; strbuf_addstr(&sob, sign_off_header); strbuf_addstr(&sob, fmt_name(getenv("GIT_COMMITTER_NAME"), getenv("GIT_COMMITTER_EMAIL"))); strbuf_addch(&sob, '\n'); - for (i = msgbuf->len - 1 - ignore_footer; i > 0 && msgbuf->buf[i - 1] != '\n'; i--) - ; /* do nothing */ - if (prefixcmp(msgbuf->buf + i, sob.buf)) { - if (!has_conforming_footer(msgbuf, ignore_footer)) - strbuf_splice(msgbuf, msgbuf->len - ignore_footer, 0, "\n", 1); - strbuf_splice(msgbuf, msgbuf->len - ignore_footer, 0, sob.buf, sob.len); - } + + /* + * If the whole message buffer is equal to the sob, pretend that we + * found a conforming footer with a matching sob + */ + if (msgbuf->len - ignore_footer == sob.len && + !strncmp(msgbuf->buf, sob.buf, sob.len)) + has_footer = 3; + else + has_footer = has_conforming_footer(msgbuf, &sob, ignore_footer); + + if (!has_footer) + strbuf_splice(msgbuf, msgbuf->len - ignore_footer, 0, "\n", 1); + + if (has_footer != 3 && (!no_dup_sob || has_footer != 2)) + strbuf_splice(msgbuf, msgbuf->len - ignore_footer, 0, + sob.buf, sob.len); + strbuf_release(&sob); } -- cgit v1.2.1 From 33f2f9ab4e5c343b21a9e602456e7facabd609a0 Mon Sep 17 00:00:00 2001 From: Brandon Casey Date: Tue, 12 Feb 2013 02:33:42 -0800 Subject: sequencer.c: teach append_signoff to avoid adding a duplicate newline Teach append_signoff to detect whether a blank line exists at the position that the signed-off-by line will be added, and refrain from adding an additional one if one already exists. Or, add an additional line if one is needed to make sure the new footer is separated from the message body by a blank line. Signed-off-by: Brandon Casey Signed-off-by: Junio C Hamano --- sequencer.c | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) (limited to 'sequencer.c') diff --git a/sequencer.c b/sequencer.c index 965b954baf..53ee49a9b0 100644 --- a/sequencer.c +++ b/sequencer.c @@ -1123,8 +1123,19 @@ void append_signoff(struct strbuf *msgbuf, int ignore_footer, unsigned flag) else has_footer = has_conforming_footer(msgbuf, &sob, ignore_footer); - if (!has_footer) - strbuf_splice(msgbuf, msgbuf->len - ignore_footer, 0, "\n", 1); + if (!has_footer) { + const char *append_newlines = NULL; + size_t len = msgbuf->len - ignore_footer; + + if (len && msgbuf->buf[len - 1] != '\n') + append_newlines = "\n\n"; + else if (len > 1 && msgbuf->buf[len - 2] != '\n') + append_newlines = "\n"; + + if (append_newlines) + strbuf_splice(msgbuf, msgbuf->len - ignore_footer, 0, + append_newlines, strlen(append_newlines)); + } if (has_footer != 3 && (!no_dup_sob || has_footer != 2)) strbuf_splice(msgbuf, msgbuf->len - ignore_footer, 0, -- cgit v1.2.1 From 8c613fd5efa1c3bbd48845ed91c533192e699c08 Mon Sep 17 00:00:00 2001 From: Brandon Casey Date: Fri, 22 Feb 2013 14:05:27 -0800 Subject: git-commit: populate the edit buffer with 2 blank lines before s-o-b 'commit -s' populates the edit buffer with a blank line before the Signed-off-by line, to allow the user to immediately start typing the log message. But commit 33f2f9ab removed this space, forcing the user to first push the Signed-off-by line down to open a place to type the log message. Fix this regression and let's ensure that the Signed-off-by line is preceded by two blank lines, instead of just one, to hint that something should be filled in, and that a blank line should separate it from the body and the Signed-off-by line. Add a test for this behavior. Reported-by: John Keeping Signed-off-by: Brandon Casey Signed-off-by: Junio C Hamano --- sequencer.c | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) (limited to 'sequencer.c') diff --git a/sequencer.c b/sequencer.c index 53ee49a9b0..a07d2d00b9 100644 --- a/sequencer.c +++ b/sequencer.c @@ -1127,10 +1127,33 @@ void append_signoff(struct strbuf *msgbuf, int ignore_footer, unsigned flag) const char *append_newlines = NULL; size_t len = msgbuf->len - ignore_footer; - if (len && msgbuf->buf[len - 1] != '\n') + if (!len) { + /* + * The buffer is completely empty. Leave foom for + * the title and body to be filled in by the user. + */ append_newlines = "\n\n"; - else if (len > 1 && msgbuf->buf[len - 2] != '\n') + } else if (msgbuf->buf[len - 1] != '\n') { + /* + * Incomplete line. Complete the line and add a + * blank one so that there is an empty line between + * the message body and the sob. + */ + append_newlines = "\n\n"; + } else if (len == 1) { + /* + * Buffer contains a single newline. Add another + * so that we leave room for the title and body. + */ + append_newlines = "\n"; + } else if (msgbuf->buf[len - 2] != '\n') { + /* + * Buffer ends with a single newline. Add another + * so that there is an empty line between the message + * body and the sob. + */ append_newlines = "\n"; + } /* else, the buffer already ends with two newlines. */ if (append_newlines) strbuf_splice(msgbuf, msgbuf->len - ignore_footer, 0, -- cgit v1.2.1 From 21246dbb9e0af278bc671e32e45a6cae4ad38b6f Mon Sep 17 00:00:00 2001 From: Miklos Vajna Date: Thu, 11 Apr 2013 15:06:52 +0200 Subject: cherry-pick: make sure all input objects are commits When a single argument was a non-commit, the error message used to be: fatal: BUG: expected exactly one commit from walk For multiple arguments, when none of the arguments was a commit, the error was: fatal: empty commit set passed Finally, when some of the arguments were non-commits, we ignored those arguments. Fix this bug and make sure all arguments are commits, and for the first non-commit, error out with: fatal: : Can't cherry-pick a Signed-off-by: Miklos Vajna Signed-off-by: Junio C Hamano --- sequencer.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) (limited to 'sequencer.c') diff --git a/sequencer.c b/sequencer.c index baa031052e..61fdb6805c 100644 --- a/sequencer.c +++ b/sequencer.c @@ -1047,6 +1047,7 @@ int sequencer_pick_revisions(struct replay_opts *opts) { struct commit_list *todo_list = NULL; unsigned char sha1[20]; + int i; if (opts->subcommand == REPLAY_NONE) assert(opts->revs); @@ -1067,6 +1068,23 @@ int sequencer_pick_revisions(struct replay_opts *opts) if (opts->subcommand == REPLAY_CONTINUE) return sequencer_continue(opts); + for (i = 0; i < opts->revs->pending.nr; i++) { + unsigned char sha1[20]; + const char *name = opts->revs->pending.objects[i].name; + + /* This happens when using --stdin. */ + if (!strlen(name)) + continue; + + if (!get_sha1(name, sha1)) { + enum object_type type = sha1_object_info(sha1, NULL); + + if (type > 0 && type != OBJ_COMMIT) + die(_("%s: can't cherry-pick a %s"), name, typename(type)); + } else + die(_("%s: bad revision"), name); + } + /* * If we were called as "git cherry-pick ", just * cherry-pick/revert it, set CHERRY_PICK_HEAD / -- cgit v1.2.1 From 41ccfdd9c931ee71b09f38f4eb19cf44a8e4381d Mon Sep 17 00:00:00 2001 From: Stefano Lattarini Date: Fri, 12 Apr 2013 00:36:10 +0200 Subject: Correct common spelling mistakes in comments and tests Most of these were found using Lucas De Marchi's codespell tool. Signed-off-by: Stefano Lattarini Signed-off-by: Jonathan Nieder Acked-by: Matthieu Moy Signed-off-by: Junio C Hamano --- sequencer.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'sequencer.c') diff --git a/sequencer.c b/sequencer.c index aef5e8a017..bad5077911 100644 --- a/sequencer.c +++ b/sequencer.c @@ -133,7 +133,7 @@ static void print_advice(int show_hint, struct replay_opts *opts) if (msg) { fprintf(stderr, "%s\n", msg); /* - * A conflict has occured but the porcelain + * A conflict has occurred but the porcelain * (typically rebase --interactive) wants to take care * of the commit itself so remove CHERRY_PICK_HEAD */ -- cgit v1.2.1 From 7c0b0d8deae040969d78eb141d899e225f1577e6 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 9 May 2013 13:27:49 -0700 Subject: cherry-pick: picking a tag that resolves to a commit is OK Earlier, 21246dbb9e0a (cherry-pick: make sure all input objects are commits, 2013-04-11) tried to catch an unlikely "git cherry-pick $blob" as an error, but broke a more important use case to cherry-pick a tag that points at a commit. Signed-off-by: Junio C Hamano --- sequencer.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'sequencer.c') diff --git a/sequencer.c b/sequencer.c index 61fdb6805c..f2c9d98f8f 100644 --- a/sequencer.c +++ b/sequencer.c @@ -1077,10 +1077,10 @@ int sequencer_pick_revisions(struct replay_opts *opts) continue; if (!get_sha1(name, sha1)) { - enum object_type type = sha1_object_info(sha1, NULL); - - if (type > 0 && type != OBJ_COMMIT) + if (!lookup_commit_reference_gently(sha1, 1)) { + enum object_type type = sha1_object_info(sha1, NULL); die(_("%s: can't cherry-pick a %s"), name, typename(type)); + } } else die(_("%s: bad revision"), name); } -- cgit v1.2.1 From c8d1351deb2ccb8634d5c3dd863dd37d0d8c082c Mon Sep 17 00:00:00 2001 From: Felipe Contreras Date: Tue, 28 May 2013 22:56:21 -0500 Subject: sequencer: remove useless indentation By using good ol' goto. Signed-off-by: Felipe Contreras Signed-off-by: Junio C Hamano --- sequencer.c | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) (limited to 'sequencer.c') diff --git a/sequencer.c b/sequencer.c index ab6f8a722d..b4989ba2c3 100644 --- a/sequencer.c +++ b/sequencer.c @@ -474,7 +474,7 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts) struct commit_message msg = { NULL, NULL, NULL, NULL, NULL }; char *defmsg = NULL; struct strbuf msgbuf = STRBUF_INIT; - int res, unborn = 0; + int res, unborn = 0, allow; if (opts->no_commit) { /* @@ -624,14 +624,16 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts) msg.subject); print_advice(res == 1, opts); rerere(opts->allow_rerere_auto); - } else { - int allow = allow_empty(opts, commit); - if (allow < 0) - return allow; - if (!opts->no_commit) - res = run_git_commit(defmsg, opts, allow); + goto leave; } + allow = allow_empty(opts, commit); + if (allow < 0) + return allow; + if (!opts->no_commit) + res = run_git_commit(defmsg, opts, allow); + +leave: free_message(&msg); free(defmsg); -- cgit v1.2.1 From 706728a37c53c3d1c3fc38aa9c9842060d03e9cc Mon Sep 17 00:00:00 2001 From: Felipe Contreras Date: Thu, 6 Jun 2013 03:58:57 -0500 Subject: sequencer: avoid leaking message buffer when refusing to create an empty commit We should free objects before leaving. Signed-off-by: Felipe Contreras Signed-off-by: Junio C Hamano --- sequencer.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'sequencer.c') diff --git a/sequencer.c b/sequencer.c index b4989ba2c3..f7be7d8be6 100644 --- a/sequencer.c +++ b/sequencer.c @@ -628,8 +628,10 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts) } allow = allow_empty(opts, commit); - if (allow < 0) - return allow; + if (allow < 0) { + res = allow; + goto leave; + } if (!opts->no_commit) res = run_git_commit(defmsg, opts, allow); -- cgit v1.2.1 From eb4be1cbe2e50c9bfaa09839564723de257869fc Mon Sep 17 00:00:00 2001 From: Ramkumar Ramachandra Date: Wed, 19 Jun 2013 13:07:09 +0530 Subject: sequencer: write useful reflog message for fast-forward The following command $ git cherry-pick --ff b8bb3f writes the following uninformative message to the reflog cherry-pick Improve it to cherry-pick: fast-forward Avoid hard-coding "cherry-pick" in fast_forward_to(), so the sequencer is generic enough to support future actions. Signed-off-by: Ramkumar Ramachandra Signed-off-by: Junio C Hamano --- sequencer.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) (limited to 'sequencer.c') diff --git a/sequencer.c b/sequencer.c index ab6f8a722d..423c1f583d 100644 --- a/sequencer.c +++ b/sequencer.c @@ -270,15 +270,20 @@ static int error_dirty_index(struct replay_opts *opts) } static int fast_forward_to(const unsigned char *to, const unsigned char *from, - int unborn) + int unborn, struct replay_opts *opts) { struct ref_lock *ref_lock; + struct strbuf sb = STRBUF_INIT; + int ret; read_cache(); if (checkout_fast_forward(from, to, 1)) exit(1); /* the callee should have complained already */ ref_lock = lock_any_ref_for_update("HEAD", unborn ? null_sha1 : from, 0); - return write_ref_sha1(ref_lock, to, "cherry-pick"); + strbuf_addf(&sb, "%s: fast-forward", action_name(opts)); + ret = write_ref_sha1(ref_lock, to, sb.buf); + strbuf_release(&sb); + return ret; } static int do_recursive_merge(struct commit *base, struct commit *next, @@ -523,7 +528,7 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts) if (opts->allow_ff && ((parent && !hashcmp(parent->object.sha1, head)) || (!parent && unborn))) - return fast_forward_to(commit->object.sha1, head, unborn); + return fast_forward_to(commit->object.sha1, head, unborn, opts); if (parent && parse_commit(parent) < 0) /* TRANSLATORS: The first %s will be "revert" or -- cgit v1.2.1 From 9c5e6c802cde9881785b7f1b3278b97be4aabd82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Tue, 9 Jul 2013 22:29:00 +0700 Subject: Convert "struct cache_entry *" to "const ..." wherever possible MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I attempted to make index_state->cache[] a "const struct cache_entry **" to find out how existing entries in index are modified and where. The question I have is what do we do if we really need to keep track of on-disk changes in the index. The result is - diff-lib.c: setting CE_UPTODATE - name-hash.c: setting CE_HASHED - preload-index.c, read-cache.c, unpack-trees.c and builtin/update-index: obvious - entry.c: write_entry() may refresh the checked out entry via fill_stat_cache_info(). This causes "non-const struct cache_entry *" in builtin/apply.c, builtin/checkout-index.c and builtin/checkout.c - builtin/ls-files.c: --with-tree changes stagemask and may set CE_UPDATE Of these, write_entry() and its call sites are probably most interesting because it modifies on-disk info. But this is stat info and can be retrieved via refresh, at least for porcelain commands. Other just uses ce_flags for local purposes. So, keeping track of "dirty" entries is just a matter of setting a flag in index modification functions exposed by read-cache.c. Except unpack-trees, the rest of the code base does not do anything funny behind read-cache's back. The actual patch is less valueable than the summary above. But if anyone wants to re-identify the above sites. Applying this patch, then this: diff --git a/cache.h b/cache.h index 430d021..1692891 100644 --- a/cache.h +++ b/cache.h @@ -267,7 +267,7 @@ static inline unsigned int canon_mode(unsigned int mode) #define cache_entry_size(len) (offsetof(struct cache_entry,name) + (len) + 1) struct index_state { - struct cache_entry **cache; + const struct cache_entry **cache; unsigned int version; unsigned int cache_nr, cache_alloc, cache_changed; struct string_list *resolve_undo; will help quickly identify them without bogus warnings. Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- sequencer.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'sequencer.c') diff --git a/sequencer.c b/sequencer.c index 425207ad5f..351548f57d 100644 --- a/sequencer.c +++ b/sequencer.c @@ -331,7 +331,7 @@ static int do_recursive_merge(struct commit *base, struct commit *next, int i; strbuf_addstr(msgbuf, "\nConflicts:\n"); for (i = 0; i < active_nr;) { - struct cache_entry *ce = active_cache[i++]; + const struct cache_entry *ce = active_cache[i++]; if (ce_stage(ce)) { strbuf_addch(msgbuf, '\t'); strbuf_addstr(msgbuf, ce->name); @@ -371,8 +371,9 @@ static int is_index_unchanged(void) active_cache_tree = cache_tree(); if (!cache_tree_fully_valid(active_cache_tree)) - if (cache_tree_update(active_cache_tree, active_cache, - active_nr, 0)) + if (cache_tree_update(active_cache_tree, + (const struct cache_entry * const *)active_cache, + active_nr, 0)) return error(_("Unable to update cache tree\n")); return !hashcmp(active_cache_tree->sha1, head_commit->tree->object.sha1); -- cgit v1.2.1