summaryrefslogtreecommitdiff
path: root/sequencer.c
diff options
context:
space:
mode:
Diffstat (limited to 'sequencer.c')
-rw-r--r--sequencer.c2934
1 files changed, 2093 insertions, 841 deletions
diff --git a/sequencer.c b/sequencer.c
index b9dbf1a..f49a871 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -1,22 +1,34 @@
-#include "cache.h"
+#include "git-compat-util.h"
+#include "abspath.h"
+#include "advice.h"
#include "config.h"
+#include "copy.h"
+#include "environment.h"
+#include "gettext.h"
+#include "hex.h"
#include "lockfile.h"
#include "dir.h"
-#include "object-store.h"
+#include "object-file.h"
+#include "object-name.h"
+#include "object-store-ll.h"
#include "object.h"
+#include "pager.h"
#include "commit.h"
#include "sequencer.h"
-#include "tag.h"
#include "run-command.h"
-#include "exec-cmd.h"
+#include "hook.h"
#include "utf8.h"
#include "cache-tree.h"
#include "diff.h"
+#include "path.h"
#include "revision.h"
#include "rerere.h"
-#include "merge-recursive.h"
+#include "merge.h"
+#include "merge-ort.h"
+#include "merge-ort-wrappers.h"
#include "refs.h"
-#include "argv-array.h"
+#include "sparse-index.h"
+#include "strvec.h"
#include "quote.h"
#include "trailer.h"
#include "log-tree.h"
@@ -25,22 +37,32 @@
#include "notes-utils.h"
#include "sigchain.h"
#include "unpack-trees.h"
-#include "worktree.h"
#include "oidmap.h"
#include "oidset.h"
#include "commit-slab.h"
#include "alias.h"
#include "commit-reach.h"
#include "rebase-interactive.h"
+#include "reset.h"
+#include "branch.h"
#define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION"
+/*
+ * To accommodate common filesystem limitations, where the loose refs' file
+ * names must not exceed `NAME_MAX`, the labels generated by `git rebase
+ * --rebase-merges` need to be truncated if the corresponding commit subjects
+ * are too long.
+ * Add some margin to stay clear from reaching `NAME_MAX`.
+ */
+#define GIT_MAX_LABEL_LENGTH ((NAME_MAX) - (LOCK_SUFFIX_LEN) - 16)
+
static const char sign_off_header[] = "Signed-off-by: ";
static const char cherry_picked_prefix[] = "(cherry picked from commit ";
GIT_PATH_FUNC(git_path_commit_editmsg, "COMMIT_EDITMSG")
-GIT_PATH_FUNC(git_path_seq_dir, "sequencer")
+static GIT_PATH_FUNC(git_path_seq_dir, "sequencer")
static GIT_PATH_FUNC(git_path_todo_file, "sequencer/todo")
static GIT_PATH_FUNC(git_path_opts_file, "sequencer/opts")
@@ -57,6 +79,8 @@ static GIT_PATH_FUNC(rebase_path, "rebase-merge")
GIT_PATH_FUNC(rebase_path_todo, "rebase-merge/git-rebase-todo")
GIT_PATH_FUNC(rebase_path_todo_backup, "rebase-merge/git-rebase-todo.backup")
+GIT_PATH_FUNC(rebase_path_dropped, "rebase-merge/dropped")
+
/*
* The rebase command lines that have already been processed. A line
* is moved here when it is first handled, before any associated user
@@ -117,10 +141,15 @@ static GIT_PATH_FUNC(rebase_path_author_script, "rebase-merge/author-script")
static GIT_PATH_FUNC(rebase_path_amend, "rebase-merge/amend")
/*
* When we stop at a given patch via the "edit" command, this file contains
- * the abbreviated commit name of the corresponding patch.
+ * the commit object name of the corresponding patch.
*/
static GIT_PATH_FUNC(rebase_path_stopped_sha, "rebase-merge/stopped-sha")
/*
+ * When we stop for the user to resolve conflicts this file contains
+ * the patch of the commit that is being picked.
+ */
+static GIT_PATH_FUNC(rebase_path_patch, "rebase-merge/patch")
+/*
* For the post-rewrite hook, we make a list of rewritten commits and
* their new sha1s. The rewritten-pending list keeps the sha1s of
* commits that have been processed, but not committed yet,
@@ -143,10 +172,26 @@ static GIT_PATH_FUNC(rebase_path_squash_onto, "rebase-merge/squash-onto")
static GIT_PATH_FUNC(rebase_path_refs_to_delete, "rebase-merge/refs-to-delete")
/*
+ * The update-refs file stores a list of refs that will be updated at the end
+ * of the rebase sequence. The 'update-ref <ref>' commands in the todo file
+ * update the OIDs for the refs in this file, but the refs are not updated
+ * until the end of the rebase sequence.
+ *
+ * rebase_path_update_refs() returns the path to this file for a given
+ * worktree directory. For the current worktree, pass the_repository->gitdir.
+ */
+static char *rebase_path_update_refs(const char *wt_git_dir)
+{
+ return xstrfmt("%s/rebase-merge/update-refs", wt_git_dir);
+}
+
+/*
* The following files are written by git-rebase just after parsing the
* command-line.
*/
static GIT_PATH_FUNC(rebase_path_gpg_sign_opt, "rebase-merge/gpg_sign_opt")
+static GIT_PATH_FUNC(rebase_path_cdate_is_adate, "rebase-merge/cdate_is_adate")
+static GIT_PATH_FUNC(rebase_path_ignore_date, "rebase-merge/ignore_date")
static GIT_PATH_FUNC(rebase_path_orig_head, "rebase-merge/orig-head")
static GIT_PATH_FUNC(rebase_path_verbose, "rebase-merge/verbose")
static GIT_PATH_FUNC(rebase_path_quiet, "rebase-merge/quiet")
@@ -158,38 +203,61 @@ static GIT_PATH_FUNC(rebase_path_strategy, "rebase-merge/strategy")
static GIT_PATH_FUNC(rebase_path_strategy_opts, "rebase-merge/strategy_opts")
static GIT_PATH_FUNC(rebase_path_allow_rerere_autoupdate, "rebase-merge/allow_rerere_autoupdate")
static GIT_PATH_FUNC(rebase_path_reschedule_failed_exec, "rebase-merge/reschedule-failed-exec")
+static GIT_PATH_FUNC(rebase_path_no_reschedule_failed_exec, "rebase-merge/no-reschedule-failed-exec")
+static GIT_PATH_FUNC(rebase_path_drop_redundant_commits, "rebase-merge/drop_redundant_commits")
+static GIT_PATH_FUNC(rebase_path_keep_redundant_commits, "rebase-merge/keep_redundant_commits")
-static int git_sequencer_config(const char *k, const char *v, void *cb)
+/**
+ * A 'struct update_refs_record' represents a value in the update-refs
+ * list. We use a string_list to map refs to these (before, after) pairs.
+ */
+struct update_ref_record {
+ struct object_id before;
+ struct object_id after;
+};
+
+static struct update_ref_record *init_update_ref_record(const char *ref)
+{
+ struct update_ref_record *rec;
+
+ CALLOC_ARRAY(rec, 1);
+
+ oidcpy(&rec->before, null_oid());
+ oidcpy(&rec->after, null_oid());
+
+ /* This may fail, but that's fine, we will keep the null OID. */
+ read_ref(ref, &rec->before);
+
+ return rec;
+}
+
+static int git_sequencer_config(const char *k, const char *v,
+ const struct config_context *ctx, void *cb)
{
struct replay_opts *opts = cb;
- int status;
if (!strcmp(k, "commit.cleanup")) {
- const char *s;
-
- status = git_config_string(&s, k, v);
- if (status)
- return status;
+ if (!v)
+ return config_error_nonbool(k);
- if (!strcmp(s, "verbatim")) {
+ if (!strcmp(v, "verbatim")) {
opts->default_msg_cleanup = COMMIT_MSG_CLEANUP_NONE;
opts->explicit_cleanup = 1;
- } else if (!strcmp(s, "whitespace")) {
+ } else if (!strcmp(v, "whitespace")) {
opts->default_msg_cleanup = COMMIT_MSG_CLEANUP_SPACE;
opts->explicit_cleanup = 1;
- } else if (!strcmp(s, "strip")) {
+ } else if (!strcmp(v, "strip")) {
opts->default_msg_cleanup = COMMIT_MSG_CLEANUP_ALL;
opts->explicit_cleanup = 1;
- } else if (!strcmp(s, "scissors")) {
+ } else if (!strcmp(v, "scissors")) {
opts->default_msg_cleanup = COMMIT_MSG_CLEANUP_SCISSORS;
opts->explicit_cleanup = 1;
} else {
warning(_("invalid commit message cleanup mode '%s'"),
- s);
+ v);
}
- free((char *)s);
- return status;
+ return 0;
}
if (!strcmp(k, "commit.gpgsign")) {
@@ -197,11 +265,24 @@ static int git_sequencer_config(const char *k, const char *v, void *cb)
return 0;
}
- status = git_gpg_config(k, v, NULL);
- if (status)
- return status;
+ if (!opts->default_strategy && !strcmp(k, "pull.twohead")) {
+ int ret = git_config_string((const char**)&opts->default_strategy, k, v);
+ if (ret == 0) {
+ /*
+ * pull.twohead is allowed to be multi-valued; we only
+ * care about the first value.
+ */
+ char *tmp = strchr(opts->default_strategy, ' ');
+ if (tmp)
+ *tmp = '\0';
+ }
+ return ret;
+ }
- return git_diff_basic_config(k, v, NULL);
+ if (opts->action == REPLAY_REVERT && !strcmp(k, "revert.reference"))
+ opts->commit_use_reference = git_config_bool(k, v);
+
+ return git_diff_basic_config(k, v, ctx, NULL);
}
void sequencer_init_config(struct replay_opts *opts)
@@ -242,12 +323,21 @@ static int has_conforming_footer(struct strbuf *sb, struct strbuf *sob,
struct trailer_info info;
size_t i;
int found_sob = 0, found_sob_last = 0;
+ char saved_char;
opts.no_divider = 1;
+ if (ignore_footer) {
+ saved_char = sb->buf[sb->len - ignore_footer];
+ sb->buf[sb->len - ignore_footer] = '\0';
+ }
+
trailer_info_get(&info, sb->buf, &opts);
- if (info.trailer_start == info.trailer_end)
+ if (ignore_footer)
+ sb->buf[sb->len - ignore_footer] = saved_char;
+
+ if (info.trailer_block_start == info.trailer_block_end)
return 0;
for (i = 0; i < info.trailer_nr; i++)
@@ -276,10 +366,23 @@ static const char *gpg_sign_opt_quoted(struct replay_opts *opts)
return buf.buf;
}
+void replay_opts_release(struct replay_opts *opts)
+{
+ free(opts->gpg_sign);
+ free(opts->reflog_action);
+ free(opts->default_strategy);
+ free(opts->strategy);
+ strvec_clear (&opts->xopts);
+ strbuf_release(&opts->current_fixups);
+ if (opts->revs)
+ release_revisions(opts->revs);
+ free(opts->revs);
+}
+
int sequencer_remove_state(struct replay_opts *opts)
{
struct strbuf buf = STRBUF_INIT;
- int i, ret = 0;
+ int ret = 0;
if (is_rebase_i(opts) &&
strbuf_read_file(&buf, rebase_path_refs_to_delete(), 0) > 0) {
@@ -288,7 +391,7 @@ int sequencer_remove_state(struct replay_opts *opts)
char *eol = strchr(p, '\n');
if (eol)
*eol = '\0';
- if (delete_ref("(rebase -i) cleanup", p, NULL, 0) < 0) {
+ if (delete_ref("(rebase) cleanup", p, NULL, 0) < 0) {
warning(_("could not delete '%s'"), p);
ret = -1;
}
@@ -298,13 +401,6 @@ int sequencer_remove_state(struct replay_opts *opts)
}
}
- free(opts->gpg_sign);
- free(opts->strategy);
- for (i = 0; i < opts->xopts_nr; i++)
- free(opts->xopts[i]);
- free(opts->xopts);
- strbuf_release(&opts->current_fixups);
-
strbuf_reset(&buf);
strbuf_addstr(&buf, get_dir(opts));
if (remove_dir_recursively(&buf, 0))
@@ -322,7 +418,7 @@ static const char *action_name(const struct replay_opts *opts)
case REPLAY_PICK:
return N_("cherry-pick");
case REPLAY_INTERACTIVE_REBASE:
- return N_("rebase -i");
+ return N_("rebase");
}
die(_("unknown action: %d"), opts->action);
}
@@ -334,9 +430,9 @@ struct commit_message {
const char *message;
};
-static const char *short_commit_name(struct commit *commit)
+static const char *short_commit_name(struct repository *r, struct commit *commit)
{
- return find_unique_abbrev(&commit->object.oid, DEFAULT_ABBREV);
+ return repo_find_unique_abbrev(r, &commit->object.oid, DEFAULT_ABBREV);
}
static int get_message(struct commit *commit, struct commit_message *out)
@@ -344,13 +440,14 @@ static int get_message(struct commit *commit, struct commit_message *out)
const char *abbrev, *subject;
int subject_len;
- out->message = logmsg_reencode(commit, NULL, get_commit_output_encoding());
- abbrev = short_commit_name(commit);
+ out->message = repo_logmsg_reencode(the_repository, commit, NULL,
+ get_commit_output_encoding());
+ abbrev = short_commit_name(the_repository, commit);
subject_len = find_commit_subject(out->message, &subject);
out->subject = xmemdupz(subject, subject_len);
- out->label = xstrfmt("%s... %s", abbrev, out->subject);
+ out->label = xstrfmt("%s (%s)", abbrev, out->subject);
out->parent_label = xstrfmt("parent of %s", out->label);
return 0;
@@ -361,7 +458,7 @@ static void free_message(struct commit *commit, struct commit_message *msg)
free(msg->parent_label);
free(msg->label);
free(msg->subject);
- unuse_commit_buffer(commit, msg->message);
+ repo_unuse_commit_buffer(the_repository, commit, msg->message);
}
static void print_advice(struct repository *r, int show_hint,
@@ -370,13 +467,14 @@ static void print_advice(struct repository *r, int show_hint,
char *msg = getenv("GIT_CHERRY_PICK_HELP");
if (msg) {
- fprintf(stderr, "%s\n", msg);
+ advise("%s\n", msg);
/*
* A conflict has occurred 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(r));
+ refs_delete_ref(get_main_ref_store(r), "", "CHERRY_PICK_HEAD",
+ NULL, REF_NO_DEREF);
return;
}
@@ -384,10 +482,22 @@ static void print_advice(struct repository *r, int show_hint,
if (opts->no_commit)
advise(_("after resolving the conflicts, mark the corrected paths\n"
"with 'git add <paths>' or 'git rm <paths>'"));
+ else if (opts->action == REPLAY_PICK)
+ advise(_("After resolving the conflicts, mark them with\n"
+ "\"git add/rm <pathspec>\", then run\n"
+ "\"git cherry-pick --continue\".\n"
+ "You can instead skip this commit with \"git cherry-pick --skip\".\n"
+ "To abort and get back to the state before \"git cherry-pick\",\n"
+ "run \"git cherry-pick --abort\"."));
+ else if (opts->action == REPLAY_REVERT)
+ advise(_("After resolving the conflicts, mark them with\n"
+ "\"git add/rm <pathspec>\", then run\n"
+ "\"git revert --continue\".\n"
+ "You can instead skip this commit with \"git revert --skip\".\n"
+ "To abort and get back to the state before \"git revert\",\n"
+ "run \"git revert --abort\"."));
else
- advise(_("after resolving the conflicts, mark the corrected paths\n"
- "with 'git add <paths>' or 'git rm <paths>'\n"
- "and commit the result with 'git commit'"));
+ BUG("unexpected pick action in print_advice()");
}
}
@@ -415,25 +525,15 @@ static int write_message(const void *buf, size_t len, const char *filename,
return 0;
}
-/*
- * Reads a file that was presumably written by a shell script, i.e. with an
- * end-of-line marker that needs to be stripped.
- *
- * Note that only the last end-of-line marker is stripped, consistent with the
- * behavior of "$(cat path)" in a shell script.
- *
- * Returns 1 if the file was read, 0 if it could not be read or does not exist.
- */
-static int read_oneliner(struct strbuf *buf,
- const char *path, int skip_if_empty)
+int read_oneliner(struct strbuf *buf,
+ const char *path, unsigned flags)
{
int orig_len = buf->len;
- if (!file_exists(path))
- return 0;
-
if (strbuf_read_file(buf, path, 0) < 0) {
- warning_errno(_("could not read '%s'"), path);
+ if ((flags & READ_ONELINER_WARN_MISSING) ||
+ (errno != ENOENT && errno != ENOTDIR))
+ warning_errno(_("could not read '%s'"), path);
return 0;
}
@@ -443,7 +543,7 @@ static int read_oneliner(struct strbuf *buf,
buf->buf[buf->len] = '\0';
}
- if (skip_if_empty && buf->len == orig_len)
+ if ((flags & READ_ONELINER_SKIP_IF_EMPTY) && buf->len == orig_len)
return 0;
return 1;
@@ -457,12 +557,12 @@ static struct tree *empty_tree(struct repository *r)
static int error_dirty_index(struct repository *repo, struct replay_opts *opts)
{
if (repo_read_index_unmerged(repo))
- return error_resolve_conflict(_(action_name(opts)));
+ return error_resolve_conflict(action_name(opts));
error(_("your local changes would be overwritten by %s."),
_(action_name(opts)));
- if (advice_commit_before_merge)
+ if (advice_enabled(ADVICE_COMMIT_BEFORE_MERGE))
advise(_("commit your changes or stash them to proceed."));
return -1;
}
@@ -475,7 +575,7 @@ static void update_abort_safety_file(void)
if (!file_exists(git_path_seq_dir()))
return;
- if (!get_oid("HEAD", &head))
+ if (!repo_get_oid(the_repository, "HEAD", &head))
write_file(git_path_abort_safety_file(), "%s", oid_to_hex(&head));
else
write_file(git_path_abort_safety_file(), "%s", "");
@@ -495,13 +595,13 @@ static int fast_forward_to(struct repository *r,
if (checkout_fast_forward(r, from, to, 1))
return -1; /* the callee should have complained already */
- strbuf_addf(&sb, _("%s: fast-forward"), _(action_name(opts)));
+ strbuf_addf(&sb, "%s: fast-forward", action_name(opts));
transaction = ref_transaction_begin(&err);
if (!transaction ||
ref_transaction_update(transaction, "HEAD",
to, unborn && !is_rebase_i(opts) ?
- &null_oid : from,
+ null_oid() : from,
0, sb.buf, &err) ||
ref_transaction_commit(transaction, &err)) {
ref_transaction_free(transaction);
@@ -567,11 +667,12 @@ void append_conflicts_hint(struct index_state *istate,
}
strbuf_addch(msgbuf, '\n');
- strbuf_commented_addf(msgbuf, "Conflicts:\n");
+ strbuf_commented_addf(msgbuf, comment_line_char, "Conflicts:\n");
for (i = 0; i < istate->cache_nr;) {
const struct cache_entry *ce = istate->cache[i++];
if (ce_stage(ce)) {
- strbuf_commented_addf(msgbuf, "\t%s\n", ce->name);
+ strbuf_commented_addf(msgbuf, comment_line_char,
+ "\t%s\n", ce->name);
while (i < istate->cache_nr &&
!strcmp(ce->name, istate->cache[i]->name))
i++;
@@ -586,9 +687,10 @@ static int do_recursive_merge(struct repository *r,
struct replay_opts *opts)
{
struct merge_options o;
+ struct merge_result result;
struct tree *next_tree, *base_tree, *head_tree;
- int clean;
- char **xopt;
+ int clean, show_output;
+ int i;
struct lock_file index_lock = LOCK_INIT;
if (repo_hold_locked_index(r, &index_lock, LOCK_REPORT_ON_ERROR) < 0)
@@ -605,18 +707,34 @@ static int do_recursive_merge(struct repository *r,
o.show_rename_progress = 1;
head_tree = parse_tree_indirect(head);
- next_tree = next ? get_commit_tree(next) : empty_tree(r);
- base_tree = base ? get_commit_tree(base) : empty_tree(r);
+ next_tree = next ? repo_get_commit_tree(r, next) : empty_tree(r);
+ base_tree = base ? repo_get_commit_tree(r, base) : empty_tree(r);
- for (xopt = opts->xopts; xopt != opts->xopts + opts->xopts_nr; xopt++)
- parse_merge_opt(&o, *xopt);
+ for (i = 0; i < opts->xopts.nr; i++)
+ parse_merge_opt(&o, opts->xopts.v[i]);
- clean = merge_trees(&o,
- head_tree,
- next_tree, base_tree);
- if (is_rebase_i(opts) && clean <= 0)
- fputs(o.obuf.buf, stdout);
- strbuf_release(&o.obuf);
+ if (!opts->strategy || !strcmp(opts->strategy, "ort")) {
+ memset(&result, 0, sizeof(result));
+ merge_incore_nonrecursive(&o, base_tree, head_tree, next_tree,
+ &result);
+ show_output = !is_rebase_i(opts) || !result.clean;
+ /*
+ * TODO: merge_switch_to_result will update index/working tree;
+ * we only really want to do that if !result.clean || this is
+ * the final patch to be picked. But determining this is the
+ * final patch would take some work, and "head_tree" would need
+ * to be replace with the tree the index matched before we
+ * started doing any picks.
+ */
+ merge_switch_to_result(&o, head_tree, &result, 1, show_output);
+ clean = result.clean;
+ } else {
+ ensure_full_index(r->index);
+ clean = merge_trees(&o, head_tree, next_tree, base_tree);
+ if (is_rebase_i(opts) && clean <= 0)
+ fputs(o.obuf.buf, stdout);
+ strbuf_release(&o.obuf);
+ }
if (clean < 0) {
rollback_lock_file(&index_lock);
return clean;
@@ -626,7 +744,7 @@ static int do_recursive_merge(struct repository *r,
COMMIT_LOCK | SKIP_IF_UNCHANGED))
/*
* TRANSLATORS: %s will be "revert", "cherry-pick" or
- * "rebase -i".
+ * "rebase".
*/
return error(_("%s: Unable to write new index file"),
_(action_name(opts)));
@@ -640,9 +758,6 @@ static int do_recursive_merge(struct repository *r,
static struct object_id *get_cache_tree_oid(struct index_state *istate)
{
- if (!istate->cache_tree)
- istate->cache_tree = cache_tree();
-
if (!cache_tree_fully_valid(istate->cache_tree))
if (cache_tree_update(istate, 0)) {
error(_("unable to update cache tree"));
@@ -666,12 +781,12 @@ static int is_index_unchanged(struct repository *r)
/*
* 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
+ * a commit object already. repo_parse_commit() will return failure
* without further complaints in such a case. Otherwise, if
- * the commit is invalid, parse_commit() will complain. So
+ * the commit is invalid, repo_parse_commit() will complain. So
* there is nothing for us to say here. Just return failure.
*/
- if (parse_commit(head_commit))
+ if (repo_parse_commit(r, head_commit))
return -1;
if (!(cache_tree_oid = get_cache_tree_oid(istate)))
@@ -821,7 +936,7 @@ int read_author_script(const char *path, char **name, char **email, char **date,
error(_("missing 'GIT_AUTHOR_EMAIL'"));
if (date_i == -2)
error(_("missing 'GIT_AUTHOR_DATE'"));
- if (date_i < 0 || email_i < 0 || date_i < 0 || err)
+ if (name_i < 0 || email_i < 0 || date_i < 0 || err)
goto finish;
*name = kv.items[name_i].util;
*email = kv.items[email_i].util;
@@ -835,10 +950,10 @@ finish:
/*
* Read a GIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL AND GIT_AUTHOR_DATE from a
- * file with shell quoting into struct argv_array. Returns -1 on
+ * file with shell quoting into struct strvec. Returns -1 on
* error, 0 otherwise.
*/
-static int read_env_script(struct argv_array *env)
+static int read_env_script(struct strvec *env)
{
char *name, *email, *date;
@@ -846,9 +961,9 @@ static int read_env_script(struct argv_array *env)
&name, &email, &date, 0))
return -1;
- argv_array_pushf(env, "GIT_AUTHOR_NAME=%s", name);
- argv_array_pushf(env, "GIT_AUTHOR_EMAIL=%s", email);
- argv_array_pushf(env, "GIT_AUTHOR_DATE=%s", date);
+ strvec_pushf(env, "GIT_AUTHOR_NAME=%s", name);
+ strvec_pushf(env, "GIT_AUTHOR_EMAIL=%s", email);
+ strvec_pushf(env, "GIT_AUTHOR_DATE=%s", date);
free(name);
free(email);
free(date);
@@ -868,6 +983,22 @@ static char *get_author(const char *message)
return NULL;
}
+static const char *author_date_from_env(const struct strvec *env)
+{
+ int i;
+ const char *date;
+
+ for (i = 0; i < env->nr; i++)
+ if (skip_prefix(env->v[i],
+ "GIT_AUTHOR_DATE=", &date))
+ return date;
+ /*
+ * If GIT_AUTHOR_DATE is missing we should have already errored out when
+ * reading the script
+ */
+ BUG("GIT_AUTHOR_DATE missing from author script");
+}
+
static const char staged_changes_advice[] =
N_("you have staged changes in your working tree\n"
"If these changes are meant to be squashed into the previous commit, run:\n"
@@ -888,6 +1019,7 @@ N_("you have staged changes in your working tree\n"
#define CLEANUP_MSG (1<<3)
#define VERIFY_MSG (1<<4)
#define CREATE_ROOT_COMMIT (1<<5)
+#define VERBATIM_MSG (1<<6)
static int run_command_silent_on_success(struct child_process *cmd)
{
@@ -918,48 +1050,67 @@ static int run_command_silent_on_success(struct child_process *cmd)
* interactive rebase: in that case, we will want to retain the
* author metadata.
*/
-static int run_git_commit(struct repository *r,
- const char *defmsg,
+static int run_git_commit(const char *defmsg,
struct replay_opts *opts,
unsigned int flags)
{
struct child_process cmd = CHILD_PROCESS_INIT;
+ if ((flags & CLEANUP_MSG) && (flags & VERBATIM_MSG))
+ BUG("CLEANUP_MSG and VERBATIM_MSG are mutually exclusive");
+
cmd.git_cmd = 1;
- if (is_rebase_i(opts) && read_env_script(&cmd.env_array)) {
+ if (is_rebase_i(opts) &&
+ ((opts->committer_date_is_author_date && !opts->ignore_date) ||
+ !(!defmsg && (flags & AMEND_MSG))) &&
+ read_env_script(&cmd.env)) {
const char *gpg_opt = gpg_sign_opt_quoted(opts);
return error(_(staged_changes_advice),
gpg_opt, gpg_opt);
}
- argv_array_push(&cmd.args, "commit");
+ strvec_pushf(&cmd.env, GIT_REFLOG_ACTION "=%s", opts->reflog_message);
+
+ if (opts->committer_date_is_author_date)
+ strvec_pushf(&cmd.env, "GIT_COMMITTER_DATE=%s",
+ opts->ignore_date ?
+ "" :
+ author_date_from_env(&cmd.env));
+ if (opts->ignore_date)
+ strvec_push(&cmd.env, "GIT_AUTHOR_DATE=");
+
+ strvec_push(&cmd.args, "commit");
if (!(flags & VERIFY_MSG))
- argv_array_push(&cmd.args, "-n");
+ strvec_push(&cmd.args, "-n");
if ((flags & AMEND_MSG))
- argv_array_push(&cmd.args, "--amend");
+ strvec_push(&cmd.args, "--amend");
if (opts->gpg_sign)
- argv_array_pushf(&cmd.args, "-S%s", opts->gpg_sign);
+ strvec_pushf(&cmd.args, "-S%s", opts->gpg_sign);
+ else
+ strvec_push(&cmd.args, "--no-gpg-sign");
if (defmsg)
- argv_array_pushl(&cmd.args, "-F", defmsg, NULL);
+ strvec_pushl(&cmd.args, "-F", defmsg, NULL);
else if (!(flags & EDIT_MSG))
- argv_array_pushl(&cmd.args, "-C", "HEAD", NULL);
+ strvec_pushl(&cmd.args, "-C", "HEAD", NULL);
if ((flags & CLEANUP_MSG))
- argv_array_push(&cmd.args, "--cleanup=strip");
+ strvec_push(&cmd.args, "--cleanup=strip");
+ if ((flags & VERBATIM_MSG))
+ strvec_push(&cmd.args, "--cleanup=verbatim");
if ((flags & EDIT_MSG))
- argv_array_push(&cmd.args, "-e");
+ strvec_push(&cmd.args, "-e");
else if (!(flags & CLEANUP_MSG) &&
!opts->signoff && !opts->record_origin &&
!opts->explicit_cleanup)
- argv_array_push(&cmd.args, "--cleanup=verbatim");
+ strvec_push(&cmd.args, "--cleanup=verbatim");
if ((flags & ALLOW_EMPTY))
- argv_array_push(&cmd.args, "--allow-empty");
+ strvec_push(&cmd.args, "--allow-empty");
if (!(flags & EDIT_MSG))
- argv_array_push(&cmd.args, "--allow-empty-message");
+ strvec_push(&cmd.args, "--allow-empty-message");
if (is_rebase_i(opts) && !(flags & EDIT_MSG))
return run_command_silent_on_success(&cmd);
@@ -1000,7 +1151,8 @@ void cleanup_message(struct strbuf *msgbuf,
cleanup_mode == COMMIT_MSG_CLEANUP_SCISSORS)
strbuf_setlen(msgbuf, wt_status_locate_end(msgbuf->buf, msgbuf->len));
if (cleanup_mode != COMMIT_MSG_CLEANUP_NONE)
- strbuf_stripspace(msgbuf, cleanup_mode == COMMIT_MSG_CLEANUP_ALL);
+ strbuf_stripspace(msgbuf,
+ cleanup_mode == COMMIT_MSG_CLEANUP_ALL ? comment_line_char : '\0');
}
/*
@@ -1031,7 +1183,8 @@ int template_untouched(const struct strbuf *sb, const char *template_file,
if (!template_file || strbuf_read_file(&tmpl, template_file, 0) <= 0)
return 0;
- strbuf_stripspace(&tmpl, cleanup_mode == COMMIT_MSG_CLEANUP_ALL);
+ strbuf_stripspace(&tmpl,
+ cleanup_mode == COMMIT_MSG_CLEANUP_ALL ? comment_line_char : '\0');
if (!skip_prefix(sb->buf, tmpl.buf, &start))
start = sb->buf;
strbuf_release(&tmpl);
@@ -1064,7 +1217,7 @@ int update_head_with_reflog(const struct commit *old_head,
transaction = ref_transaction_begin(err);
if (!transaction ||
ref_transaction_update(transaction, "HEAD", new_head,
- old_head ? &old_head->object.oid : &null_oid,
+ old_head ? &old_head->object.oid : null_oid(),
0, sb.buf, err) ||
ref_transaction_commit(transaction, err)) {
ret = -1;
@@ -1079,18 +1232,14 @@ static int run_rewrite_hook(const struct object_id *oldoid,
const struct object_id *newoid)
{
struct child_process proc = CHILD_PROCESS_INIT;
- const char *argv[3];
int code;
struct strbuf sb = STRBUF_INIT;
+ const char *hook_path = find_hook("post-rewrite");
- argv[0] = find_hook("post-rewrite");
- if (!argv[0])
+ if (!hook_path)
return 0;
- argv[1] = "amend";
- argv[2] = NULL;
-
- proc.argv = argv;
+ strvec_pushl(&proc.args, hook_path, "amend", NULL);
proc.in = -1;
proc.stdout_to_stderr = 1;
proc.trace2_hook_name = "post-rewrite";
@@ -1139,7 +1288,7 @@ static int run_prepare_commit_msg_hook(struct repository *r,
} else {
arg1 = "message";
}
- if (run_commit_hook(0, r->index_file, "prepare-commit-msg", name,
+ if (run_commit_hook(0, r->index_file, NULL, "prepare-commit-msg", name,
arg1, arg2, NULL))
ret = error(_("'prepare-commit-msg' hook failed"));
@@ -1173,7 +1322,7 @@ N_("Your name and email address were configured automatically based\n"
static const char *implicit_ident_advice(void)
{
- char *user_config = expand_user_path("~/.gitconfig", 0);
+ char *user_config = interpolate_path("~/.gitconfig", 0);
char *xdg_config = xdg_config_home("config");
int config_exists = file_exists(user_config) || file_exists(xdg_config);
@@ -1199,17 +1348,20 @@ void print_commit_summary(struct repository *r,
struct pretty_print_context pctx = {0};
struct strbuf author_ident = STRBUF_INIT;
struct strbuf committer_ident = STRBUF_INIT;
+ struct ref_store *refs;
commit = lookup_commit(r, oid);
if (!commit)
die(_("couldn't look up newly created commit"));
- if (parse_commit(commit))
+ if (repo_parse_commit(r, commit))
die(_("could not parse newly created commit"));
strbuf_addstr(&format, "format:%h] %s");
- format_commit_message(commit, "%an <%ae>", &author_ident, &pctx);
- format_commit_message(commit, "%cn <%ce>", &committer_ident, &pctx);
+ repo_format_commit_message(r, commit, "%an <%ae>", &author_ident,
+ &pctx);
+ repo_format_commit_message(r, commit, "%cn <%ce>", &committer_ident,
+ &pctx);
if (strbuf_cmp(&author_ident, &committer_ident)) {
strbuf_addstr(&format, "\n Author: ");
strbuf_addbuf_percentquote(&format, &author_ident);
@@ -1217,7 +1369,7 @@ void print_commit_summary(struct repository *r,
if (flags & SUMMARY_SHOW_AUTHOR_DATE) {
struct strbuf date = STRBUF_INIT;
- format_commit_message(commit, "%ad", &date, &pctx);
+ repo_format_commit_message(r, commit, "%ad", &date, &pctx);
strbuf_addstr(&format, "\n Date: ");
strbuf_addbuf_percentquote(&format, &date);
strbuf_release(&date);
@@ -1225,7 +1377,7 @@ void print_commit_summary(struct repository *r,
if (!committer_ident_sufficiently_given()) {
strbuf_addstr(&format, "\n Committer: ");
strbuf_addbuf_percentquote(&format, &committer_ident);
- if (advice_implicit_identity) {
+ if (advice_enabled(ADVICE_IMPLICIT_IDENTITY)) {
strbuf_addch(&format, '\n');
strbuf_addstr(&format, implicit_ident_advice());
}
@@ -1245,12 +1397,12 @@ void print_commit_summary(struct repository *r,
get_commit_format(format.buf, &rev);
rev.always_show_header = 0;
rev.diffopt.detect_rename = DIFF_DETECT_RENAME;
- rev.diffopt.break_opt = 0;
diff_setup_done(&rev.diffopt);
- head = resolve_ref_unsafe("HEAD", 0, NULL, NULL);
+ refs = get_main_ref_store(r);
+ head = refs_resolve_ref_unsafe(refs, "HEAD", 0, NULL, NULL);
if (!head)
- die_errno(_("unable to resolve HEAD after creating commit"));
+ die(_("unable to resolve HEAD after creating commit"));
if (!strcmp(head, "HEAD"))
head = _("detached HEAD");
else
@@ -1264,6 +1416,7 @@ void print_commit_summary(struct repository *r,
log_tree_commit(&rev, commit);
}
+ release_revisions(&rev);
strbuf_release(&format);
}
@@ -1272,7 +1425,7 @@ static int parse_head(struct repository *r, struct commit **head)
struct commit *current_head;
struct object_id oid;
- if (get_oid("HEAD", &oid)) {
+ if (repo_get_oid(r, "HEAD", &oid)) {
current_head = NULL;
} else {
current_head = lookup_commit_reference(r, &oid);
@@ -1282,7 +1435,7 @@ static int parse_head(struct repository *r, struct commit **head)
warning(_("HEAD %s is not a commit!"),
oid_to_hex(&oid));
}
- if (parse_commit(current_head))
+ if (repo_parse_commit(r, current_head))
return error(_("could not parse HEAD commit"));
}
*head = current_head;
@@ -1311,18 +1464,22 @@ static int try_to_commit(struct repository *r,
struct strbuf err = STRBUF_INIT;
struct strbuf commit_msg = STRBUF_INIT;
char *amend_author = NULL;
+ const char *committer = NULL;
const char *hook_commit = NULL;
enum commit_msg_cleanup_mode cleanup;
int res = 0;
+ if ((flags & CLEANUP_MSG) && (flags & VERBATIM_MSG))
+ BUG("CLEANUP_MSG and VERBATIM_MSG are mutually exclusive");
+
if (parse_head(r, &current_head))
return -1;
if (flags & AMEND_MSG) {
- const char *exclude_gpgsig[] = { "gpgsig", NULL };
+ const char *exclude_gpgsig[] = { "gpgsig", "gpgsig-sha256", NULL };
const char *out_enc = get_commit_output_encoding();
- const char *message = logmsg_reencode(current_head, NULL,
- out_enc);
+ const char *message = repo_logmsg_reencode(r, current_head,
+ NULL, out_enc);
if (!msg) {
const char *orig_message = NULL;
@@ -1333,7 +1490,8 @@ static int try_to_commit(struct repository *r,
hook_commit = "HEAD";
}
author = amend_author = get_author(message);
- unuse_commit_buffer(current_head, message);
+ repo_unuse_commit_buffer(r, current_head,
+ message);
if (!author) {
res = error(_("unable to parse commit author"));
goto out;
@@ -1373,7 +1531,7 @@ static int try_to_commit(struct repository *r,
}
}
- if (find_hook("prepare-commit-msg")) {
+ if (hook_exists("prepare-commit-msg")) {
res = run_prepare_commit_msg_hook(r, msg, hook_commit);
if (res)
goto out;
@@ -1389,6 +1547,8 @@ static int try_to_commit(struct repository *r,
if (flags & CLEANUP_MSG)
cleanup = COMMIT_MSG_CLEANUP_ALL;
+ else if (flags & VERBATIM_MSG)
+ cleanup = COMMIT_MSG_CLEANUP_NONE;
else if ((opts->signoff || opts->record_origin) &&
!opts->explicit_cleanup)
cleanup = COMMIT_MSG_CLEANUP_SPACE;
@@ -1396,27 +1556,75 @@ static int try_to_commit(struct repository *r,
cleanup = opts->default_msg_cleanup;
if (cleanup != COMMIT_MSG_CLEANUP_NONE)
- strbuf_stripspace(msg, cleanup == COMMIT_MSG_CLEANUP_ALL);
+ strbuf_stripspace(msg,
+ cleanup == COMMIT_MSG_CLEANUP_ALL ? comment_line_char : '\0');
if ((flags & EDIT_MSG) && message_is_empty(msg, cleanup)) {
res = 1; /* run 'git commit' to display error message */
goto out;
}
- reset_ident_date();
+ if (opts->committer_date_is_author_date) {
+ struct ident_split id;
+ struct strbuf date = STRBUF_INIT;
- if (commit_tree_extended(msg->buf, msg->len, &tree, parents,
- oid, author, opts->gpg_sign, extra)) {
+ if (!opts->ignore_date) {
+ if (split_ident_line(&id, author, (int)strlen(author)) < 0) {
+ res = error(_("invalid author identity '%s'"),
+ author);
+ goto out;
+ }
+ if (!id.date_begin) {
+ res = error(_(
+ "corrupt author: missing date information"));
+ goto out;
+ }
+ strbuf_addf(&date, "@%.*s %.*s",
+ (int)(id.date_end - id.date_begin),
+ id.date_begin,
+ (int)(id.tz_end - id.tz_begin),
+ id.tz_begin);
+ } else {
+ reset_ident_date();
+ }
+ committer = fmt_ident(getenv("GIT_COMMITTER_NAME"),
+ getenv("GIT_COMMITTER_EMAIL"),
+ WANT_COMMITTER_IDENT,
+ opts->ignore_date ? NULL : date.buf,
+ IDENT_STRICT);
+ strbuf_release(&date);
+ } else {
+ reset_ident_date();
+ }
+
+ if (opts->ignore_date) {
+ struct ident_split id;
+ char *name, *email;
+
+ if (split_ident_line(&id, author, strlen(author)) < 0) {
+ error(_("invalid author identity '%s'"), author);
+ goto out;
+ }
+ name = xmemdupz(id.name_begin, id.name_end - id.name_begin);
+ email = xmemdupz(id.mail_begin, id.mail_end - id.mail_begin);
+ author = fmt_ident(name, email, WANT_AUTHOR_IDENT, NULL,
+ IDENT_STRICT);
+ free(name);
+ free(email);
+ }
+
+ if (commit_tree_extended(msg->buf, msg->len, &tree, parents, oid,
+ author, committer, opts->gpg_sign, extra)) {
res = error(_("failed to write commit object"));
goto out;
}
- if (update_head_with_reflog(current_head, oid,
- getenv("GIT_REFLOG_ACTION"), msg, &err)) {
+ if (update_head_with_reflog(current_head, oid, opts->reflog_message,
+ msg, &err)) {
res = error("%s", err.buf);
goto out;
}
- run_commit_hook(0, r->index_file, "post-commit", NULL);
+ run_commit_hook(0, r->index_file, NULL, "post-commit", NULL);
if (flags & AMEND_MSG)
commit_post_rewrite(r, current_head, oid);
@@ -1429,9 +1637,19 @@ out:
return res;
}
+static int write_rebase_head(struct object_id *oid)
+{
+ if (update_ref("rebase", "REBASE_HEAD", oid,
+ NULL, REF_NO_DEREF, UPDATE_REFS_MSG_ON_ERR))
+ return error(_("could not update %s"), "REBASE_HEAD");
+
+ return 0;
+}
+
static int do_commit(struct repository *r,
const char *msg_file, const char *author,
- struct replay_opts *opts, unsigned int flags)
+ struct replay_opts *opts, unsigned int flags,
+ struct object_id *oid)
{
int res = 1;
@@ -1448,7 +1666,8 @@ static int do_commit(struct repository *r,
author, opts, flags, &oid);
strbuf_release(&sb);
if (!res) {
- unlink(git_path_cherry_pick_head(r));
+ refs_delete_ref(get_main_ref_store(r), "",
+ "CHERRY_PICK_HEAD", NULL, REF_NO_DEREF);
unlink(git_path_merge_msg(r));
if (!is_rebase_i(opts))
print_commit_summary(r, NULL, &oid,
@@ -1456,8 +1675,12 @@ static int do_commit(struct repository *r,
return res;
}
}
- if (res == 1)
- return run_git_commit(r, msg_file, opts, flags);
+ if (res == 1) {
+ if (is_rebase_i(opts) && oid)
+ if (write_rebase_head(oid))
+ return -1;
+ return run_git_commit(msg_file, opts, flags);
+ }
return res;
}
@@ -1466,12 +1689,12 @@ static int is_original_commit_empty(struct commit *commit)
{
const struct object_id *ptree_oid;
- if (parse_commit(commit))
+ if (repo_parse_commit(the_repository, commit))
return error(_("could not parse commit %s"),
oid_to_hex(&commit->object.oid));
if (commit->parents) {
struct commit *parent = commit->parents->item;
- if (parse_commit(parent))
+ if (repo_parse_commit(the_repository, parent))
return error(_("could not parse parent commit %s"),
oid_to_hex(&parent->object.oid));
ptree_oid = get_commit_tree_oid(parent);
@@ -1483,23 +1706,30 @@ static int is_original_commit_empty(struct commit *commit)
}
/*
- * Do we run "git commit" with "--allow-empty"?
+ * Should empty commits be allowed? Return status:
+ * <0: Error in is_index_unchanged(r) or is_original_commit_empty(commit)
+ * 0: Halt on empty commit
+ * 1: Allow empty commit
+ * 2: Drop empty commit
*/
static int allow_empty(struct repository *r,
struct replay_opts *opts,
struct commit *commit)
{
- int index_unchanged, empty_commit;
+ int index_unchanged, originally_empty;
/*
- * Three cases:
+ * Four 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;
+ * (2) we allow ones that were initially empty, and
+ * just drop the ones that become empty
*
- * (3) we allow both.
+ * (3) we allow ones that were initially empty, but
+ * halt for the ones that become empty;
+ *
+ * (4) we allow both.
*/
if (!opts->allow_empty)
return 0; /* let "git commit" barf as necessary */
@@ -1513,33 +1743,36 @@ static int allow_empty(struct repository *r,
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
+ originally_empty = is_original_commit_empty(commit);
+ if (originally_empty < 0)
+ return originally_empty;
+ if (originally_empty)
return 1;
+ else if (opts->drop_redundant_commits)
+ return 2;
+ else
+ return 0;
}
static struct {
char c;
const char *str;
} todo_command_info[] = {
- { 'p', "pick" },
- { 0, "revert" },
- { 'e', "edit" },
- { 'r', "reword" },
- { 'f', "fixup" },
- { 's', "squash" },
- { 'x', "exec" },
- { 'b', "break" },
- { 'l', "label" },
- { 't', "reset" },
- { 'm', "merge" },
- { 0, "noop" },
- { 'd', "drop" },
- { 0, NULL }
+ [TODO_PICK] = { 'p', "pick" },
+ [TODO_REVERT] = { 0, "revert" },
+ [TODO_EDIT] = { 'e', "edit" },
+ [TODO_REWORD] = { 'r', "reword" },
+ [TODO_FIXUP] = { 'f', "fixup" },
+ [TODO_SQUASH] = { 's', "squash" },
+ [TODO_EXEC] = { 'x', "exec" },
+ [TODO_BREAK] = { 'b', "break" },
+ [TODO_LABEL] = { 'l', "label" },
+ [TODO_RESET] = { 't', "reset" },
+ [TODO_MERGE] = { 'm', "merge" },
+ [TODO_UPDATE_REF] = { 'u', "update-ref" },
+ [TODO_NOOP] = { 0, "noop" },
+ [TODO_DROP] = { 'd', "drop" },
+ [TODO_COMMENT] = { 0, NULL },
};
static const char *command_to_string(const enum todo_command command)
@@ -1551,7 +1784,7 @@ static const char *command_to_string(const enum todo_command command)
static char command_to_char(const enum todo_command command)
{
- if (command < TODO_COMMENT && todo_command_info[command].c)
+ if (command < TODO_COMMENT)
return todo_command_info[command].c;
return comment_line_char;
}
@@ -1582,13 +1815,183 @@ static int is_pick_or_similar(enum todo_command command)
}
}
+enum todo_item_flags {
+ TODO_EDIT_MERGE_MSG = (1 << 0),
+ TODO_REPLACE_FIXUP_MSG = (1 << 1),
+ TODO_EDIT_FIXUP_MSG = (1 << 2),
+};
+
+static const char first_commit_msg_str[] = N_("This is the 1st commit message:");
+static const char nth_commit_msg_fmt[] = N_("This is the commit message #%d:");
+static const char skip_first_commit_msg_str[] = N_("The 1st commit message will be skipped:");
+static const char skip_nth_commit_msg_fmt[] = N_("The commit message #%d will be skipped:");
+static const char combined_commit_msg_fmt[] = N_("This is a combination of %d commits.");
+
+static int is_fixup_flag(enum todo_command command, unsigned flag)
+{
+ return command == TODO_FIXUP && ((flag & TODO_REPLACE_FIXUP_MSG) ||
+ (flag & TODO_EDIT_FIXUP_MSG));
+}
+
+/*
+ * Wrapper around strbuf_add_commented_lines() which avoids double
+ * commenting commit subjects.
+ */
+static void add_commented_lines(struct strbuf *buf, const void *str, size_t len)
+{
+ const char *s = str;
+ while (len > 0 && s[0] == comment_line_char) {
+ size_t count;
+ const char *n = memchr(s, '\n', len);
+ if (!n)
+ count = len;
+ else
+ count = n - s + 1;
+ strbuf_add(buf, s, count);
+ s += count;
+ len -= count;
+ }
+ strbuf_add_commented_lines(buf, s, len, comment_line_char);
+}
+
+/* Does the current fixup chain contain a squash command? */
+static int seen_squash(struct replay_opts *opts)
+{
+ return starts_with(opts->current_fixups.buf, "squash") ||
+ strstr(opts->current_fixups.buf, "\nsquash");
+}
+
+static void update_comment_bufs(struct strbuf *buf1, struct strbuf *buf2, int n)
+{
+ strbuf_setlen(buf1, 2);
+ strbuf_addf(buf1, _(nth_commit_msg_fmt), n);
+ strbuf_addch(buf1, '\n');
+ strbuf_setlen(buf2, 2);
+ strbuf_addf(buf2, _(skip_nth_commit_msg_fmt), n);
+ strbuf_addch(buf2, '\n');
+}
+
+/*
+ * Comment out any un-commented commit messages, updating the message comments
+ * to say they will be skipped but do not comment out the empty lines that
+ * surround commit messages and their comments.
+ */
+static void update_squash_message_for_fixup(struct strbuf *msg)
+{
+ void (*copy_lines)(struct strbuf *, const void *, size_t) = strbuf_add;
+ struct strbuf buf1 = STRBUF_INIT, buf2 = STRBUF_INIT;
+ const char *s, *start;
+ char *orig_msg;
+ size_t orig_msg_len;
+ int i = 1;
+
+ strbuf_addf(&buf1, "# %s\n", _(first_commit_msg_str));
+ strbuf_addf(&buf2, "# %s\n", _(skip_first_commit_msg_str));
+ s = start = orig_msg = strbuf_detach(msg, &orig_msg_len);
+ while (s) {
+ const char *next;
+ size_t off;
+ if (skip_prefix(s, buf1.buf, &next)) {
+ /*
+ * Copy the last message, preserving the blank line
+ * preceding the current line
+ */
+ off = (s > start + 1 && s[-2] == '\n') ? 1 : 0;
+ copy_lines(msg, start, s - start - off);
+ if (off)
+ strbuf_addch(msg, '\n');
+ /*
+ * The next message needs to be commented out but the
+ * message header is already commented out so just copy
+ * it and the blank line that follows it.
+ */
+ strbuf_addbuf(msg, &buf2);
+ if (*next == '\n')
+ strbuf_addch(msg, *next++);
+ start = s = next;
+ copy_lines = add_commented_lines;
+ update_comment_bufs(&buf1, &buf2, ++i);
+ } else if (skip_prefix(s, buf2.buf, &next)) {
+ off = (s > start + 1 && s[-2] == '\n') ? 1 : 0;
+ copy_lines(msg, start, s - start - off);
+ start = s - off;
+ s = next;
+ copy_lines = strbuf_add;
+ update_comment_bufs(&buf1, &buf2, ++i);
+ } else {
+ s = strchr(s, '\n');
+ if (s)
+ s++;
+ }
+ }
+ copy_lines(msg, start, orig_msg_len - (start - orig_msg));
+ free(orig_msg);
+ strbuf_release(&buf1);
+ strbuf_release(&buf2);
+}
+
+static int append_squash_message(struct strbuf *buf, const char *body,
+ enum todo_command command, struct replay_opts *opts,
+ unsigned flag)
+{
+ const char *fixup_msg;
+ size_t commented_len = 0, fixup_off;
+ /*
+ * amend is non-interactive and not normally used with fixup!
+ * or squash! commits, so only comment out those subjects when
+ * squashing commit messages.
+ */
+ if (starts_with(body, "amend!") ||
+ ((command == TODO_SQUASH || seen_squash(opts)) &&
+ (starts_with(body, "squash!") || starts_with(body, "fixup!"))))
+ commented_len = commit_subject_length(body);
+
+ strbuf_addf(buf, "\n%c ", comment_line_char);
+ strbuf_addf(buf, _(nth_commit_msg_fmt),
+ ++opts->current_fixup_count + 1);
+ strbuf_addstr(buf, "\n\n");
+ strbuf_add_commented_lines(buf, body, commented_len, comment_line_char);
+ /* buf->buf may be reallocated so store an offset into the buffer */
+ fixup_off = buf->len;
+ strbuf_addstr(buf, body + commented_len);
+
+ /* fixup -C after squash behaves like squash */
+ if (is_fixup_flag(command, flag) && !seen_squash(opts)) {
+ /*
+ * We're replacing the commit message so we need to
+ * append the Signed-off-by: trailer if the user
+ * requested '--signoff'.
+ */
+ if (opts->signoff)
+ append_signoff(buf, 0, 0);
+
+ if ((command == TODO_FIXUP) &&
+ (flag & TODO_REPLACE_FIXUP_MSG) &&
+ (file_exists(rebase_path_fixup_msg()) ||
+ !file_exists(rebase_path_squash_msg()))) {
+ fixup_msg = skip_blank_lines(buf->buf + fixup_off);
+ if (write_message(fixup_msg, strlen(fixup_msg),
+ rebase_path_fixup_msg(), 0) < 0)
+ return error(_("cannot write '%s'"),
+ rebase_path_fixup_msg());
+ } else {
+ unlink(rebase_path_fixup_msg());
+ }
+ } else {
+ unlink(rebase_path_fixup_msg());
+ }
+
+ return 0;
+}
+
static int update_squash_messages(struct repository *r,
enum todo_command command,
struct commit *commit,
- struct replay_opts *opts)
+ struct replay_opts *opts,
+ unsigned flag)
{
struct strbuf buf = STRBUF_INIT;
- int res;
+ int res = 0;
const char *message, *body;
const char *encoding = get_commit_output_encoding();
@@ -1604,63 +2007,68 @@ static int update_squash_messages(struct repository *r,
buf.buf : strchrnul(buf.buf, '\n');
strbuf_addf(&header, "%c ", comment_line_char);
- strbuf_addf(&header, _("This is a combination of %d commits."),
+ strbuf_addf(&header, _(combined_commit_msg_fmt),
opts->current_fixup_count + 2);
strbuf_splice(&buf, 0, eol - buf.buf, header.buf, header.len);
strbuf_release(&header);
+ if (is_fixup_flag(command, flag) && !seen_squash(opts))
+ update_squash_message_for_fixup(&buf);
} else {
struct object_id head;
struct commit *head_commit;
const char *head_message, *body;
- if (get_oid("HEAD", &head))
+ if (repo_get_oid(r, "HEAD", &head))
return error(_("need a HEAD to fixup"));
if (!(head_commit = lookup_commit_reference(r, &head)))
return error(_("could not read HEAD"));
- if (!(head_message = logmsg_reencode(head_commit, NULL, encoding)))
+ if (!(head_message = repo_logmsg_reencode(r, head_commit, NULL,
+ encoding)))
return error(_("could not read HEAD's commit message"));
find_commit_subject(head_message, &body);
- if (write_message(body, strlen(body),
- rebase_path_fixup_msg(), 0)) {
- unuse_commit_buffer(head_commit, head_message);
- return error(_("cannot write '%s'"),
- rebase_path_fixup_msg());
+ if (command == TODO_FIXUP && !flag && write_message(body, strlen(body),
+ rebase_path_fixup_msg(), 0) < 0) {
+ repo_unuse_commit_buffer(r, head_commit, head_message);
+ return error(_("cannot write '%s'"), rebase_path_fixup_msg());
}
-
strbuf_addf(&buf, "%c ", comment_line_char);
- strbuf_addf(&buf, _("This is a combination of %d commits."), 2);
+ strbuf_addf(&buf, _(combined_commit_msg_fmt), 2);
strbuf_addf(&buf, "\n%c ", comment_line_char);
- strbuf_addstr(&buf, _("This is the 1st commit message:"));
+ strbuf_addstr(&buf, is_fixup_flag(command, flag) ?
+ _(skip_first_commit_msg_str) :
+ _(first_commit_msg_str));
strbuf_addstr(&buf, "\n\n");
- strbuf_addstr(&buf, body);
+ if (is_fixup_flag(command, flag))
+ strbuf_add_commented_lines(&buf, body, strlen(body),
+ comment_line_char);
+ else
+ strbuf_addstr(&buf, body);
- unuse_commit_buffer(head_commit, head_message);
+ repo_unuse_commit_buffer(r, head_commit, head_message);
}
- if (!(message = logmsg_reencode(commit, NULL, encoding)))
+ if (!(message = repo_logmsg_reencode(r, commit, NULL, encoding)))
return error(_("could not read commit message of %s"),
oid_to_hex(&commit->object.oid));
find_commit_subject(message, &body);
- if (command == TODO_SQUASH) {
- unlink(rebase_path_fixup_msg());
- strbuf_addf(&buf, "\n%c ", comment_line_char);
- strbuf_addf(&buf, _("This is the commit message #%d:"),
- ++opts->current_fixup_count + 1);
- strbuf_addstr(&buf, "\n\n");
- strbuf_addstr(&buf, body);
+ if (command == TODO_SQUASH || is_fixup_flag(command, flag)) {
+ res = append_squash_message(&buf, body, command, opts, flag);
} else if (command == TODO_FIXUP) {
strbuf_addf(&buf, "\n%c ", comment_line_char);
- strbuf_addf(&buf, _("The commit message #%d will be skipped:"),
+ strbuf_addf(&buf, _(skip_nth_commit_msg_fmt),
++opts->current_fixup_count + 1);
strbuf_addstr(&buf, "\n\n");
- strbuf_add_commented_lines(&buf, body, strlen(body));
+ strbuf_add_commented_lines(&buf, body, strlen(body),
+ comment_line_char);
} else
return error(_("unknown command: %d"), command);
- unuse_commit_buffer(commit, message);
+ repo_unuse_commit_buffer(r, commit, message);
- res = write_message(buf.buf, buf.len, rebase_path_squash_msg(), 0);
+ if (!res)
+ res = write_message(buf.buf, buf.len, rebase_path_squash_msg(),
+ 0);
strbuf_release(&buf);
if (!res) {
@@ -1683,7 +2091,7 @@ static void flush_rewritten_pending(void)
FILE *out;
if (strbuf_read_file(&buf, rebase_path_rewritten_pending(), (GIT_MAX_HEXSZ + 1) * 2) > 0 &&
- !get_oid("HEAD", &newoid) &&
+ !repo_get_oid(the_repository, "HEAD", &newoid) &&
(out = fopen_or_warn(rebase_path_rewritten_list(), "a"))) {
char *bol = buf.buf, *eol;
@@ -1716,33 +2124,60 @@ static void record_in_rewritten(struct object_id *oid,
flush_rewritten_pending();
}
+static int should_edit(struct replay_opts *opts) {
+ if (opts->edit < 0)
+ /*
+ * Note that we only handle the case of non-conflicted
+ * commits; continue_single_pick() handles the conflicted
+ * commits itself instead of calling this function.
+ */
+ return (opts->action == REPLAY_REVERT && isatty(0)) ? 1 : 0;
+ return opts->edit;
+}
+
+static void refer_to_commit(struct replay_opts *opts,
+ struct strbuf *msgbuf, struct commit *commit)
+{
+ if (opts->commit_use_reference) {
+ struct pretty_print_context ctx = {
+ .abbrev = DEFAULT_ABBREV,
+ .date_mode.type = DATE_SHORT,
+ };
+ repo_format_commit_message(the_repository, commit,
+ "%h (%s, %ad)", msgbuf, &ctx);
+ } else {
+ strbuf_addstr(msgbuf, oid_to_hex(&commit->object.oid));
+ }
+}
+
static int do_pick_commit(struct repository *r,
- enum todo_command command,
- struct commit *commit,
+ struct todo_item *item,
struct replay_opts *opts,
int final_fixup, int *check_todo)
{
- unsigned int flags = opts->edit ? EDIT_MSG : 0;
- const char *msg_file = opts->edit ? NULL : git_path_merge_msg(r);
+ unsigned int flags = should_edit(opts) ? EDIT_MSG : 0;
+ const char *msg_file = should_edit(opts) ? NULL : git_path_merge_msg(r);
struct object_id head;
struct commit *base, *next, *parent;
const char *base_label, *next_label;
char *author = NULL;
struct commit_message msg = { NULL, NULL, NULL, NULL };
struct strbuf msgbuf = STRBUF_INIT;
- int res, unborn = 0, reword = 0, allow;
+ int res, unborn = 0, reword = 0, allow, drop_commit;
+ enum todo_command command = item->command;
+ struct commit *commit = item->commit;
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
+ * that represents the "current" state for the merge machinery
* to work on.
*/
if (write_index_as_tree(&head, r->index, r->index_file, 0, NULL))
return error(_("your index file is unmerged."));
} else {
- unborn = get_oid("HEAD", &head);
+ unborn = repo_get_oid(r, "HEAD", &head);
/* Do we want to generate a root commit? */
if (is_pick_or_similar(command) && opts->have_squash_onto &&
oideq(&head, &opts->squash_onto)) {
@@ -1804,7 +2239,7 @@ static int do_pick_commit(struct repository *r,
msg_file = NULL;
goto fast_forward_edit;
}
- if (parent && parse_commit(parent) < 0)
+ if (parent && repo_parse_commit(r, parent) < 0)
/* TRANSLATORS: The first %s will be a "todo" command like
"revert" or "pick", the second %s a SHA1. */
return error(_("%s: cannot parse parent commit %s"),
@@ -1819,18 +2254,35 @@ static int do_pick_commit(struct repository *r,
*/
if (command == TODO_REVERT) {
+ const char *orig_subject;
+
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, oid_to_hex(&commit->object.oid));
+ if (opts->commit_use_reference) {
+ strbuf_addstr(&msgbuf,
+ "# *** SAY WHY WE ARE REVERTING ON THE TITLE LINE ***");
+ } else if (skip_prefix(msg.subject, "Revert \"", &orig_subject) &&
+ /*
+ * We don't touch pre-existing repeated reverts, because
+ * theoretically these can be nested arbitrarily deeply,
+ * thus requiring excessive complexity to deal with.
+ */
+ !starts_with(orig_subject, "Revert \"")) {
+ strbuf_addstr(&msgbuf, "Reapply \"");
+ strbuf_addstr(&msgbuf, orig_subject);
+ } else {
+ strbuf_addstr(&msgbuf, "Revert \"");
+ strbuf_addstr(&msgbuf, msg.subject);
+ strbuf_addstr(&msgbuf, "\"");
+ }
+ strbuf_addstr(&msgbuf, "\n\nThis reverts commit ");
+ refer_to_commit(opts, &msgbuf, commit);
if (commit->parents && commit->parents->next) {
strbuf_addstr(&msgbuf, ", reversing\nchanges made to ");
- strbuf_addstr(&msgbuf, oid_to_hex(&parent->object.oid));
+ refer_to_commit(opts, &msgbuf, parent);
}
strbuf_addstr(&msgbuf, ".\n");
} else {
@@ -1860,20 +2312,25 @@ static int do_pick_commit(struct repository *r,
if (command == TODO_REWORD)
reword = 1;
else if (is_fixup(command)) {
- if (update_squash_messages(r, command, commit, opts))
- return -1;
+ if (update_squash_messages(r, command, commit,
+ opts, item->flags)) {
+ res = -1;
+ goto leave;
+ }
flags |= AMEND_MSG;
if (!final_fixup)
msg_file = rebase_path_squash_msg();
else if (file_exists(rebase_path_fixup_msg())) {
- flags |= CLEANUP_MSG;
+ flags |= VERBATIM_MSG;
msg_file = rebase_path_fixup_msg();
} else {
const char *dest = git_path_squash_msg(r);
unlink(dest);
- if (copy_file(dest, rebase_path_squash_msg(), 0666))
- return error(_("could not rename '%s' to '%s'"),
- rebase_path_squash_msg(), dest);
+ if (copy_file(dest, rebase_path_squash_msg(), 0666)) {
+ res = error(_("could not copy '%s' to '%s'"),
+ rebase_path_squash_msg(), dest);
+ goto leave;
+ }
unlink(git_path_merge_msg(r));
msg_file = dest;
flags |= EDIT_MSG;
@@ -1885,7 +2342,10 @@ static int do_pick_commit(struct repository *r,
if (is_rebase_i(opts) && write_author_script(msg.message) < 0)
res = -1;
- else if (!opts->strategy || !strcmp(opts->strategy, "recursive") || command == TODO_REVERT) {
+ else if (!opts->strategy ||
+ !strcmp(opts->strategy, "recursive") ||
+ !strcmp(opts->strategy, "ort") ||
+ command == TODO_REVERT) {
res = do_recursive_merge(r, base, next, base_label, next_label,
&head, &msgbuf, opts);
if (res < 0)
@@ -1903,12 +2363,11 @@ static int do_pick_commit(struct repository *r,
commit_list_insert(base, &common);
commit_list_insert(next, &remotes);
res |= try_merge_command(r, opts->strategy,
- opts->xopts_nr, (const char **)opts->xopts,
+ opts->xopts.nr, opts->xopts.v,
common, oid_to_hex(&head), remotes);
free_commit_list(common);
free_commit_list(remotes);
}
- strbuf_release(&msgbuf);
/*
* If the merge was clean or if it failed due to conflict, we write
@@ -1916,7 +2375,9 @@ static int do_pick_commit(struct repository *r,
* However, if the merge did not even start, then we don't want to
* write it at all.
*/
- if (command == TODO_PICK && !opts->no_commit && (res == 0 || res == 1) &&
+ if ((command == TODO_PICK || command == TODO_REWORD ||
+ command == TODO_EDIT) && !opts->no_commit &&
+ (res == 0 || res == 1) &&
update_ref(NULL, "CHERRY_PICK_HEAD", &commit->object.oid, NULL,
REF_NO_DEREF, UPDATE_REFS_MSG_ON_ERR))
res = -1;
@@ -1929,27 +2390,40 @@ static int do_pick_commit(struct repository *r,
error(command == TODO_REVERT
? _("could not revert %s... %s")
: _("could not apply %s... %s"),
- short_commit_name(commit), msg.subject);
+ short_commit_name(r, commit), msg.subject);
print_advice(r, res == 1, opts);
repo_rerere(r, opts->allow_rerere_auto);
goto leave;
}
+ drop_commit = 0;
allow = allow_empty(r, opts, commit);
if (allow < 0) {
res = allow;
goto leave;
- } else if (allow)
+ } else if (allow == 1) {
flags |= ALLOW_EMPTY;
- if (!opts->no_commit) {
+ } else if (allow == 2) {
+ drop_commit = 1;
+ refs_delete_ref(get_main_ref_store(r), "", "CHERRY_PICK_HEAD",
+ NULL, REF_NO_DEREF);
+ unlink(git_path_merge_msg(r));
+ refs_delete_ref(get_main_ref_store(r), "", "AUTO_MERGE",
+ NULL, REF_NO_DEREF);
+ fprintf(stderr,
+ _("dropping %s %s -- patch contents already upstream\n"),
+ oid_to_hex(&commit->object.oid), msg.subject);
+ } /* else allow == 0 and there's nothing special to do */
+ if (!opts->no_commit && !drop_commit) {
if (author || command == TODO_REVERT || (flags & AMEND_MSG))
- res = do_commit(r, msg_file, author, opts, flags);
+ res = do_commit(r, msg_file, author, opts, flags,
+ commit? &commit->object.oid : NULL);
else
res = error(_("unable to parse commit author"));
*check_todo = !!(flags & EDIT_MSG);
if (!res && reword) {
fast_forward_edit:
- res = run_git_commit(r, NULL, opts, EDIT_MSG |
+ res = run_git_commit(NULL, opts, EDIT_MSG |
VERIFY_MSG | AMEND_MSG |
(flags & ALLOW_EMPTY));
*check_todo = 1;
@@ -1968,6 +2442,7 @@ fast_forward_edit:
leave:
free_message(commit, &msg);
free(author);
+ strbuf_release(&msgbuf);
update_abort_safety_file();
return res;
@@ -1996,23 +2471,27 @@ static int read_and_refresh_cache(struct repository *r,
if (repo_read_index(r) < 0) {
rollback_lock_file(&index_lock);
return error(_("git %s: failed to read the index"),
- _(action_name(opts)));
+ action_name(opts));
}
refresh_index(r->index, REFRESH_QUIET|REFRESH_UNMERGED, NULL, NULL, NULL);
+
if (index_fd >= 0) {
if (write_locked_index(r->index, &index_lock,
COMMIT_LOCK | SKIP_IF_UNCHANGED)) {
return error(_("git %s: failed to refresh the index"),
- _(action_name(opts)));
+ action_name(opts));
}
}
+
+ /*
+ * If we are resolving merges in any way other than "ort", then
+ * expand the sparse index.
+ */
+ if (opts->strategy && strcmp(opts->strategy, "ort"))
+ ensure_full_index(r->index);
return 0;
}
-enum todo_item_flags {
- TODO_EDIT_MERGE_MSG = 1
-};
-
void todo_list_release(struct todo_list *todo_list)
{
strbuf_release(&todo_list->buf);
@@ -2023,7 +2502,6 @@ void todo_list_release(struct todo_list *todo_list)
static struct todo_item *append_new_todo(struct todo_list *todo_list)
{
ALLOC_GROW(todo_list->items, todo_list->nr + 1, todo_list->alloc);
- todo_list->total_nr++;
return todo_list->items + todo_list->nr++;
}
@@ -2037,12 +2515,39 @@ static int is_command(enum todo_command command, const char **bol)
{
const char *str = todo_command_info[command].str;
const char nick = todo_command_info[command].c;
- const char *p = *bol + 1;
+ const char *p = *bol;
- return skip_prefix(*bol, str, bol) ||
- ((nick && **bol == nick) &&
- (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r' || !*p) &&
- (*bol = p));
+ return (skip_prefix(p, str, &p) || (nick && *p++ == nick)) &&
+ (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r' || !*p) &&
+ (*bol = p);
+}
+
+static int check_label_or_ref_arg(enum todo_command command, const char *arg)
+{
+ switch (command) {
+ case TODO_LABEL:
+ /*
+ * '#' is not a valid label as the merge command uses it to
+ * separate merge parents from the commit subject.
+ */
+ if (!strcmp(arg, "#") ||
+ check_refname_format(arg, REFNAME_ALLOW_ONELEVEL))
+ return error(_("'%s' is not a valid label"), arg);
+ break;
+
+ case TODO_UPDATE_REF:
+ if (check_refname_format(arg, REFNAME_ALLOW_ONELEVEL))
+ return error(_("'%s' is not a valid refname"), arg);
+ if (check_refname_format(arg, 0))
+ return error(_("update-ref requires a fully qualified "
+ "refname e.g. refs/heads/%s"), arg);
+ break;
+
+ default:
+ BUG("unexpected todo_command");
+ }
+
+ return 0;
}
static int parse_insn_line(struct repository *r, struct todo_item *item,
@@ -2071,7 +2576,8 @@ static int parse_insn_line(struct repository *r, struct todo_item *item,
break;
}
if (i >= TODO_COMMENT)
- return -1;
+ return error(_("invalid command '%.*s'"),
+ (int)strcspn(bol, " \t\r\n"), bol);
/* Eat up extra spaces/ tabs before object name */
padding = strspn(bol, " \t");
@@ -2092,11 +2598,30 @@ static int parse_insn_line(struct repository *r, struct todo_item *item,
command_to_string(item->command));
if (item->command == TODO_EXEC || item->command == TODO_LABEL ||
- item->command == TODO_RESET) {
+ item->command == TODO_RESET || item->command == TODO_UPDATE_REF) {
+ int ret = 0;
+
item->commit = NULL;
item->arg_offset = bol - buf;
item->arg_len = (int)(eol - bol);
- return 0;
+ if (item->command == TODO_LABEL ||
+ item->command == TODO_UPDATE_REF) {
+ saved = *eol;
+ *eol = '\0';
+ ret = check_label_or_ref_arg(item->command, bol);
+ *eol = saved;
+ }
+ return ret;
+ }
+
+ if (item->command == TODO_FIXUP) {
+ if (skip_prefix(bol, "-C", &bol)) {
+ bol += strspn(bol, " \t");
+ item->flags |= TODO_REPLACE_FIXUP_MSG;
+ } else if (skip_prefix(bol, "-c", &bol)) {
+ bol += strspn(bol, " \t");
+ item->flags |= TODO_EDIT_FIXUP_MSG;
+ }
}
if (item->command == TODO_MERGE) {
@@ -2117,7 +2642,9 @@ static int parse_insn_line(struct repository *r, struct todo_item *item,
end_of_object_name = (char *) bol + strcspn(bol, " \t\n");
saved = *end_of_object_name;
*end_of_object_name = '\0';
- status = get_oid(bol, &commit_oid);
+ status = repo_get_oid(r, bol, &commit_oid);
+ if (status < 0)
+ error(_("could not parse '%s'"), bol); /* return later */
*end_of_object_name = saved;
bol = end_of_object_name + strspn(end_of_object_name, " \t");
@@ -2125,14 +2652,13 @@ static int parse_insn_line(struct repository *r, struct todo_item *item,
item->arg_len = (int)(eol - bol);
if (status < 0)
- return error(_("could not parse '%.*s'"),
- (int)(end_of_object_name - bol), bol);
+ return status;
item->commit = lookup_commit_reference(r, &commit_oid);
- return !item->commit;
+ return item->commit ? 0 : -1;
}
-int sequencer_get_last_command(struct repository *r, enum replay_action *action)
+int sequencer_get_last_command(struct repository *r UNUSED, enum replay_action *action)
{
const char *todo_file, *bol;
struct strbuf buf = STRBUF_INIT;
@@ -2166,7 +2692,7 @@ int todo_list_parse_insn_buffer(struct repository *r, char *buf,
char *p = buf, *next_p;
int i, res = 0, fixup_okay = file_exists(rebase_path_done());
- todo_list->current = todo_list->nr = 0;
+ todo_list->current = todo_list->nr = todo_list->total_nr = 0;
for (i = 1; *p; i++, p = next_p) {
char *eol = strchrnul(p, '\n');
@@ -2187,10 +2713,13 @@ int todo_list_parse_insn_buffer(struct repository *r, char *buf,
item->commit = NULL;
}
+ if (item->command != TODO_COMMENT)
+ todo_list->total_nr++;
+
if (fixup_okay)
; /* do nothing */
else if (is_fixup(item->command))
- return error(_("cannot '%s' without a previous commit"),
+ res = error(_("cannot '%s' without a previous commit"),
command_to_string(item->command));
else if (!is_noop(item->command))
fixup_okay = 1;
@@ -2272,20 +2801,27 @@ void sequencer_post_commit_cleanup(struct repository *r, int verbose)
struct replay_opts opts = REPLAY_OPTS_INIT;
int need_cleanup = 0;
- if (file_exists(git_path_cherry_pick_head(r))) {
- if (!unlink(git_path_cherry_pick_head(r)) && verbose)
+ if (refs_ref_exists(get_main_ref_store(r), "CHERRY_PICK_HEAD")) {
+ if (!refs_delete_ref(get_main_ref_store(r), "",
+ "CHERRY_PICK_HEAD", NULL, REF_NO_DEREF) &&
+ verbose)
warning(_("cancelling a cherry picking in progress"));
opts.action = REPLAY_PICK;
need_cleanup = 1;
}
- if (file_exists(git_path_revert_head(r))) {
- if (!unlink(git_path_revert_head(r)) && verbose)
+ if (refs_ref_exists(get_main_ref_store(r), "REVERT_HEAD")) {
+ if (!refs_delete_ref(get_main_ref_store(r), "", "REVERT_HEAD",
+ NULL, REF_NO_DEREF) &&
+ verbose)
warning(_("cancelling a revert in progress"));
opts.action = REPLAY_REVERT;
need_cleanup = 1;
}
+ refs_delete_ref(get_main_ref_store(r), "", "AUTO_MERGE",
+ NULL, REF_NO_DEREF);
+
if (!need_cleanup)
return;
@@ -2309,7 +2845,6 @@ static int read_populate_todo(struct repository *r,
struct todo_list *todo_list,
struct replay_opts *opts)
{
- struct stat st;
const char *todo_file = get_todo_path(opts);
int res;
@@ -2317,11 +2852,6 @@ static int read_populate_todo(struct repository *r,
if (strbuf_read_file_or_whine(&todo_list->buf, todo_file) < 0)
return -1;
- res = stat(todo_file, &st);
- if (res)
- return error(_("could not stat '%s'"), todo_file);
- fill_stat_data(&todo_list->stat, &st);
-
res = todo_list_parse_insn_buffer(r, todo_list->buf.buf, todo_list);
if (res) {
if (is_rebase_i(opts))
@@ -2377,7 +2907,9 @@ static int git_config_string_dup(char **dest,
return 0;
}
-static int populate_opts_cb(const char *key, const char *value, void *data)
+static int populate_opts_cb(const char *key, const char *value,
+ const struct config_context *ctx,
+ void *data)
{
struct replay_opts *opts = data;
int error_flag = 1;
@@ -2385,36 +2917,35 @@ static int populate_opts_cb(const char *key, const char *value, void *data)
if (!value)
error_flag = 0;
else if (!strcmp(key, "options.no-commit"))
- opts->no_commit = git_config_bool_or_int(key, value, &error_flag);
+ opts->no_commit = git_config_bool_or_int(key, value, ctx->kvi, &error_flag);
else if (!strcmp(key, "options.edit"))
- opts->edit = git_config_bool_or_int(key, value, &error_flag);
+ opts->edit = git_config_bool_or_int(key, value, ctx->kvi, &error_flag);
else if (!strcmp(key, "options.allow-empty"))
opts->allow_empty =
- git_config_bool_or_int(key, value, &error_flag);
+ git_config_bool_or_int(key, value, ctx->kvi, &error_flag);
else if (!strcmp(key, "options.allow-empty-message"))
opts->allow_empty_message =
- git_config_bool_or_int(key, value, &error_flag);
+ git_config_bool_or_int(key, value, ctx->kvi, &error_flag);
else if (!strcmp(key, "options.keep-redundant-commits"))
opts->keep_redundant_commits =
- git_config_bool_or_int(key, value, &error_flag);
+ git_config_bool_or_int(key, value, ctx->kvi, &error_flag);
else if (!strcmp(key, "options.signoff"))
- opts->signoff = git_config_bool_or_int(key, value, &error_flag);
+ opts->signoff = git_config_bool_or_int(key, value, ctx->kvi, &error_flag);
else if (!strcmp(key, "options.record-origin"))
- opts->record_origin = git_config_bool_or_int(key, value, &error_flag);
+ opts->record_origin = git_config_bool_or_int(key, value, ctx->kvi, &error_flag);
else if (!strcmp(key, "options.allow-ff"))
- opts->allow_ff = git_config_bool_or_int(key, value, &error_flag);
+ opts->allow_ff = git_config_bool_or_int(key, value, ctx->kvi, &error_flag);
else if (!strcmp(key, "options.mainline"))
- opts->mainline = git_config_int(key, value);
+ opts->mainline = git_config_int(key, value, ctx->kvi);
else if (!strcmp(key, "options.strategy"))
git_config_string_dup(&opts->strategy, key, value);
else if (!strcmp(key, "options.gpg-sign"))
git_config_string_dup(&opts->gpg_sign, 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);
+ strvec_push(&opts->xopts, value);
} else if (!strcmp(key, "options.allow-rerere-auto"))
opts->allow_rerere_auto =
- git_config_bool_or_int(key, value, &error_flag) ?
+ git_config_bool_or_int(key, value, ctx->kvi, &error_flag) ?
RERERE_AUTOUPDATE : RERERE_NOAUTOUPDATE;
else if (!strcmp(key, "options.default-msg-cleanup")) {
opts->explicit_cleanup = 1;
@@ -2423,27 +2954,32 @@ static int populate_opts_cb(const char *key, const char *value, void *data)
return error(_("invalid key: %s"), key);
if (!error_flag)
- return error(_("invalid value for %s: %s"), key, value);
+ return error(_("invalid value for '%s': '%s'"), key, value);
return 0;
}
-void parse_strategy_opts(struct replay_opts *opts, char *raw_opts)
+static void parse_strategy_opts(struct replay_opts *opts, char *raw_opts)
{
int i;
+ int count;
+ const char **argv;
char *strategy_opts_string = raw_opts;
if (*strategy_opts_string == ' ')
strategy_opts_string++;
- opts->xopts_nr = split_cmdline(strategy_opts_string,
- (const char ***)&opts->xopts);
- for (i = 0; i < opts->xopts_nr; i++) {
- const char *arg = opts->xopts[i];
+ count = split_cmdline(strategy_opts_string, &argv);
+ if (count < 0)
+ BUG("could not split '%s': %s", strategy_opts_string,
+ split_cmdline_strerror(count));
+ for (i = 0; i < count; i++) {
+ const char *arg = argv[i];
skip_prefix(arg, "--", &arg);
- opts->xopts[i] = xstrdup(arg);
+ strvec_push(&opts->xopts, arg);
}
+ free(argv);
}
static void read_strategy_opts(struct replay_opts *opts, struct strbuf *buf)
@@ -2462,8 +2998,10 @@ static int read_populate_opts(struct replay_opts *opts)
{
if (is_rebase_i(opts)) {
struct strbuf buf = STRBUF_INIT;
+ int ret = 0;
- if (read_oneliner(&buf, rebase_path_gpg_sign_opt(), 1)) {
+ if (read_oneliner(&buf, rebase_path_gpg_sign_opt(),
+ READ_ONELINER_SKIP_IF_EMPTY)) {
if (!starts_with(buf.buf, "-S"))
strbuf_reset(&buf);
else {
@@ -2473,7 +3011,8 @@ static int read_populate_opts(struct replay_opts *opts)
strbuf_reset(&buf);
}
- if (read_oneliner(&buf, rebase_path_allow_rerere_autoupdate(), 1)) {
+ if (read_oneliner(&buf, rebase_path_allow_rerere_autoupdate(),
+ READ_ONELINER_SKIP_IF_EMPTY)) {
if (!strcmp(buf.buf, "--rerere-autoupdate"))
opts->allow_rerere_auto = RERERE_AUTOUPDATE;
else if (!strcmp(buf.buf, "--no-rerere-autoupdate"))
@@ -2492,14 +3031,33 @@ static int read_populate_opts(struct replay_opts *opts)
opts->signoff = 1;
}
+ if (file_exists(rebase_path_cdate_is_adate())) {
+ opts->allow_ff = 0;
+ opts->committer_date_is_author_date = 1;
+ }
+
+ if (file_exists(rebase_path_ignore_date())) {
+ opts->allow_ff = 0;
+ opts->ignore_date = 1;
+ }
+
if (file_exists(rebase_path_reschedule_failed_exec()))
opts->reschedule_failed_exec = 1;
+ else if (file_exists(rebase_path_no_reschedule_failed_exec()))
+ opts->reschedule_failed_exec = 0;
+
+ if (file_exists(rebase_path_drop_redundant_commits()))
+ opts->drop_redundant_commits = 1;
+
+ if (file_exists(rebase_path_keep_redundant_commits()))
+ opts->keep_redundant_commits = 1;
read_strategy_opts(opts, &buf);
- strbuf_release(&buf);
+ strbuf_reset(&buf);
if (read_oneliner(&opts->current_fixups,
- rebase_path_current_fixups(), 1)) {
+ rebase_path_current_fixups(),
+ READ_ONELINER_SKIP_IF_EMPTY)) {
const char *p = opts->current_fixups.buf;
opts->current_fixup_count = 1;
while ((p = strchr(p, '\n'))) {
@@ -2509,12 +3067,16 @@ static int read_populate_opts(struct replay_opts *opts)
}
if (read_oneliner(&buf, rebase_path_squash_onto(), 0)) {
- if (get_oid_hex(buf.buf, &opts->squash_onto) < 0)
- return error(_("unusable squash-onto"));
+ if (repo_get_oid_committish(the_repository, buf.buf, &opts->squash_onto) < 0) {
+ ret = error(_("unusable squash-onto"));
+ goto done_rebase_i;
+ }
opts->have_squash_onto = 1;
}
- return 0;
+done_rebase_i:
+ strbuf_release(&buf);
+ return ret;
}
if (!file_exists(git_path_opts_file()))
@@ -2533,36 +3095,36 @@ static int read_populate_opts(struct replay_opts *opts)
static void write_strategy_opts(struct replay_opts *opts)
{
- int i;
struct strbuf buf = STRBUF_INIT;
- for (i = 0; i < opts->xopts_nr; ++i)
- strbuf_addf(&buf, " --%s", opts->xopts[i]);
-
+ /*
+ * Quote strategy options so that they can be read correctly
+ * by split_cmdline().
+ */
+ quote_cmdline(&buf, opts->xopts.v);
write_file(rebase_path_strategy_opts(), "%s\n", buf.buf);
strbuf_release(&buf);
}
int write_basic_state(struct replay_opts *opts, const char *head_name,
- struct commit *onto, const char *orig_head)
+ struct commit *onto, const struct object_id *orig_head)
{
- const char *quiet = getenv("GIT_QUIET");
-
if (head_name)
write_file(rebase_path_head_name(), "%s\n", head_name);
if (onto)
write_file(rebase_path_onto(), "%s\n",
oid_to_hex(&onto->object.oid));
if (orig_head)
- write_file(rebase_path_orig_head(), "%s\n", orig_head);
+ write_file(rebase_path_orig_head(), "%s\n",
+ oid_to_hex(orig_head));
- if (quiet)
- write_file(rebase_path_quiet(), "%s\n", quiet);
+ if (opts->quiet)
+ write_file(rebase_path_quiet(), "%s", "");
if (opts->verbose)
write_file(rebase_path_verbose(), "%s", "");
if (opts->strategy)
write_file(rebase_path_strategy(), "%s\n", opts->strategy);
- if (opts->xopts_nr > 0)
+ if (opts->xopts.nr > 0)
write_strategy_opts(opts);
if (opts->allow_rerere_auto == RERERE_AUTOUPDATE)
@@ -2574,8 +3136,18 @@ int write_basic_state(struct replay_opts *opts, const char *head_name,
write_file(rebase_path_gpg_sign_opt(), "-S%s\n", opts->gpg_sign);
if (opts->signoff)
write_file(rebase_path_signoff(), "--signoff\n");
+ if (opts->drop_redundant_commits)
+ write_file(rebase_path_drop_redundant_commits(), "%s", "");
+ if (opts->keep_redundant_commits)
+ write_file(rebase_path_keep_redundant_commits(), "%s", "");
+ if (opts->committer_date_is_author_date)
+ write_file(rebase_path_cdate_is_adate(), "%s", "");
+ if (opts->ignore_date)
+ write_file(rebase_path_ignore_date(), "%s", "");
if (opts->reschedule_failed_exec)
write_file(rebase_path_reschedule_failed_exec(), "%s", "");
+ else
+ write_file(rebase_path_no_reschedule_failed_exec(), "%s", "");
return 0;
}
@@ -2596,7 +3168,9 @@ static int walk_revs_populate_todo(struct todo_list *todo_list,
while ((commit = get_revision(opts->revs))) {
struct todo_item *item = append_new_todo(todo_list);
- const char *commit_buffer = logmsg_reencode(commit, NULL, encoding);
+ const char *commit_buffer = repo_logmsg_reencode(the_repository,
+ commit, NULL,
+ encoding);
const char *subject;
int subject_len;
@@ -2607,8 +3181,10 @@ static int walk_revs_populate_todo(struct todo_list *todo_list,
item->offset_in_buf = todo_list->buf.len;
subject_len = find_commit_subject(commit_buffer, &subject);
strbuf_addf(&todo_list->buf, "%s %s %.*s\n", command_string,
- short_commit_name(commit), subject_len, subject);
- unuse_commit_buffer(commit, commit_buffer);
+ short_commit_name(the_repository, commit),
+ subject_len, subject);
+ repo_unuse_commit_buffer(the_repository, commit,
+ commit_buffer);
}
if (!todo_list->nr)
@@ -2622,8 +3198,9 @@ static int create_seq_dir(struct repository *r)
enum replay_action action;
const char *in_progress_error = NULL;
const char *in_progress_advice = NULL;
- unsigned int advise_skip = file_exists(git_path_revert_head(r)) ||
- file_exists(git_path_cherry_pick_head(r));
+ unsigned int advise_skip =
+ refs_ref_exists(get_main_ref_store(r), "REVERT_HEAD") ||
+ refs_ref_exists(get_main_ref_store(r), "CHERRY_PICK_HEAD");
if (!sequencer_get_last_command(r, &action)) {
switch (action) {
@@ -2643,7 +3220,7 @@ static int create_seq_dir(struct repository *r)
}
if (in_progress_error) {
error("%s", in_progress_error);
- if (advice_sequencer_in_use)
+ if (advice_enabled(ADVICE_SEQUENCER_IN_USE))
advise(in_progress_advice,
advise_skip ? "--skip | " : "");
return -1;
@@ -2657,25 +3234,7 @@ static int create_seq_dir(struct repository *r)
static int save_head(const char *head)
{
- struct lock_file head_lock = LOCK_INIT;
- struct strbuf buf = STRBUF_INIT;
- int fd;
- ssize_t written;
-
- fd = hold_lock_file_for_update(&head_lock, git_path_head_file(), 0);
- if (fd < 0)
- return error_errno(_("could not lock HEAD"));
- strbuf_addf(&buf, "%s\n", head);
- written = write_in_full(fd, buf.buf, buf.len);
- strbuf_release(&buf);
- if (written < 0) {
- error_errno(_("could not write to '%s'"), git_path_head_file());
- rollback_lock_file(&head_lock);
- return -1;
- }
- if (commit_lock_file(&head_lock) < 0)
- return error(_("failed to finalize '%s'"), git_path_head_file());
- return 0;
+ return write_message(head, strlen(head), git_path_head_file(), 1);
}
static int rollback_is_safe(void)
@@ -2696,7 +3255,7 @@ static int rollback_is_safe(void)
else
die_errno(_("could not read '%s'"), git_path_abort_safety_file());
- if (get_oid("HEAD", &actual_head))
+ if (repo_get_oid(the_repository, "HEAD", &actual_head))
oidclr(&actual_head);
return oideq(&actual_head, &expected_head);
@@ -2704,26 +3263,23 @@ static int rollback_is_safe(void)
static int reset_merge(const struct object_id *oid)
{
- int ret;
- struct argv_array argv = ARGV_ARRAY_INIT;
+ struct child_process cmd = CHILD_PROCESS_INIT;
- argv_array_pushl(&argv, "reset", "--merge", NULL);
+ cmd.git_cmd = 1;
+ strvec_pushl(&cmd.args, "reset", "--merge", NULL);
if (!is_null_oid(oid))
- argv_array_push(&argv, oid_to_hex(oid));
+ strvec_push(&cmd.args, oid_to_hex(oid));
- ret = run_command_v_opt(argv.argv, RUN_GIT_CMD);
- argv_array_clear(&argv);
-
- return ret;
+ return run_command(&cmd);
}
static int rollback_single_pick(struct repository *r)
{
struct object_id head_oid;
- if (!file_exists(git_path_cherry_pick_head(r)) &&
- !file_exists(git_path_revert_head(r)))
+ if (!refs_ref_exists(get_main_ref_store(r), "CHERRY_PICK_HEAD") &&
+ !refs_ref_exists(get_main_ref_store(r), "REVERT_HEAD"))
return error(_("no cherry-pick or revert in progress"));
if (read_ref_full("HEAD", 0, &head_oid, NULL))
return error(_("cannot resolve HEAD"));
@@ -2817,7 +3373,7 @@ int sequencer_skip(struct repository *r, struct replay_opts *opts)
*/
switch (opts->action) {
case REPLAY_REVERT:
- if (!file_exists(git_path_revert_head(r))) {
+ if (!refs_ref_exists(get_main_ref_store(r), "REVERT_HEAD")) {
if (action != REPLAY_REVERT)
return error(_("no revert in progress"));
if (!rollback_is_safe())
@@ -2825,7 +3381,8 @@ int sequencer_skip(struct repository *r, struct replay_opts *opts)
}
break;
case REPLAY_PICK:
- if (!file_exists(git_path_cherry_pick_head(r))) {
+ if (!refs_ref_exists(get_main_ref_store(r),
+ "CHERRY_PICK_HEAD")) {
if (action != REPLAY_PICK)
return error(_("no cherry-pick in progress"));
if (!rollback_is_safe())
@@ -2846,7 +3403,7 @@ int sequencer_skip(struct repository *r, struct replay_opts *opts)
give_advice:
error(_("there is nothing to skip"));
- if (advice_resolve_conflict) {
+ if (advice_enabled(ADVICE_RESOLVE_CONFLICT)) {
advise(_("have you committed already?\n"
"try \"git %s --continue\""),
action == REPLAY_REVERT ? "revert" : "cherry-pick");
@@ -2854,7 +3411,8 @@ give_advice:
return -1;
}
-static int save_todo(struct todo_list *todo_list, struct replay_opts *opts)
+static int save_todo(struct todo_list *todo_list, struct replay_opts *opts,
+ int reschedule)
{
struct lock_file todo_lock = LOCK_INIT;
const char *todo_path = get_todo_path(opts);
@@ -2864,7 +3422,7 @@ static int save_todo(struct todo_list *todo_list, struct replay_opts *opts)
* rebase -i writes "git-rebase-todo" without the currently executing
* command, appending it to "done" instead.
*/
- if (is_rebase_i(opts))
+ if (is_rebase_i(opts) && !reschedule)
next++;
fd = hold_lock_file_for_update(&todo_lock, todo_path, 0);
@@ -2877,7 +3435,7 @@ static int save_todo(struct todo_list *todo_list, struct replay_opts *opts)
if (commit_lock_file(&todo_lock) < 0)
return error(_("failed to finalize '%s'"), todo_path);
- if (is_rebase_i(opts) && next > 0) {
+ if (is_rebase_i(opts) && !reschedule && next > 0) {
const char *done = rebase_path_done();
int fd = open(done, O_CREAT | O_WRONLY | O_APPEND, 0666);
int ret = 0;
@@ -2903,9 +3461,9 @@ static int save_opts(struct replay_opts *opts)
if (opts->no_commit)
res |= git_config_set_in_file_gently(opts_file,
"options.no-commit", "true");
- if (opts->edit)
- res |= git_config_set_in_file_gently(opts_file,
- "options.edit", "true");
+ if (opts->edit >= 0)
+ res |= git_config_set_in_file_gently(opts_file, "options.edit",
+ opts->edit ? "true" : "false");
if (opts->allow_empty)
res |= git_config_set_in_file_gently(opts_file,
"options.allow-empty", "true");
@@ -2937,13 +3495,10 @@ static int save_opts(struct replay_opts *opts)
if (opts->gpg_sign)
res |= git_config_set_in_file_gently(opts_file,
"options.gpg-sign", opts->gpg_sign);
- if (opts->xopts) {
- int i;
- for (i = 0; i < opts->xopts_nr; i++)
- res |= git_config_set_multivar_in_file_gently(opts_file,
- "options.strategy-option",
- opts->xopts[i], "^$", 0);
- }
+ for (size_t i = 0; i < opts->xopts.nr; i++)
+ res |= git_config_set_multivar_in_file_gently(opts_file,
+ "options.strategy-option",
+ opts->xopts.v[i], "^$", 0);
if (opts->allow_rerere_auto)
res |= git_config_set_in_file_gently(opts_file,
"options.allow-rerere-auto",
@@ -2961,19 +3516,19 @@ static int make_patch(struct repository *r,
struct commit *commit,
struct replay_opts *opts)
{
- struct strbuf buf = STRBUF_INIT;
struct rev_info log_tree_opt;
- const char *subject, *p;
+ const char *subject;
+ char hex[GIT_MAX_HEXSZ + 1];
int res = 0;
- p = short_commit_name(commit);
- if (write_message(p, strlen(p), rebase_path_stopped_sha(), 1) < 0)
+ if (!is_rebase_i(opts))
+ BUG("make_patch should only be called when rebasing");
+
+ oid_to_hex_r(hex, &commit->object.oid);
+ if (write_message(hex, strlen(hex), rebase_path_stopped_sha(), 1) < 0)
return -1;
- if (update_ref("rebase", "REBASE_HEAD", &commit->object.oid,
- NULL, REF_NO_DEREF, UPDATE_REFS_MSG_ON_ERR))
- res |= error(_("could not update %s"), "REBASE_HEAD");
+ res |= write_rebase_head(&commit->object.oid);
- strbuf_addf(&buf, "%s/patch", get_dir(opts));
memset(&log_tree_opt, 0, sizeof(log_tree_opt));
repo_init_revisions(r, &log_tree_opt, NULL);
log_tree_opt.abbrev = 0;
@@ -2981,25 +3536,27 @@ static int make_patch(struct repository *r,
log_tree_opt.diffopt.output_format = DIFF_FORMAT_PATCH;
log_tree_opt.disable_stdin = 1;
log_tree_opt.no_commit_id = 1;
- log_tree_opt.diffopt.file = fopen(buf.buf, "w");
+ log_tree_opt.diffopt.file = fopen(rebase_path_patch(), "w");
log_tree_opt.diffopt.use_color = GIT_COLOR_NEVER;
if (!log_tree_opt.diffopt.file)
- res |= error_errno(_("could not open '%s'"), buf.buf);
+ res |= error_errno(_("could not open '%s'"),
+ rebase_path_patch());
else {
res |= log_tree_commit(&log_tree_opt, commit);
fclose(log_tree_opt.diffopt.file);
}
- strbuf_reset(&buf);
- strbuf_addf(&buf, "%s/message", get_dir(opts));
- if (!file_exists(buf.buf)) {
+ if (!file_exists(rebase_path_message())) {
const char *encoding = get_commit_output_encoding();
- const char *commit_buffer = logmsg_reencode(commit, NULL, encoding);
+ const char *commit_buffer = repo_logmsg_reencode(r,
+ commit, NULL,
+ encoding);
find_commit_subject(commit_buffer, &subject);
- res |= write_message(subject, strlen(subject), buf.buf, 1);
- unuse_commit_buffer(commit, commit_buffer);
+ res |= write_message(subject, strlen(subject), rebase_path_message(), 1);
+ repo_unuse_commit_buffer(r, commit,
+ commit_buffer);
}
- strbuf_release(&buf);
+ release_revisions(&log_tree_opt);
return res;
}
@@ -3009,7 +3566,7 @@ static int intend_to_amend(void)
struct object_id head;
char *p;
- if (get_oid("HEAD", &head))
+ if (repo_get_oid(the_repository, "HEAD", &head))
return error(_("cannot read HEAD"));
p = oid_to_hex(&head);
@@ -3046,7 +3603,7 @@ static int error_with_patch(struct repository *r,
} else if (exit_code) {
if (commit)
fprintf_ln(stderr, _("Could not apply %s... %.*s"),
- short_commit_name(commit), subject_len, subject);
+ short_commit_name(r, commit), subject_len, subject);
else
/*
* We don't have the hash of the parent so
@@ -3078,20 +3635,18 @@ static int error_failed_squash(struct repository *r,
static int do_exec(struct repository *r, const char *command_line)
{
- struct argv_array child_env = ARGV_ARRAY_INIT;
- const char *child_argv[] = { NULL, NULL };
+ struct child_process cmd = CHILD_PROCESS_INIT;
int dirty, status;
- fprintf(stderr, "Executing: %s\n", command_line);
- child_argv[0] = command_line;
- argv_array_pushf(&child_env, "GIT_DIR=%s", absolute_path(get_git_dir()));
- argv_array_pushf(&child_env, "GIT_WORK_TREE=%s",
- absolute_path(get_git_work_tree()));
- status = run_command_v_opt_cd_env(child_argv, RUN_USING_SHELL, NULL,
- child_env.argv);
+ fprintf(stderr, _("Executing: %s\n"), command_line);
+ cmd.use_shell = 1;
+ strvec_push(&cmd.args, command_line);
+ strvec_push(&cmd.env, "GIT_CHERRY_PICK_HELP");
+ status = run_command(&cmd);
/* force re-reading of the cache */
- if (discard_index(r->index) < 0 || repo_read_index(r) < 0)
+ discard_index(r->index);
+ if (repo_read_index(r) < 0)
return error(_("could not read index"));
dirty = require_clean_work_tree(r, "rebase", NULL, 1, 1);
@@ -3103,14 +3658,14 @@ static int do_exec(struct repository *r, const char *command_line)
" git rebase --continue\n"
"\n"),
command_line,
- dirty ? N_("and made changes to the index and/or the "
- "working tree\n") : "");
+ dirty ? _("and made changes to the index and/or the "
+ "working tree.\n") : "");
if (status == 127)
/* command not found */
status = 1;
} else if (dirty) {
warning(_("execution succeeded: %s\nbut "
- "left changes to the index and/or the working tree\n"
+ "left changes to the index and/or the working tree.\n"
"Commit or stash your changes, and then run\n"
"\n"
" git rebase --continue\n"
@@ -3118,11 +3673,10 @@ static int do_exec(struct repository *r, const char *command_line)
status = 1;
}
- argv_array_clear(&child_env);
-
return status;
}
+__attribute__((format (printf, 2, 3)))
static int safe_append(const char *filename, const char *fmt, ...)
{
va_list ap;
@@ -3152,7 +3706,6 @@ static int safe_append(const char *filename, const char *fmt, ...)
}
if (commit_lock_file(&lock) < 0) {
strbuf_release(&buf);
- rollback_lock_file(&lock);
return error(_("failed to finalize '%s'"), filename);
}
@@ -3173,13 +3726,13 @@ static int do_label(struct repository *r, const char *name, int len)
return error(_("illegal label name: '%.*s'"), len, name);
strbuf_addf(&ref_name, "refs/rewritten/%.*s", len, name);
- strbuf_addf(&msg, "rebase -i (label) '%.*s'", len, name);
+ strbuf_addf(&msg, "rebase (label) '%.*s'", len, name);
transaction = ref_store_transaction_begin(refs, &err);
if (!transaction) {
error("%s", err.buf);
ret = -1;
- } else if (get_oid("HEAD", &head_oid)) {
+ } else if (repo_get_oid(r, "HEAD", &head_oid)) {
error(_("could not read HEAD"));
ret = -1;
} else if (ref_transaction_update(transaction, ref_name.buf, &head_oid,
@@ -3200,8 +3753,60 @@ static int do_label(struct repository *r, const char *name, int len)
return ret;
}
+static const char *sequencer_reflog_action(struct replay_opts *opts)
+{
+ if (!opts->reflog_action) {
+ opts->reflog_action = getenv(GIT_REFLOG_ACTION);
+ opts->reflog_action =
+ xstrdup(opts->reflog_action ? opts->reflog_action
+ : action_name(opts));
+ }
+
+ return opts->reflog_action;
+}
+
+__attribute__((format (printf, 3, 4)))
static const char *reflog_message(struct replay_opts *opts,
- const char *sub_action, const char *fmt, ...);
+ const char *sub_action, const char *fmt, ...)
+{
+ va_list ap;
+ static struct strbuf buf = STRBUF_INIT;
+
+ va_start(ap, fmt);
+ strbuf_reset(&buf);
+ strbuf_addstr(&buf, sequencer_reflog_action(opts));
+ if (sub_action)
+ strbuf_addf(&buf, " (%s)", sub_action);
+ if (fmt) {
+ strbuf_addstr(&buf, ": ");
+ strbuf_vaddf(&buf, fmt, ap);
+ }
+ va_end(ap);
+
+ return buf.buf;
+}
+
+static struct commit *lookup_label(struct repository *r, const char *label,
+ int len, struct strbuf *buf)
+{
+ struct commit *commit;
+ struct object_id oid;
+
+ strbuf_reset(buf);
+ strbuf_addf(buf, "refs/rewritten/%.*s", len, label);
+ if (!read_ref(buf->buf, &oid)) {
+ commit = lookup_commit_object(r, &oid);
+ } else {
+ /* fall back to non-rewritten ref or commit */
+ strbuf_splice(buf, 0, strlen("refs/rewritten/"), "", 0);
+ commit = lookup_commit_reference_by_name(buf->buf);
+ }
+
+ if (!commit)
+ error(_("could not resolve '%s'"), buf->buf);
+
+ return commit;
+}
static int do_reset(struct repository *r,
const char *name, int len,
@@ -3210,9 +3815,9 @@ static int do_reset(struct repository *r,
struct strbuf ref_name = STRBUF_INIT;
struct object_id oid;
struct lock_file lock = LOCK_INIT;
- struct tree_desc desc;
+ struct tree_desc desc = { 0 };
struct tree *tree;
- struct unpack_trees_options unpack_tree_opts;
+ struct unpack_trees_options unpack_tree_opts = { 0 };
int ret = 0;
if (repo_hold_locked_index(r, &lock, LOCK_REPORT_ON_ERROR) < 0)
@@ -3234,6 +3839,7 @@ static int do_reset(struct repository *r,
oidcpy(&oid, &opts->squash_onto);
} else {
int i;
+ struct commit *commit;
/* Determine the length of the label */
for (i = 0; i < len; i++)
@@ -3241,17 +3847,14 @@ static int do_reset(struct repository *r,
break;
len = i;
- strbuf_addf(&ref_name, "refs/rewritten/%.*s", len, name);
- if (get_oid(ref_name.buf, &oid) &&
- get_oid(ref_name.buf + strlen("refs/rewritten/"), &oid)) {
- error(_("could not read '%s'"), ref_name.buf);
- rollback_lock_file(&lock);
- strbuf_release(&ref_name);
- return -1;
+ commit = lookup_label(r, name, len, &ref_name);
+ if (!commit) {
+ ret = -1;
+ goto cleanup;
}
+ oid = commit->object.oid;
}
- memset(&unpack_tree_opts, 0, sizeof(unpack_tree_opts));
setup_unpack_trees_porcelain(&unpack_tree_opts, "reset");
unpack_tree_opts.head_idx = 1;
unpack_tree_opts.src_index = r->index;
@@ -3259,26 +3862,23 @@ static int do_reset(struct repository *r,
unpack_tree_opts.fn = oneway_merge;
unpack_tree_opts.merge = 1;
unpack_tree_opts.update = 1;
+ unpack_tree_opts.preserve_ignored = 0; /* FIXME: !overwrite_ignore */
+ unpack_tree_opts.skip_cache_tree_update = 1;
+ init_checkout_metadata(&unpack_tree_opts.meta, name, &oid, NULL);
if (repo_read_index_unmerged(r)) {
- rollback_lock_file(&lock);
- strbuf_release(&ref_name);
- return error_resolve_conflict(_(action_name(opts)));
+ ret = error_resolve_conflict(action_name(opts));
+ goto cleanup;
}
if (!fill_tree_descriptor(r, &desc, &oid)) {
- error(_("failed to find tree of %s"), oid_to_hex(&oid));
- rollback_lock_file(&lock);
- free((void *)desc.buffer);
- strbuf_release(&ref_name);
- return -1;
+ ret = error(_("failed to find tree of %s"), oid_to_hex(&oid));
+ goto cleanup;
}
if (unpack_trees(1, &desc, &unpack_tree_opts)) {
- rollback_lock_file(&lock);
- free((void *)desc.buffer);
- strbuf_release(&ref_name);
- return -1;
+ ret = -1;
+ goto cleanup;
}
tree = parse_tree_indirect(&oid);
@@ -3286,50 +3886,34 @@ static int do_reset(struct repository *r,
if (write_locked_index(r->index, &lock, COMMIT_LOCK) < 0)
ret = error(_("could not write index"));
- free((void *)desc.buffer);
if (!ret)
ret = update_ref(reflog_message(opts, "reset", "'%.*s'",
len, name), "HEAD", &oid,
NULL, 0, UPDATE_REFS_MSG_ON_ERR);
-
+cleanup:
+ free((void *)desc.buffer);
+ if (ret < 0)
+ rollback_lock_file(&lock);
strbuf_release(&ref_name);
+ clear_unpack_trees_porcelain(&unpack_tree_opts);
return ret;
}
-static struct commit *lookup_label(const char *label, int len,
- struct strbuf *buf)
-{
- struct commit *commit;
-
- strbuf_reset(buf);
- strbuf_addf(buf, "refs/rewritten/%.*s", len, label);
- commit = lookup_commit_reference_by_name(buf->buf);
- if (!commit) {
- /* fall back to non-rewritten ref or commit */
- strbuf_splice(buf, 0, strlen("refs/rewritten/"), "", 0);
- commit = lookup_commit_reference_by_name(buf->buf);
- }
-
- if (!commit)
- error(_("could not resolve '%s'"), buf->buf);
-
- return commit;
-}
-
static int do_merge(struct repository *r,
struct commit *commit,
const char *arg, int arg_len,
- int flags, struct replay_opts *opts)
+ int flags, int *check_todo, struct replay_opts *opts)
{
- int run_commit_flags = (flags & TODO_EDIT_MERGE_MSG) ?
- EDIT_MSG | VERIFY_MSG : 0;
+ int run_commit_flags = 0;
struct strbuf ref_name = STRBUF_INIT;
struct commit *head_commit, *merge_commit, *i;
- struct commit_list *bases, *j, *reversed = NULL;
+ struct commit_list *bases, *j;
struct commit_list *to_merge = NULL, **tail = &to_merge;
- const char *strategy = !opts->xopts_nr &&
- (!opts->strategy || !strcmp(opts->strategy, "recursive")) ?
+ const char *strategy = !opts->xopts.nr &&
+ (!opts->strategy ||
+ !strcmp(opts->strategy, "recursive") ||
+ !strcmp(opts->strategy, "ort")) ?
NULL : opts->strategy;
struct merge_options o;
int merge_arg_len, oneline_offset, can_fast_forward, ret, k;
@@ -3363,7 +3947,7 @@ static int do_merge(struct repository *r,
k = strcspn(p, " \t\n");
if (!k)
continue;
- merge_commit = lookup_label(p, k, &ref_name);
+ merge_commit = lookup_label(r, p, k, &ref_name);
if (!merge_commit) {
ret = error(_("unable to parse '%.*s'"), k, p);
goto leave_merge;
@@ -3395,9 +3979,49 @@ static int do_merge(struct repository *r,
goto leave_merge;
}
+ /*
+ * If HEAD is not identical to the first parent of the original merge
+ * commit, we cannot fast-forward.
+ */
+ can_fast_forward = opts->allow_ff && commit && commit->parents &&
+ oideq(&commit->parents->item->object.oid,
+ &head_commit->object.oid);
+
+ /*
+ * If any merge head is different from the original one, we cannot
+ * fast-forward.
+ */
+ if (can_fast_forward) {
+ struct commit_list *p = commit->parents->next;
+
+ for (j = to_merge; j && p; j = j->next, p = p->next)
+ if (!oideq(&j->item->object.oid,
+ &p->item->object.oid)) {
+ can_fast_forward = 0;
+ break;
+ }
+ /*
+ * If the number of merge heads differs from the original merge
+ * commit, we cannot fast-forward.
+ */
+ if (j || p)
+ can_fast_forward = 0;
+ }
+
+ if (can_fast_forward) {
+ rollback_lock_file(&lock);
+ ret = fast_forward_to(r, &commit->object.oid,
+ &head_commit->object.oid, 0, opts);
+ if (flags & TODO_EDIT_MERGE_MSG)
+ goto fast_forward_edit;
+
+ goto leave_merge;
+ }
+
if (commit) {
const char *encoding = get_commit_output_encoding();
- const char *message = logmsg_reencode(commit, NULL, encoding);
+ const char *message = repo_logmsg_reencode(r, commit, NULL,
+ encoding);
const char *body;
int len;
@@ -3410,7 +4034,7 @@ static int do_merge(struct repository *r,
find_commit_subject(message, &body);
len = strlen(body);
ret = write_message(body, len, git_path_merge_msg(r), 0);
- unuse_commit_buffer(commit, message);
+ repo_unuse_commit_buffer(r, commit, message);
if (ret) {
error_errno(_("could not write '%s'"),
git_path_merge_msg(r));
@@ -3444,98 +4068,73 @@ static int do_merge(struct repository *r,
}
}
- /*
- * If HEAD is not identical to the first parent of the original merge
- * commit, we cannot fast-forward.
- */
- can_fast_forward = opts->allow_ff && commit && commit->parents &&
- oideq(&commit->parents->item->object.oid,
- &head_commit->object.oid);
-
- /*
- * If any merge head is different from the original one, we cannot
- * fast-forward.
- */
- if (can_fast_forward) {
- struct commit_list *p = commit->parents->next;
-
- for (j = to_merge; j && p; j = j->next, p = p->next)
- if (!oideq(&j->item->object.oid,
- &p->item->object.oid)) {
- can_fast_forward = 0;
- break;
- }
- /*
- * If the number of merge heads differs from the original merge
- * commit, we cannot fast-forward.
- */
- if (j || p)
- can_fast_forward = 0;
- }
-
- if (can_fast_forward) {
- rollback_lock_file(&lock);
- ret = fast_forward_to(r, &commit->object.oid,
- &head_commit->object.oid, 0, opts);
- if (flags & TODO_EDIT_MERGE_MSG) {
- run_commit_flags |= AMEND_MSG;
- goto fast_forward_edit;
- }
- goto leave_merge;
- }
-
if (strategy || to_merge->next) {
/* Octopus merge */
struct child_process cmd = CHILD_PROCESS_INIT;
- if (read_env_script(&cmd.env_array)) {
+ if (read_env_script(&cmd.env)) {
const char *gpg_opt = gpg_sign_opt_quoted(opts);
ret = error(_(staged_changes_advice), gpg_opt, gpg_opt);
goto leave_merge;
}
+ if (opts->committer_date_is_author_date)
+ strvec_pushf(&cmd.env, "GIT_COMMITTER_DATE=%s",
+ opts->ignore_date ?
+ "" :
+ author_date_from_env(&cmd.env));
+ if (opts->ignore_date)
+ strvec_push(&cmd.env, "GIT_AUTHOR_DATE=");
+
cmd.git_cmd = 1;
- argv_array_push(&cmd.args, "merge");
- argv_array_push(&cmd.args, "-s");
+ strvec_push(&cmd.args, "merge");
+ strvec_push(&cmd.args, "-s");
if (!strategy)
- argv_array_push(&cmd.args, "octopus");
+ strvec_push(&cmd.args, "octopus");
else {
- argv_array_push(&cmd.args, strategy);
- for (k = 0; k < opts->xopts_nr; k++)
- argv_array_pushf(&cmd.args,
- "-X%s", opts->xopts[k]);
+ strvec_push(&cmd.args, strategy);
+ for (k = 0; k < opts->xopts.nr; k++)
+ strvec_pushf(&cmd.args,
+ "-X%s", opts->xopts.v[k]);
}
- argv_array_push(&cmd.args, "--no-edit");
- argv_array_push(&cmd.args, "--no-ff");
- argv_array_push(&cmd.args, "--no-log");
- argv_array_push(&cmd.args, "--no-stat");
- argv_array_push(&cmd.args, "-F");
- argv_array_push(&cmd.args, git_path_merge_msg(r));
+ if (!(flags & TODO_EDIT_MERGE_MSG))
+ strvec_push(&cmd.args, "--no-edit");
+ else
+ strvec_push(&cmd.args, "--edit");
+ strvec_push(&cmd.args, "--no-ff");
+ strvec_push(&cmd.args, "--no-log");
+ strvec_push(&cmd.args, "--no-stat");
+ strvec_push(&cmd.args, "-F");
+ strvec_push(&cmd.args, git_path_merge_msg(r));
if (opts->gpg_sign)
- argv_array_push(&cmd.args, opts->gpg_sign);
+ strvec_pushf(&cmd.args, "-S%s", opts->gpg_sign);
+ else
+ strvec_push(&cmd.args, "--no-gpg-sign");
/* Add the tips to be merged */
for (j = to_merge; j; j = j->next)
- argv_array_push(&cmd.args,
- oid_to_hex(&j->item->object.oid));
+ strvec_push(&cmd.args,
+ oid_to_hex(&j->item->object.oid));
strbuf_release(&ref_name);
- unlink(git_path_cherry_pick_head(r));
+ refs_delete_ref(get_main_ref_store(r), "", "CHERRY_PICK_HEAD",
+ NULL, REF_NO_DEREF);
rollback_lock_file(&lock);
- rollback_lock_file(&lock);
ret = run_command(&cmd);
/* force re-reading of the cache */
- if (!ret && (discard_index(r->index) < 0 ||
- repo_read_index(r) < 0))
- ret = error(_("could not read index"));
+ if (!ret) {
+ discard_index(r->index);
+ if (repo_read_index(r) < 0)
+ ret = error(_("could not read index"));
+ }
goto leave_merge;
}
merge_commit = to_merge->item;
- bases = get_merge_bases(head_commit, merge_commit);
+ bases = repo_get_merge_bases(r, head_commit, merge_commit);
if (bases && oideq(&merge_commit->object.oid,
&bases->item->object.oid)) {
ret = 0;
@@ -3547,9 +4146,7 @@ static int do_merge(struct repository *r,
git_path_merge_head(r), 0);
write_message("no-ff", 5, git_path_merge_mode(r), 0);
- for (j = bases; j; j = j->next)
- commit_list_insert(j->item, &reversed);
- free_commit_list(bases);
+ bases = reverse_commit_list(bases);
repo_read_index(r);
init_merge_options(&o, r);
@@ -3557,13 +4154,27 @@ static int do_merge(struct repository *r,
o.branch2 = ref_name.buf;
o.buffer_output = 2;
- ret = merge_recursive(&o, head_commit, merge_commit, reversed, &i);
+ if (!opts->strategy || !strcmp(opts->strategy, "ort")) {
+ /*
+ * TODO: Should use merge_incore_recursive() and
+ * merge_switch_to_result(), skipping the call to
+ * merge_switch_to_result() when we don't actually need to
+ * update the index and working copy immediately.
+ */
+ ret = merge_ort_recursive(&o,
+ head_commit, merge_commit, bases,
+ &i);
+ } else {
+ ret = merge_recursive(&o, head_commit, merge_commit, bases,
+ &i);
+ }
if (ret <= 0)
fputs(o.obuf.buf, stdout);
strbuf_release(&o.obuf);
if (ret < 0) {
error(_("could not even attempt to merge '%.*s'"),
merge_arg_len, arg);
+ unlink(git_path_merge_msg(r));
goto leave_merge;
}
/*
@@ -3591,10 +4202,17 @@ static int do_merge(struct repository *r,
* value (a negative one would indicate that the `merge`
* command needs to be rescheduled).
*/
- fast_forward_edit:
- ret = !!run_git_commit(r, git_path_merge_msg(r), opts,
+ ret = !!run_git_commit(git_path_merge_msg(r), opts,
run_commit_flags);
+ if (!ret && flags & TODO_EDIT_MERGE_MSG) {
+ fast_forward_edit:
+ *check_todo = 1;
+ run_commit_flags |= AMEND_MSG | EDIT_MSG | VERIFY_MSG;
+ ret = !!run_git_commit(NULL, opts, run_commit_flags);
+ }
+
+
leave_merge:
strbuf_release(&ref_name);
rollback_lock_file(&lock);
@@ -3602,6 +4220,224 @@ leave_merge:
return ret;
}
+static int write_update_refs_state(struct string_list *refs_to_oids)
+{
+ int result = 0;
+ struct lock_file lock = LOCK_INIT;
+ FILE *fp = NULL;
+ struct string_list_item *item;
+ char *path;
+
+ path = rebase_path_update_refs(the_repository->gitdir);
+
+ if (!refs_to_oids->nr) {
+ if (unlink(path) && errno != ENOENT)
+ result = error_errno(_("could not unlink: %s"), path);
+ goto cleanup;
+ }
+
+ if (safe_create_leading_directories(path)) {
+ result = error(_("unable to create leading directories of %s"),
+ path);
+ goto cleanup;
+ }
+
+ if (hold_lock_file_for_update(&lock, path, 0) < 0) {
+ result = error(_("another 'rebase' process appears to be running; "
+ "'%s.lock' already exists"),
+ path);
+ goto cleanup;
+ }
+
+ fp = fdopen_lock_file(&lock, "w");
+ if (!fp) {
+ result = error_errno(_("could not open '%s' for writing"), path);
+ rollback_lock_file(&lock);
+ goto cleanup;
+ }
+
+ for_each_string_list_item(item, refs_to_oids) {
+ struct update_ref_record *rec = item->util;
+ fprintf(fp, "%s\n%s\n%s\n", item->string,
+ oid_to_hex(&rec->before), oid_to_hex(&rec->after));
+ }
+
+ result = commit_lock_file(&lock);
+
+cleanup:
+ free(path);
+ return result;
+}
+
+/*
+ * Parse the update-refs file for the current rebase, then remove the
+ * refs that do not appear in the todo_list (and have not had updated
+ * values stored) and add refs that are in the todo_list but not
+ * represented in the update-refs file.
+ *
+ * If there are changes to the update-refs list, then write the new state
+ * to disk.
+ */
+void todo_list_filter_update_refs(struct repository *r,
+ struct todo_list *todo_list)
+{
+ int i;
+ int updated = 0;
+ struct string_list update_refs = STRING_LIST_INIT_DUP;
+
+ sequencer_get_update_refs_state(r->gitdir, &update_refs);
+
+ /*
+ * For each item in the update_refs list, if it has no updated
+ * value and does not appear in the todo_list, then remove it
+ * from the update_refs list.
+ */
+ for (i = 0; i < update_refs.nr; i++) {
+ int j;
+ int found = 0;
+ const char *ref = update_refs.items[i].string;
+ size_t reflen = strlen(ref);
+ struct update_ref_record *rec = update_refs.items[i].util;
+
+ /* OID already stored as updated. */
+ if (!is_null_oid(&rec->after))
+ continue;
+
+ for (j = 0; !found && j < todo_list->nr; j++) {
+ struct todo_item *item = &todo_list->items[j];
+ const char *arg = todo_list->buf.buf + item->arg_offset;
+
+ if (item->command != TODO_UPDATE_REF)
+ continue;
+
+ if (item->arg_len != reflen ||
+ strncmp(arg, ref, reflen))
+ continue;
+
+ found = 1;
+ }
+
+ if (!found) {
+ free(update_refs.items[i].string);
+ free(update_refs.items[i].util);
+
+ update_refs.nr--;
+ MOVE_ARRAY(update_refs.items + i, update_refs.items + i + 1, update_refs.nr - i);
+
+ updated = 1;
+ i--;
+ }
+ }
+
+ /*
+ * For each todo_item, check if its ref is in the update_refs list.
+ * If not, then add it as an un-updated ref.
+ */
+ for (i = 0; i < todo_list->nr; i++) {
+ struct todo_item *item = &todo_list->items[i];
+ const char *arg = todo_list->buf.buf + item->arg_offset;
+ int j, found = 0;
+
+ if (item->command != TODO_UPDATE_REF)
+ continue;
+
+ for (j = 0; !found && j < update_refs.nr; j++) {
+ const char *ref = update_refs.items[j].string;
+
+ found = strlen(ref) == item->arg_len &&
+ !strncmp(ref, arg, item->arg_len);
+ }
+
+ if (!found) {
+ struct string_list_item *inserted;
+ struct strbuf argref = STRBUF_INIT;
+
+ strbuf_add(&argref, arg, item->arg_len);
+ inserted = string_list_insert(&update_refs, argref.buf);
+ inserted->util = init_update_ref_record(argref.buf);
+ strbuf_release(&argref);
+ updated = 1;
+ }
+ }
+
+ if (updated)
+ write_update_refs_state(&update_refs);
+ string_list_clear(&update_refs, 1);
+}
+
+static int do_update_ref(struct repository *r, const char *refname)
+{
+ struct string_list_item *item;
+ struct string_list list = STRING_LIST_INIT_DUP;
+
+ if (sequencer_get_update_refs_state(r->gitdir, &list))
+ return -1;
+
+ for_each_string_list_item(item, &list) {
+ if (!strcmp(item->string, refname)) {
+ struct update_ref_record *rec = item->util;
+ if (read_ref("HEAD", &rec->after))
+ return -1;
+ break;
+ }
+ }
+
+ write_update_refs_state(&list);
+ string_list_clear(&list, 1);
+ return 0;
+}
+
+static int do_update_refs(struct repository *r, int quiet)
+{
+ int res = 0;
+ struct string_list_item *item;
+ struct string_list refs_to_oids = STRING_LIST_INIT_DUP;
+ struct ref_store *refs = get_main_ref_store(r);
+ struct strbuf update_msg = STRBUF_INIT;
+ struct strbuf error_msg = STRBUF_INIT;
+
+ if ((res = sequencer_get_update_refs_state(r->gitdir, &refs_to_oids)))
+ return res;
+
+ for_each_string_list_item(item, &refs_to_oids) {
+ struct update_ref_record *rec = item->util;
+ int loop_res;
+
+ loop_res = refs_update_ref(refs, "rewritten during rebase",
+ item->string,
+ &rec->after, &rec->before,
+ 0, UPDATE_REFS_MSG_ON_ERR);
+ res |= loop_res;
+
+ if (quiet)
+ continue;
+
+ if (loop_res)
+ strbuf_addf(&error_msg, "\t%s\n", item->string);
+ else
+ strbuf_addf(&update_msg, "\t%s\n", item->string);
+ }
+
+ if (!quiet &&
+ (update_msg.len || error_msg.len)) {
+ fprintf(stderr,
+ _("Updated the following refs with %s:\n%s"),
+ "--update-refs",
+ update_msg.buf);
+
+ if (res)
+ fprintf(stderr,
+ _("Failed to update the following refs with %s:\n%s"),
+ "--update-refs",
+ error_msg.buf);
+ }
+
+ string_list_clear(&refs_to_oids, 1);
+ strbuf_release(&update_msg);
+ strbuf_release(&error_msg);
+ return res;
+}
+
static int is_final_fixup(struct todo_list *todo_list)
{
int i = todo_list->current;
@@ -3628,124 +4464,205 @@ static enum todo_command peek_command(struct todo_list *todo_list, int offset)
return -1;
}
-static int apply_autostash(struct replay_opts *opts)
+static void create_autostash_internal(struct repository *r,
+ const char *path,
+ const char *refname)
+{
+ struct strbuf buf = STRBUF_INIT;
+ struct lock_file lock_file = LOCK_INIT;
+ int fd;
+
+ if (path && refname)
+ BUG("can only pass path or refname");
+
+ fd = repo_hold_locked_index(r, &lock_file, 0);
+ refresh_index(r->index, REFRESH_QUIET, NULL, NULL, NULL);
+ if (0 <= fd)
+ repo_update_index_if_able(r, &lock_file);
+ rollback_lock_file(&lock_file);
+
+ if (has_unstaged_changes(r, 1) ||
+ has_uncommitted_changes(r, 1)) {
+ struct child_process stash = CHILD_PROCESS_INIT;
+ struct reset_head_opts ropts = { .flags = RESET_HEAD_HARD };
+ struct object_id oid;
+
+ strvec_pushl(&stash.args,
+ "stash", "create", "autostash", NULL);
+ stash.git_cmd = 1;
+ stash.no_stdin = 1;
+ strbuf_reset(&buf);
+ if (capture_command(&stash, &buf, GIT_MAX_HEXSZ))
+ die(_("Cannot autostash"));
+ strbuf_trim_trailing_newline(&buf);
+ if (repo_get_oid(r, buf.buf, &oid))
+ die(_("Unexpected stash response: '%s'"),
+ buf.buf);
+ strbuf_reset(&buf);
+ strbuf_add_unique_abbrev(&buf, &oid, DEFAULT_ABBREV);
+
+ if (path) {
+ if (safe_create_leading_directories_const(path))
+ die(_("Could not create directory for '%s'"),
+ path);
+ write_file(path, "%s", oid_to_hex(&oid));
+ } else {
+ refs_update_ref(get_main_ref_store(r), "", refname,
+ &oid, null_oid(), 0, UPDATE_REFS_DIE_ON_ERR);
+ }
+
+ printf(_("Created autostash: %s\n"), buf.buf);
+ if (reset_head(r, &ropts) < 0)
+ die(_("could not reset --hard"));
+ discard_index(r->index);
+ if (repo_read_index(r) < 0)
+ die(_("could not read index"));
+ }
+ strbuf_release(&buf);
+}
+
+void create_autostash(struct repository *r, const char *path)
+{
+ create_autostash_internal(r, path, NULL);
+}
+
+void create_autostash_ref(struct repository *r, const char *refname)
+{
+ create_autostash_internal(r, NULL, refname);
+}
+
+static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply)
{
- struct strbuf stash_sha1 = STRBUF_INIT;
struct child_process child = CHILD_PROCESS_INIT;
int ret = 0;
- if (!read_oneliner(&stash_sha1, rebase_path_autostash(), 1)) {
- strbuf_release(&stash_sha1);
- return 0;
+ if (attempt_apply) {
+ child.git_cmd = 1;
+ child.no_stdout = 1;
+ child.no_stderr = 1;
+ strvec_push(&child.args, "stash");
+ strvec_push(&child.args, "apply");
+ strvec_push(&child.args, stash_oid);
+ ret = run_command(&child);
}
- strbuf_trim(&stash_sha1);
- child.git_cmd = 1;
- child.no_stdout = 1;
- child.no_stderr = 1;
- argv_array_push(&child.args, "stash");
- argv_array_push(&child.args, "apply");
- argv_array_push(&child.args, stash_sha1.buf);
- if (!run_command(&child))
+ if (attempt_apply && !ret)
fprintf(stderr, _("Applied autostash.\n"));
else {
struct child_process store = CHILD_PROCESS_INIT;
store.git_cmd = 1;
- argv_array_push(&store.args, "stash");
- argv_array_push(&store.args, "store");
- argv_array_push(&store.args, "-m");
- argv_array_push(&store.args, "autostash");
- argv_array_push(&store.args, "-q");
- argv_array_push(&store.args, stash_sha1.buf);
+ strvec_push(&store.args, "stash");
+ strvec_push(&store.args, "store");
+ strvec_push(&store.args, "-m");
+ strvec_push(&store.args, "autostash");
+ strvec_push(&store.args, "-q");
+ strvec_push(&store.args, stash_oid);
if (run_command(&store))
- ret = error(_("cannot store %s"), stash_sha1.buf);
+ ret = error(_("cannot store %s"), stash_oid);
else
fprintf(stderr,
- _("Applying autostash resulted in conflicts.\n"
+ _("%s\n"
"Your changes are safe in the stash.\n"
"You can run \"git stash pop\" or"
- " \"git stash drop\" at any time.\n"));
+ " \"git stash drop\" at any time.\n"),
+ attempt_apply ?
+ _("Applying autostash resulted in conflicts.") :
+ _("Autostash exists; creating a new stash entry."));
}
- strbuf_release(&stash_sha1);
return ret;
}
-static const char *reflog_message(struct replay_opts *opts,
- const char *sub_action, const char *fmt, ...)
+static int apply_save_autostash(const char *path, int attempt_apply)
{
- va_list ap;
- static struct strbuf buf = STRBUF_INIT;
+ struct strbuf stash_oid = STRBUF_INIT;
+ int ret = 0;
- va_start(ap, fmt);
- strbuf_reset(&buf);
- strbuf_addstr(&buf, action_name(opts));
- if (sub_action)
- strbuf_addf(&buf, " (%s)", sub_action);
- if (fmt) {
- strbuf_addstr(&buf, ": ");
- strbuf_vaddf(&buf, fmt, ap);
+ if (!read_oneliner(&stash_oid, path,
+ READ_ONELINER_SKIP_IF_EMPTY)) {
+ strbuf_release(&stash_oid);
+ return 0;
}
- va_end(ap);
+ strbuf_trim(&stash_oid);
- return buf.buf;
+ ret = apply_save_autostash_oid(stash_oid.buf, attempt_apply);
+
+ unlink(path);
+ strbuf_release(&stash_oid);
+ return ret;
}
-static int run_git_checkout(struct repository *r, struct replay_opts *opts,
- const char *commit, const char *action)
+int save_autostash(const char *path)
{
- struct child_process cmd = CHILD_PROCESS_INIT;
- int ret;
+ return apply_save_autostash(path, 0);
+}
- cmd.git_cmd = 1;
+int apply_autostash(const char *path)
+{
+ return apply_save_autostash(path, 1);
+}
- argv_array_push(&cmd.args, "checkout");
- argv_array_push(&cmd.args, commit);
- argv_array_pushf(&cmd.env_array, GIT_REFLOG_ACTION "=%s", action);
+int apply_autostash_oid(const char *stash_oid)
+{
+ return apply_save_autostash_oid(stash_oid, 1);
+}
- if (opts->verbose)
- ret = run_command(&cmd);
- else
- ret = run_command_silent_on_success(&cmd);
+static int apply_save_autostash_ref(struct repository *r, const char *refname,
+ int attempt_apply)
+{
+ struct object_id stash_oid;
+ char stash_oid_hex[GIT_MAX_HEXSZ + 1];
+ int flag, ret;
- if (!ret)
- discard_index(r->index);
+ if (!refs_ref_exists(get_main_ref_store(r), refname))
+ return 0;
+
+ if (!refs_resolve_ref_unsafe(get_main_ref_store(r), refname,
+ RESOLVE_REF_READING, &stash_oid, &flag))
+ return -1;
+ if (flag & REF_ISSYMREF)
+ return error(_("autostash reference is a symref"));
+
+ oid_to_hex_r(stash_oid_hex, &stash_oid);
+ ret = apply_save_autostash_oid(stash_oid_hex, attempt_apply);
+
+ refs_delete_ref(get_main_ref_store(r), "", refname,
+ &stash_oid, REF_NO_DEREF);
return ret;
}
-int prepare_branch_to_be_rebased(struct repository *r, struct replay_opts *opts,
- const char *commit)
+int save_autostash_ref(struct repository *r, const char *refname)
{
- const char *action;
-
- if (commit && *commit) {
- action = reflog_message(opts, "start", "checkout %s", commit);
- if (run_git_checkout(r, opts, commit, action))
- return error(_("could not checkout %s"), commit);
- }
+ return apply_save_autostash_ref(r, refname, 0);
+}
- return 0;
+int apply_autostash_ref(struct repository *r, const char *refname)
+{
+ return apply_save_autostash_ref(r, refname, 1);
}
static int checkout_onto(struct repository *r, struct replay_opts *opts,
const char *onto_name, const struct object_id *onto,
- const char *orig_head)
-{
- struct object_id oid;
- const char *action = reflog_message(opts, "start", "checkout %s", onto_name);
-
- if (get_oid(orig_head, &oid))
- return error(_("%s: not a valid OID"), orig_head);
-
- if (run_git_checkout(r, opts, oid_to_hex(onto), action)) {
- apply_autostash(opts);
+ const struct object_id *orig_head)
+{
+ struct reset_head_opts ropts = {
+ .oid = onto,
+ .orig_head = orig_head,
+ .flags = RESET_HEAD_DETACH | RESET_ORIG_HEAD |
+ RESET_HEAD_RUN_POST_CHECKOUT_HOOK,
+ .head_msg = reflog_message(opts, "start", "checkout %s",
+ onto_name),
+ .default_reflog_action = sequencer_reflog_action(opts)
+ };
+ if (reset_head(r, &ropts)) {
+ apply_autostash(rebase_path_autostash());
sequencer_remove_state(opts);
return error(_("could not detach HEAD"));
}
- return update_ref(NULL, "ORIG_HEAD", &oid, NULL, 0, UPDATE_REFS_MSG_ON_ERR);
+ return 0;
}
static int stopped_at_head(struct repository *r)
@@ -3754,9 +4671,9 @@ static int stopped_at_head(struct repository *r)
struct commit *commit;
struct commit_message message;
- if (get_oid("HEAD", &head) ||
+ if (repo_get_oid(r, "HEAD", &head) ||
!(commit = lookup_commit(r, &head)) ||
- parse_commit(commit) || get_message(commit, &message))
+ repo_parse_commit(r, commit) || get_message(commit, &message))
fprintf(stderr, _("Stopped at HEAD\n"));
else {
fprintf(stderr, _("Stopped at %s\n"), message.label);
@@ -3766,6 +4683,30 @@ static int stopped_at_head(struct repository *r)
}
+static int reread_todo_if_changed(struct repository *r,
+ struct todo_list *todo_list,
+ struct replay_opts *opts)
+{
+ int offset;
+ struct strbuf buf = STRBUF_INIT;
+
+ if (strbuf_read_file_or_whine(&buf, get_todo_path(opts)) < 0)
+ return -1;
+ offset = get_item_line_offset(todo_list, todo_list->current + 1);
+ if (buf.len != todo_list->buf.len - offset ||
+ memcmp(buf.buf, todo_list->buf.buf + offset, buf.len)) {
+ /* Reread the todo file if it has changed. */
+ todo_list_release(todo_list);
+ if (read_populate_todo(r, todo_list, opts))
+ return -1; /* message was printed */
+ /* `current` will be incremented on return */
+ todo_list->current = -1;
+ }
+ strbuf_release(&buf);
+
+ return 0;
+}
+
static const char rescheduled_advice[] =
N_("Could not execute the todo command\n"
"\n"
@@ -3777,25 +4718,94 @@ N_("Could not execute the todo command\n"
" git rebase --edit-todo\n"
" git rebase --continue\n");
+static int pick_one_commit(struct repository *r,
+ struct todo_list *todo_list,
+ struct replay_opts *opts,
+ int *check_todo, int* reschedule)
+{
+ int res;
+ struct todo_item *item = todo_list->items + todo_list->current;
+ const char *arg = todo_item_get_arg(todo_list, item);
+ if (is_rebase_i(opts))
+ opts->reflog_message = reflog_message(
+ opts, command_to_string(item->command), NULL);
+
+ res = do_pick_commit(r, item, opts, is_final_fixup(todo_list),
+ check_todo);
+ if (is_rebase_i(opts) && res < 0) {
+ /* Reschedule */
+ *reschedule = 1;
+ return -1;
+ }
+ if (item->command == TODO_EDIT) {
+ struct commit *commit = item->commit;
+ if (!res) {
+ if (!opts->verbose)
+ term_clear_line();
+ fprintf(stderr, _("Stopped at %s... %.*s\n"),
+ short_commit_name(r, commit), item->arg_len, arg);
+ }
+ return error_with_patch(r, commit,
+ arg, item->arg_len, opts, res, !res);
+ }
+ if (is_rebase_i(opts) && !res)
+ record_in_rewritten(&item->commit->object.oid,
+ peek_command(todo_list, 1));
+ if (res && is_fixup(item->command)) {
+ if (res == 1)
+ intend_to_amend();
+ return error_failed_squash(r, item->commit, opts,
+ item->arg_len, arg);
+ } else if (res && is_rebase_i(opts) && item->commit) {
+ int to_amend = 0;
+ struct object_id oid;
+
+ /*
+ * If we are rewording and have either
+ * fast-forwarded already, or are about to
+ * create a new root commit, we want to amend,
+ * otherwise we do not.
+ */
+ if (item->command == TODO_REWORD &&
+ !repo_get_oid(r, "HEAD", &oid) &&
+ (oideq(&item->commit->object.oid, &oid) ||
+ (opts->have_squash_onto &&
+ oideq(&opts->squash_onto, &oid))))
+ to_amend = 1;
+
+ return res | error_with_patch(r, item->commit,
+ arg, item->arg_len, opts,
+ res, to_amend);
+ }
+ return res;
+}
+
static int pick_commits(struct repository *r,
struct todo_list *todo_list,
struct replay_opts *opts)
{
int res = 0, reschedule = 0;
- setenv(GIT_REFLOG_ACTION, action_name(opts), 0);
+ opts->reflog_message = sequencer_reflog_action(opts);
if (opts->allow_ff)
assert(!(opts->signoff || opts->no_commit ||
- opts->record_origin || opts->edit));
+ opts->record_origin || should_edit(opts) ||
+ opts->committer_date_is_author_date ||
+ opts->ignore_date));
if (read_and_refresh_cache(r, opts))
return -1;
+ unlink(rebase_path_message());
+ unlink(rebase_path_stopped_sha());
+ unlink(rebase_path_amend());
+ unlink(rebase_path_patch());
+
while (todo_list->current < todo_list->nr) {
struct todo_item *item = todo_list->items + todo_list->current;
const char *arg = todo_item_get_arg(todo_list, item);
int check_todo = 0;
- if (save_todo(todo_list, opts))
+ if (save_todo(todo_list, opts, reschedule))
return -1;
if (is_rebase_i(opts)) {
if (item->command != TODO_COMMENT) {
@@ -3808,17 +4818,17 @@ static int pick_commits(struct repository *r,
fclose(f);
}
if (!opts->quiet)
- fprintf(stderr, "Rebasing (%d/%d)%s",
+ fprintf(stderr, _("Rebasing (%d/%d)%s"),
todo_list->done_nr,
todo_list->total_nr,
opts->verbose ? "\n" : "\r");
}
- unlink(rebase_path_message());
unlink(rebase_path_author_script());
- unlink(rebase_path_stopped_sha());
- unlink(rebase_path_amend());
unlink(git_path_merge_head(r));
- delete_ref(NULL, "REBASE_HEAD", NULL, REF_NO_DEREF);
+ refs_delete_ref(get_main_ref_store(r), "", "AUTO_MERGE",
+ NULL, REF_NO_DEREF);
+ refs_delete_ref(get_main_ref_store(r), "", "REBASE_HEAD",
+ NULL, REF_NO_DEREF);
if (item->command == TODO_BREAK) {
if (!opts->verbose)
@@ -3827,66 +4837,10 @@ static int pick_commits(struct repository *r,
}
}
if (item->command <= TODO_SQUASH) {
- if (is_rebase_i(opts))
- setenv("GIT_REFLOG_ACTION", reflog_message(opts,
- command_to_string(item->command), NULL),
- 1);
- res = do_pick_commit(r, item->command, item->commit,
- opts, is_final_fixup(todo_list),
- &check_todo);
- if (is_rebase_i(opts) && res < 0) {
- /* Reschedule */
- advise(_(rescheduled_advice),
- get_item_line_length(todo_list,
- todo_list->current),
- get_item_line(todo_list,
- todo_list->current));
- todo_list->current--;
- if (save_todo(todo_list, opts))
- return -1;
- }
- if (item->command == TODO_EDIT) {
- struct commit *commit = item->commit;
- if (!res) {
- if (!opts->verbose)
- term_clear_line();
- fprintf(stderr,
- _("Stopped at %s... %.*s\n"),
- short_commit_name(commit),
- item->arg_len, arg);
- }
- return error_with_patch(r, commit,
- arg, item->arg_len, opts, res, !res);
- }
- if (is_rebase_i(opts) && !res)
- record_in_rewritten(&item->commit->object.oid,
- peek_command(todo_list, 1));
- if (res && is_fixup(item->command)) {
- if (res == 1)
- intend_to_amend();
- return error_failed_squash(r, item->commit, opts,
- item->arg_len, arg);
- } else if (res && is_rebase_i(opts) && item->commit) {
- int to_amend = 0;
- struct object_id oid;
-
- /*
- * If we are rewording and have either
- * fast-forwarded already, or are about to
- * create a new root commit, we want to amend,
- * otherwise we do not.
- */
- if (item->command == TODO_REWORD &&
- !get_oid("HEAD", &oid) &&
- (oideq(&item->commit->object.oid, &oid) ||
- (opts->have_squash_onto &&
- oideq(&opts->squash_onto, &oid))))
- to_amend = 1;
-
- return res | error_with_patch(r, item->commit,
- arg, item->arg_len, opts,
- res, to_amend);
- }
+ res = pick_one_commit(r, todo_list, opts, &check_todo,
+ &reschedule);
+ if (!res && item->command == TODO_EDIT)
+ return 0;
} else if (item->command == TODO_EXEC) {
char *end_of_arg = (char *)(arg + item->arg_len);
int saved = *end_of_arg;
@@ -3909,9 +4863,8 @@ static int pick_commits(struct repository *r,
if ((res = do_reset(r, arg, item->arg_len, opts)))
reschedule = 1;
} else if (item->command == TODO_MERGE) {
- if ((res = do_merge(r, item->commit,
- arg, item->arg_len,
- item->flags, opts)) < 0)
+ if ((res = do_merge(r, item->commit, arg, item->arg_len,
+ item->flags, &check_todo, opts)) < 0)
reschedule = 1;
else if (item->commit)
record_in_rewritten(&item->commit->object.oid,
@@ -3921,6 +4874,12 @@ static int pick_commits(struct repository *r,
return error_with_patch(r, item->commit,
arg, item->arg_len,
opts, res, 0);
+ } else if (item->command == TODO_UPDATE_REF) {
+ struct strbuf ref = STRBUF_INIT;
+ strbuf_add(&ref, arg, item->arg_len);
+ if ((res = do_update_ref(r, ref.buf)))
+ reschedule = 1;
+ strbuf_release(&ref);
} else if (!is_noop(item->command))
return error(_("unknown command %d"), item->command);
@@ -3929,50 +4888,32 @@ static int pick_commits(struct repository *r,
get_item_line_length(todo_list,
todo_list->current),
get_item_line(todo_list, todo_list->current));
- todo_list->current--;
- if (save_todo(todo_list, opts))
+ if (save_todo(todo_list, opts, reschedule))
return -1;
if (item->commit)
- return error_with_patch(r,
- item->commit,
- arg, item->arg_len,
- opts, res, 0);
- } else if (is_rebase_i(opts) && check_todo && !res) {
- struct stat st;
-
- if (stat(get_todo_path(opts), &st)) {
- res = error_errno(_("could not stat '%s'"),
- get_todo_path(opts));
- } else if (match_stat_data(&todo_list->stat, &st)) {
- /* Reread the todo file if it has changed. */
- todo_list_release(todo_list);
- if (read_populate_todo(r, todo_list, opts))
- res = -1; /* message was printed */
- /* `current` will be incremented below */
- todo_list->current = -1;
- }
+ write_rebase_head(&item->commit->object.oid);
+ } else if (is_rebase_i(opts) && check_todo && !res &&
+ reread_todo_if_changed(r, todo_list, opts)) {
+ return -1;
}
- todo_list->current++;
if (res)
return res;
+
+ todo_list->current++;
}
if (is_rebase_i(opts)) {
struct strbuf head_ref = STRBUF_INIT, buf = STRBUF_INIT;
struct stat st;
- /* Stopped in the middle, as planned? */
- if (todo_list->current < todo_list->nr)
- return 0;
-
if (read_oneliner(&head_ref, rebase_path_head_name(), 0) &&
starts_with(head_ref.buf, "refs/")) {
const char *msg;
struct object_id head, orig;
int res;
- if (get_oid("HEAD", &head)) {
+ if (repo_get_oid(r, "HEAD", &head)) {
res = error(_("cannot read HEAD"));
cleanup_head_ref:
strbuf_release(&head_ref);
@@ -4019,53 +4960,47 @@ cleanup_head_ref:
log_tree_opt.disable_stdin = 1;
if (read_oneliner(&buf, rebase_path_orig_head(), 0) &&
- !get_oid(buf.buf, &orig) &&
- !get_oid("HEAD", &head)) {
+ !repo_get_oid(r, buf.buf, &orig) &&
+ !repo_get_oid(r, "HEAD", &head)) {
diff_tree_oid(&orig, &head, "",
&log_tree_opt.diffopt);
log_tree_diff_flush(&log_tree_opt);
}
+ release_revisions(&log_tree_opt);
}
flush_rewritten_pending();
if (!stat(rebase_path_rewritten_list(), &st) &&
st.st_size > 0) {
struct child_process child = CHILD_PROCESS_INIT;
- const char *post_rewrite_hook =
- find_hook("post-rewrite");
+ struct run_hooks_opt hook_opt = RUN_HOOKS_OPT_INIT;
child.in = open(rebase_path_rewritten_list(), O_RDONLY);
child.git_cmd = 1;
- argv_array_push(&child.args, "notes");
- argv_array_push(&child.args, "copy");
- argv_array_push(&child.args, "--for-rewrite=rebase");
+ strvec_push(&child.args, "notes");
+ strvec_push(&child.args, "copy");
+ strvec_push(&child.args, "--for-rewrite=rebase");
/* we don't care if this copying failed */
run_command(&child);
- if (post_rewrite_hook) {
- struct child_process hook = CHILD_PROCESS_INIT;
-
- hook.in = open(rebase_path_rewritten_list(),
- O_RDONLY);
- hook.stdout_to_stderr = 1;
- hook.trace2_hook_name = "post-rewrite";
- argv_array_push(&hook.args, post_rewrite_hook);
- argv_array_push(&hook.args, "rebase");
- /* we don't care if this hook failed */
- run_command(&hook);
- }
+ hook_opt.path_to_stdin = rebase_path_rewritten_list();
+ strvec_push(&hook_opt.args, "rebase");
+ run_hooks_opt("post-rewrite", &hook_opt);
}
- apply_autostash(opts);
+ apply_autostash(rebase_path_autostash());
if (!opts->quiet) {
if (!opts->verbose)
term_clear_line();
fprintf(stderr,
- "Successfully rebased and updated %s.\n",
+ _("Successfully rebased and updated %s.\n"),
head_ref.buf);
}
strbuf_release(&buf);
strbuf_release(&head_ref);
+
+ if (do_update_refs(r, opts->quiet))
+ return -1;
}
/*
@@ -4075,14 +5010,31 @@ cleanup_head_ref:
return sequencer_remove_state(opts);
}
-static int continue_single_pick(struct repository *r)
+static int continue_single_pick(struct repository *r, struct replay_opts *opts)
{
- const char *argv[] = { "commit", NULL };
+ struct child_process cmd = CHILD_PROCESS_INIT;
- if (!file_exists(git_path_cherry_pick_head(r)) &&
- !file_exists(git_path_revert_head(r)))
+ if (!refs_ref_exists(get_main_ref_store(r), "CHERRY_PICK_HEAD") &&
+ !refs_ref_exists(get_main_ref_store(r), "REVERT_HEAD"))
return error(_("no cherry-pick or revert in progress"));
- return run_command_v_opt(argv, RUN_GIT_CMD);
+
+ cmd.git_cmd = 1;
+ strvec_push(&cmd.args, "commit");
+
+ /*
+ * continue_single_pick() handles the case of recovering from a
+ * conflict. should_edit() doesn't handle that case; for a conflict,
+ * we want to edit if the user asked for it, or if they didn't specify
+ * and stdin is a tty.
+ */
+ if (!opts->edit || (opts->edit < 0 && !isatty(0)))
+ /*
+ * Include --cleanup=strip as well because we don't want the
+ * "# Conflicts:" messages.
+ */
+ strvec_pushl(&cmd.args, "--no-edit", "--cleanup=strip", NULL);
+
+ return run_command(&cmd);
}
static int commit_staged_changes(struct repository *r,
@@ -4097,11 +5049,16 @@ static int commit_staged_changes(struct repository *r,
is_clean = !has_uncommitted_changes(r, 0);
+ if (!is_clean && !file_exists(rebase_path_message())) {
+ const char *gpg_opt = gpg_sign_opt_quoted(opts);
+
+ return error(_(staged_changes_advice), gpg_opt, gpg_opt);
+ }
if (file_exists(rebase_path_amend())) {
struct strbuf rev = STRBUF_INIT;
struct object_id head, to_amend;
- if (get_oid("HEAD", &head))
+ if (repo_get_oid(r, "HEAD", &head))
return error(_("cannot amend non-existing commit"));
if (!read_oneliner(&rev, rebase_path_amend(), 0))
return error(_("invalid file: '%s'"), rebase_path_amend());
@@ -4176,18 +5133,31 @@ static int commit_staged_changes(struct repository *r,
* We need to update the squash message to skip
* the latest commit message.
*/
+ int res = 0;
struct commit *commit;
+ const char *msg;
const char *path = rebase_path_squash_msg();
const char *encoding = get_commit_output_encoding();
- if (parse_head(r, &commit) ||
- !(p = logmsg_reencode(commit, NULL, encoding)) ||
- write_message(p, strlen(p), path, 0)) {
- unuse_commit_buffer(commit, p);
- return error(_("could not write file: "
+ if (parse_head(r, &commit))
+ return error(_("could not parse HEAD"));
+
+ p = repo_logmsg_reencode(r, commit, NULL, encoding);
+ if (!p) {
+ res = error(_("could not parse commit %s"),
+ oid_to_hex(&commit->object.oid));
+ goto unuse_commit_buffer;
+ }
+ find_commit_subject(p, &msg);
+ if (write_message(msg, strlen(msg), path, 0)) {
+ res = error(_("could not write file: "
"'%s'"), path);
+ goto unuse_commit_buffer;
}
- unuse_commit_buffer(commit, p);
+ unuse_commit_buffer:
+ repo_unuse_commit_buffer(r, commit, p);
+ if (res)
+ return res;
}
}
@@ -4196,19 +5166,25 @@ static int commit_staged_changes(struct repository *r,
}
if (is_clean) {
- const char *cherry_pick_head = git_path_cherry_pick_head(r);
-
- if (file_exists(cherry_pick_head) && unlink(cherry_pick_head))
+ if (refs_ref_exists(get_main_ref_store(r),
+ "CHERRY_PICK_HEAD") &&
+ refs_delete_ref(get_main_ref_store(r), "",
+ "CHERRY_PICK_HEAD", NULL, REF_NO_DEREF))
return error(_("could not remove CHERRY_PICK_HEAD"));
+ if (unlink(git_path_merge_msg(r)) && errno != ENOENT)
+ return error_errno(_("could not remove '%s'"),
+ git_path_merge_msg(r));
if (!final_fixup)
return 0;
}
- if (run_git_commit(r, final_fixup ? NULL : rebase_path_message(),
+ if (run_git_commit(final_fixup ? NULL : rebase_path_message(),
opts, flags))
return error(_("could not commit staged changes."));
unlink(rebase_path_amend());
unlink(git_path_merge_head(r));
+ refs_delete_ref(get_main_ref_store(r), "", "AUTO_MERGE",
+ NULL, REF_NO_DEREF);
if (final_fixup) {
unlink(rebase_path_fixup_msg());
unlink(rebase_path_squash_msg());
@@ -4238,20 +5214,30 @@ int sequencer_continue(struct repository *r, struct replay_opts *opts)
if (is_rebase_i(opts)) {
if ((res = read_populate_todo(r, &todo_list, opts)))
goto release_todo_list;
+
+ if (file_exists(rebase_path_dropped())) {
+ if ((res = todo_list_check_against_backup(r, &todo_list)))
+ goto release_todo_list;
+
+ unlink(rebase_path_dropped());
+ }
+
+ opts->reflog_message = reflog_message(opts, "continue", NULL);
if (commit_staged_changes(r, opts, &todo_list)) {
res = -1;
goto release_todo_list;
}
} else if (!file_exists(get_todo_path(opts)))
- return continue_single_pick(r);
+ return continue_single_pick(r, opts);
else if ((res = read_populate_todo(r, &todo_list, opts)))
goto release_todo_list;
if (!is_rebase_i(opts)) {
/* Verify that the conflict has been resolved */
- if (file_exists(git_path_cherry_pick_head(r)) ||
- file_exists(git_path_revert_head(r))) {
- res = continue_single_pick(r);
+ if (refs_ref_exists(get_main_ref_store(r),
+ "CHERRY_PICK_HEAD") ||
+ refs_ref_exists(get_main_ref_store(r), "REVERT_HEAD")) {
+ res = continue_single_pick(r, opts);
if (res)
goto release_todo_list;
}
@@ -4264,8 +5250,9 @@ int sequencer_continue(struct repository *r, struct replay_opts *opts)
struct strbuf buf = STRBUF_INIT;
struct object_id oid;
- if (read_oneliner(&buf, rebase_path_stopped_sha(), 1) &&
- !get_oid_committish(buf.buf, &oid))
+ if (read_oneliner(&buf, rebase_path_stopped_sha(),
+ READ_ONELINER_SKIP_IF_EMPTY) &&
+ !get_oid_hex(buf.buf, &oid))
record_in_rewritten(&oid, peek_command(&todo_list, 0));
strbuf_release(&buf);
}
@@ -4281,11 +5268,14 @@ static int single_pick(struct repository *r,
struct replay_opts *opts)
{
int check_todo;
+ struct todo_item item;
- setenv(GIT_REFLOG_ACTION, action_name(opts), 0);
- return do_pick_commit(r, opts->action == REPLAY_PICK ?
- TODO_PICK : TODO_REVERT, cmit, opts, 0,
- &check_todo);
+ item.command = opts->action == REPLAY_PICK ?
+ TODO_PICK : TODO_REVERT;
+ item.commit = cmit;
+
+ opts->reflog_message = sequencer_reflog_action(opts);
+ return do_pick_commit(r, &item, opts, 0, &check_todo);
}
int sequencer_pick_revisions(struct repository *r,
@@ -4307,7 +5297,7 @@ int sequencer_pick_revisions(struct repository *r,
if (!strlen(name))
continue;
- if (!get_oid(name, &oid)) {
+ if (!repo_get_oid(r, name, &oid)) {
if (!lookup_commit_reference_gently(r, &oid, 1)) {
enum object_type type = oid_object_info(r,
&oid,
@@ -4350,7 +5340,7 @@ int sequencer_pick_revisions(struct repository *r,
if (walk_revs_populate_todo(&todo_list, opts) ||
create_seq_dir(r) < 0)
return -1;
- if (get_oid("HEAD", &oid) && (opts->action == REPLAY_REVERT))
+ if (repo_get_oid(r, "HEAD", &oid) && (opts->action == REPLAY_REVERT))
return error(_("can't revert as initial commit"));
if (save_head(oid_to_hex(&oid)))
return -1;
@@ -4427,7 +5417,8 @@ struct labels_entry {
char label[FLEX_ARRAY];
};
-static int labels_cmp(const void *fndata, const struct hashmap_entry *eptr,
+static int labels_cmp(const void *fndata UNUSED,
+ const struct hashmap_entry *eptr,
const struct hashmap_entry *entry_or_key, const void *key)
{
const struct labels_entry *a, *b;
@@ -4447,6 +5438,7 @@ struct label_state {
struct oidmap commit2label;
struct hashmap labels;
struct strbuf buf;
+ int max_label_length;
};
static const char *label_oid(struct object_id *oid, const char *label,
@@ -4465,7 +5457,7 @@ static const char *label_oid(struct object_id *oid, const char *label,
* For "uninteresting" commits, i.e. commits that are not to be
* rebased, and which can therefore not be labeled, we use a unique
* abbreviation of the commit name. This is slightly more complicated
- * than calling find_unique_abbrev() because we also need to make
+ * than calling repo_find_unique_abbrev() because we also need to make
* sure that the abbreviation does not conflict with any other
* label.
*
@@ -4481,7 +5473,8 @@ static const char *label_oid(struct object_id *oid, const char *label,
strbuf_grow(&state->buf, GIT_MAX_HEXSZ);
label = p = state->buf.buf;
- find_unique_abbrev_r(p, oid, default_abbrev);
+ repo_find_unique_abbrev_r(the_repository, p, oid,
+ default_abbrev);
/*
* We may need to extend the abbreviated hash so that there is
@@ -4502,6 +5495,8 @@ static const char *label_oid(struct object_id *oid, const char *label,
}
} else {
struct strbuf *buf = &state->buf;
+ int label_is_utf8 = 1; /* start with this assumption */
+ size_t max_len = buf->len + state->max_label_length;
/*
* Sanitize labels by replacing non-alpha-numeric characters
@@ -4510,14 +5505,34 @@ static const char *label_oid(struct object_id *oid, const char *label,
*
* Note that we retain non-ASCII UTF-8 characters (identified
* via the most significant bit). They should be all acceptable
- * in file names. We do not validate the UTF-8 here, that's not
- * the job of this function.
+ * in file names.
+ *
+ * As we will use the labels as names of (loose) refs, it is
+ * vital that the name not be longer than the maximum component
+ * size of the file system (`NAME_MAX`). We are careful to
+ * truncate the label accordingly, allowing for the `.lock`
+ * suffix and for the label to be UTF-8 encoded (i.e. we avoid
+ * truncating in the middle of a character).
*/
- for (; *label; label++)
- if ((*label & 0x80) || isalnum(*label))
+ for (; *label && buf->len + 1 < max_len; label++)
+ if (isalnum(*label) ||
+ (!label_is_utf8 && (*label & 0x80)))
strbuf_addch(buf, *label);
+ else if (*label & 0x80) {
+ const char *p = label;
+
+ utf8_width(&p, NULL);
+ if (p) {
+ if (buf->len + (p - label) > max_len)
+ break;
+ strbuf_add(buf, label, p - label);
+ label = p - 1;
+ } else {
+ label_is_utf8 = 0;
+ strbuf_addch(buf, *label);
+ }
/* avoid leading dash and double-dashes */
- else if (buf->len && buf->buf[buf->len - 1] != '-')
+ } else if (buf->len && buf->buf[buf->len - 1] != '-')
strbuf_addch(buf, '-');
if (!buf->len) {
strbuf_addstr(buf, "rev-");
@@ -4569,6 +5584,7 @@ static int make_script_with_merges(struct pretty_print_context *pp,
int keep_empty = flags & TODO_LIST_KEEP_EMPTY;
int rebase_cousins = flags & TODO_LIST_REBASE_COUSINS;
int root_with_onto = flags & TODO_LIST_ROOT_WITH_ONTO;
+ int skipped_commit = 0;
struct strbuf buf = STRBUF_INIT, oneline = STRBUF_INIT;
struct strbuf label = STRBUF_INIT;
struct commit_list *commits = NULL, **tail = &commits, *iter;
@@ -4578,7 +5594,8 @@ static int make_script_with_merges(struct pretty_print_context *pp,
struct string_entry *entry;
struct oidset interesting = OIDSET_INIT, child_seen = OIDSET_INIT,
shown = OIDSET_INIT;
- struct label_state state = { OIDMAP_INIT, { NULL }, STRBUF_INIT };
+ struct label_state state =
+ { OIDMAP_INIT, { NULL }, STRBUF_INIT, GIT_MAX_LABEL_LENGTH };
int abbr = flags & TODO_LIST_ABBREVIATE_CMDS;
const char *cmd_pick = abbr ? "p" : "pick",
@@ -4586,6 +5603,8 @@ static int make_script_with_merges(struct pretty_print_context *pp,
*cmd_reset = abbr ? "t" : "reset",
*cmd_merge = abbr ? "m" : "merge";
+ git_config_get_int("rebase.maxlabellength", &state.max_label_length);
+
oidmap_init(&commit2todo, 0);
oidmap_init(&state.commit2label, 0);
hashmap_init(&state.labels, labels_cmp, NULL, 0);
@@ -4619,7 +5638,14 @@ static int make_script_with_merges(struct pretty_print_context *pp,
oidset_insert(&interesting, &commit->object.oid);
is_empty = is_original_commit_empty(commit);
- if (!is_empty && (commit->object.flags & PATCHSAME))
+ if (!is_empty && (commit->object.flags & PATCHSAME)) {
+ if (flags & TODO_LIST_WARN_SKIPPED_CHERRY_PICKS)
+ warning(_("skipped previously applied commit %s"),
+ short_commit_name(the_repository, commit));
+ skipped_commit = 1;
+ continue;
+ }
+ if (is_empty && !keep_empty)
continue;
strbuf_reset(&oneline);
@@ -4629,11 +5655,12 @@ static int make_script_with_merges(struct pretty_print_context *pp,
if (!to_merge) {
/* non-merge commit: easy case */
strbuf_reset(&buf);
- if (!keep_empty && is_empty)
- strbuf_addf(&buf, "%c ", comment_line_char);
strbuf_addf(&buf, "%s %s %s", cmd_pick,
oid_to_hex(&commit->object.oid),
oneline.buf);
+ if (is_empty)
+ strbuf_addf(&buf, " %c empty",
+ comment_line_char);
FLEX_ALLOC_STR(entry, string, buf.buf);
oidcpy(&entry->entry.oid, &commit->object.oid);
@@ -4681,6 +5708,9 @@ static int make_script_with_merges(struct pretty_print_context *pp,
oidcpy(&entry->entry.oid, &commit->object.oid);
oidmap_put(&commit2todo, entry);
}
+ if (skipped_commit)
+ advise_if_enabled(ADVICE_SKIPPED_CHERRY_PICKS,
+ _("use --reapply-cherry-picks to include skipped commits"));
/*
* Second phase:
@@ -4784,7 +5814,7 @@ static int make_script_with_merges(struct pretty_print_context *pp,
oidmap_free(&commit2todo, 1);
oidmap_free(&state.commit2label, 1);
- hashmap_free_entries(&state.labels, struct labels_entry, entry);
+ hashmap_clear_and_free(&state.labels, struct labels_entry, entry);
strbuf_release(&state.buf);
return 0;
@@ -4800,12 +5830,15 @@ int sequencer_make_script(struct repository *r, struct strbuf *out, int argc,
int keep_empty = flags & TODO_LIST_KEEP_EMPTY;
const char *insn = flags & TODO_LIST_ABBREVIATE_CMDS ? "p" : "pick";
int rebase_merges = flags & TODO_LIST_REBASE_MERGES;
+ int reapply_cherry_picks = flags & TODO_LIST_REAPPLY_CHERRY_PICKS;
+ int skipped_commit = 0;
+ int ret = 0;
repo_init_revisions(r, &revs, NULL);
revs.verbose_header = 1;
if (!rebase_merges)
revs.max_parents = 1;
- revs.cherry_mark = 1;
+ revs.cherry_mark = !reapply_cherry_picks;
revs.limited = 1;
revs.reverse = 1;
revs.right_only = 1;
@@ -4823,43 +5856,61 @@ int sequencer_make_script(struct repository *r, struct strbuf *out, int argc,
pp.fmt = revs.commit_format;
pp.output_encoding = get_log_output_encoding();
- if (setup_revisions(argc, argv, &revs, NULL) > 1)
- return error(_("make_script: unhandled options"));
+ if (setup_revisions(argc, argv, &revs, NULL) > 1) {
+ ret = error(_("make_script: unhandled options"));
+ goto cleanup;
+ }
- if (prepare_revision_walk(&revs) < 0)
- return error(_("make_script: error preparing revisions"));
+ if (prepare_revision_walk(&revs) < 0) {
+ ret = error(_("make_script: error preparing revisions"));
+ goto cleanup;
+ }
- if (rebase_merges)
- return make_script_with_merges(&pp, &revs, out, flags);
+ if (rebase_merges) {
+ ret = make_script_with_merges(&pp, &revs, out, flags);
+ goto cleanup;
+ }
while ((commit = get_revision(&revs))) {
- int is_empty = is_original_commit_empty(commit);
+ int is_empty = is_original_commit_empty(commit);
- if (!is_empty && (commit->object.flags & PATCHSAME))
+ if (!is_empty && (commit->object.flags & PATCHSAME)) {
+ if (flags & TODO_LIST_WARN_SKIPPED_CHERRY_PICKS)
+ warning(_("skipped previously applied commit %s"),
+ short_commit_name(r, commit));
+ skipped_commit = 1;
+ continue;
+ }
+ if (is_empty && !keep_empty)
continue;
- if (!keep_empty && is_empty)
- strbuf_addf(out, "%c ", comment_line_char);
strbuf_addf(out, "%s %s ", insn,
oid_to_hex(&commit->object.oid));
pretty_print_commit(&pp, commit, out);
+ if (is_empty)
+ strbuf_addf(out, " %c empty", comment_line_char);
strbuf_addch(out, '\n');
}
- return 0;
+ if (skipped_commit)
+ advise_if_enabled(ADVICE_SKIPPED_CHERRY_PICKS,
+ _("use --reapply-cherry-picks to include skipped commits"));
+cleanup:
+ release_revisions(&revs);
+ return ret;
}
/*
* Add commands after pick and (series of) squash/fixup commands
* in the todo list.
*/
-void todo_list_add_exec_commands(struct todo_list *todo_list,
- struct string_list *commands)
+static void todo_list_add_exec_commands(struct todo_list *todo_list,
+ struct string_list *commands)
{
struct strbuf *buf = &todo_list->buf;
size_t base_offset = buf->len;
int i, insert, nr = 0, alloc = 0;
struct todo_item *items = NULL, *base_items = NULL;
- base_items = xcalloc(commands->nr, sizeof(struct todo_item));
+ CALLOC_ARRAY(base_items, commands->nr);
for (i = 0; i < commands->nr; i++) {
size_t command_len = strlen(commands->items[i].string);
@@ -4868,8 +5919,8 @@ void todo_list_add_exec_commands(struct todo_list *todo_list,
base_items[i].command = TODO_EXEC;
base_items[i].offset_in_buf = base_offset;
- base_items[i].arg_offset = base_offset + strlen("exec ");
- base_items[i].arg_len = command_len - strlen("exec ");
+ base_items[i].arg_offset = base_offset;
+ base_items[i].arg_len = command_len;
base_offset += command_len + 1;
}
@@ -4905,7 +5956,7 @@ void todo_list_add_exec_commands(struct todo_list *todo_list,
}
/* insert or append final <commands> */
- if (insert || nr == todo_list->nr) {
+ if (insert) {
ALLOC_GROW(items, nr + commands->nr, alloc);
COPY_ARRAY(items + nr, base_items, commands->nr);
nr += commands->nr;
@@ -4918,7 +5969,8 @@ void todo_list_add_exec_commands(struct todo_list *todo_list,
todo_list->alloc = alloc;
}
-static void todo_list_to_strbuf(struct repository *r, struct todo_list *todo_list,
+static void todo_list_to_strbuf(struct repository *r,
+ struct todo_list *todo_list,
struct strbuf *buf, int num, unsigned flags)
{
struct todo_item *item;
@@ -4928,6 +5980,8 @@ static void todo_list_to_strbuf(struct repository *r, struct todo_list *todo_lis
max = num;
for (item = todo_list->items, i = 0; i < max; i++, item++) {
+ char cmd;
+
/* if the item is not a command write it and continue */
if (item->command >= TODO_COMMENT) {
strbuf_addf(buf, "%.*s\n", item->arg_len,
@@ -4936,17 +5990,26 @@ static void todo_list_to_strbuf(struct repository *r, struct todo_list *todo_lis
}
/* add command to the buffer */
- if (flags & TODO_LIST_ABBREVIATE_CMDS)
- strbuf_addch(buf, command_to_char(item->command));
+ cmd = command_to_char(item->command);
+ if ((flags & TODO_LIST_ABBREVIATE_CMDS) && cmd)
+ strbuf_addch(buf, cmd);
else
strbuf_addstr(buf, command_to_string(item->command));
/* add commit id */
if (item->commit) {
const char *oid = flags & TODO_LIST_SHORTEN_IDS ?
- short_commit_name(item->commit) :
+ short_commit_name(r, item->commit) :
oid_to_hex(&item->commit->object.oid);
+ if (item->command == TODO_FIXUP) {
+ if (item->flags & TODO_EDIT_FIXUP_MSG)
+ strbuf_addstr(buf, " -c");
+ else if (item->flags & TODO_REPLACE_FIXUP_MSG) {
+ strbuf_addstr(buf, " -C");
+ }
+ }
+
if (item->command == TODO_MERGE) {
if (item->flags & TODO_EDIT_MERGE_MSG)
strbuf_addstr(buf, " -c");
@@ -4975,7 +6038,7 @@ int todo_list_write_to_file(struct repository *r, struct todo_list *todo_list,
todo_list_to_strbuf(r, todo_list, &buf, num, flags);
if (flags & TODO_LIST_APPEND_TODO_HELP)
- append_todo_help(flags & TODO_LIST_KEEP_EMPTY, count_commands(todo_list),
+ append_todo_help(count_commands(todo_list),
shortrevisions, shortonto, &buf);
res = write_message(buf.buf, buf.len, file, 0);
@@ -4984,41 +6047,6 @@ int todo_list_write_to_file(struct repository *r, struct todo_list *todo_list,
return res;
}
-static const char edit_todo_list_advice[] =
-N_("You can fix this with 'git rebase --edit-todo' "
-"and then run 'git rebase --continue'.\n"
-"Or you can abort the rebase with 'git rebase"
-" --abort'.\n");
-
-int check_todo_list_from_file(struct repository *r)
-{
- struct todo_list old_todo = TODO_LIST_INIT, new_todo = TODO_LIST_INIT;
- int res = 0;
-
- if (strbuf_read_file_or_whine(&new_todo.buf, rebase_path_todo()) < 0) {
- res = -1;
- goto out;
- }
-
- if (strbuf_read_file_or_whine(&old_todo.buf, rebase_path_todo_backup()) < 0) {
- res = -1;
- goto out;
- }
-
- res = todo_list_parse_insn_buffer(r, old_todo.buf.buf, &old_todo);
- if (!res)
- res = todo_list_parse_insn_buffer(r, new_todo.buf.buf, &new_todo);
- if (!res)
- res = todo_list_check(&old_todo, &new_todo);
- if (res)
- fprintf(stderr, _(edit_todo_list_advice));
-out:
- todo_list_release(&old_todo);
- todo_list_release(&new_todo);
-
- return res;
-}
-
/* skip picking commits whose parents are unchanged */
static int skip_unnecessary_picks(struct repository *r,
struct todo_list *todo_list,
@@ -5034,7 +6062,7 @@ static int skip_unnecessary_picks(struct repository *r,
continue;
if (item->command != TODO_PICK)
break;
- if (parse_commit(item->commit)) {
+ if (repo_parse_commit(r, item->commit)) {
return error(_("could not parse commit '%s'"),
oid_to_hex(&item->commit->object.oid));
}
@@ -5067,19 +6095,146 @@ static int skip_unnecessary_picks(struct repository *r,
return 0;
}
+struct todo_add_branch_context {
+ struct todo_item *items;
+ size_t items_nr;
+ size_t items_alloc;
+ struct strbuf *buf;
+ struct commit *commit;
+ struct string_list refs_to_oids;
+};
+
+static int add_decorations_to_list(const struct commit *commit,
+ struct todo_add_branch_context *ctx)
+{
+ const struct name_decoration *decoration = get_name_decoration(&commit->object);
+ const char *head_ref = resolve_ref_unsafe("HEAD",
+ RESOLVE_REF_READING,
+ NULL,
+ NULL);
+
+ while (decoration) {
+ struct todo_item *item;
+ const char *path;
+ size_t base_offset = ctx->buf->len;
+
+ /*
+ * If the branch is the current HEAD, then it will be
+ * updated by the default rebase behavior.
+ */
+ if (head_ref && !strcmp(head_ref, decoration->name)) {
+ decoration = decoration->next;
+ continue;
+ }
+
+ ALLOC_GROW(ctx->items,
+ ctx->items_nr + 1,
+ ctx->items_alloc);
+ item = &ctx->items[ctx->items_nr];
+ memset(item, 0, sizeof(*item));
+
+ /* If the branch is checked out, then leave a comment instead. */
+ if ((path = branch_checked_out(decoration->name))) {
+ item->command = TODO_COMMENT;
+ strbuf_addf(ctx->buf, "# Ref %s checked out at '%s'\n",
+ decoration->name, path);
+ } else {
+ struct string_list_item *sti;
+ item->command = TODO_UPDATE_REF;
+ strbuf_addf(ctx->buf, "%s\n", decoration->name);
+
+ sti = string_list_insert(&ctx->refs_to_oids,
+ decoration->name);
+ sti->util = init_update_ref_record(decoration->name);
+ }
+
+ item->offset_in_buf = base_offset;
+ item->arg_offset = base_offset;
+ item->arg_len = ctx->buf->len - base_offset;
+ ctx->items_nr++;
+
+ decoration = decoration->next;
+ }
+
+ return 0;
+}
+
+/*
+ * For each 'pick' command, find out if the commit has a decoration in
+ * refs/heads/. If so, then add a 'label for-update-refs/' command.
+ */
+static int todo_list_add_update_ref_commands(struct todo_list *todo_list)
+{
+ int i, res;
+ static struct string_list decorate_refs_exclude = STRING_LIST_INIT_NODUP;
+ static struct string_list decorate_refs_exclude_config = STRING_LIST_INIT_NODUP;
+ static struct string_list decorate_refs_include = STRING_LIST_INIT_NODUP;
+ struct decoration_filter decoration_filter = {
+ .include_ref_pattern = &decorate_refs_include,
+ .exclude_ref_pattern = &decorate_refs_exclude,
+ .exclude_ref_config_pattern = &decorate_refs_exclude_config,
+ };
+ struct todo_add_branch_context ctx = {
+ .buf = &todo_list->buf,
+ .refs_to_oids = STRING_LIST_INIT_DUP,
+ };
+
+ ctx.items_alloc = 2 * todo_list->nr + 1;
+ ALLOC_ARRAY(ctx.items, ctx.items_alloc);
+
+ string_list_append(&decorate_refs_include, "refs/heads/");
+ load_ref_decorations(&decoration_filter, 0);
+
+ for (i = 0; i < todo_list->nr; ) {
+ struct todo_item *item = &todo_list->items[i];
+
+ /* insert ith item into new list */
+ ALLOC_GROW(ctx.items,
+ ctx.items_nr + 1,
+ ctx.items_alloc);
+
+ ctx.items[ctx.items_nr++] = todo_list->items[i++];
+
+ if (item->commit) {
+ ctx.commit = item->commit;
+ add_decorations_to_list(item->commit, &ctx);
+ }
+ }
+
+ res = write_update_refs_state(&ctx.refs_to_oids);
+
+ string_list_clear(&ctx.refs_to_oids, 1);
+
+ if (res) {
+ /* we failed, so clean up the new list. */
+ free(ctx.items);
+ return res;
+ }
+
+ free(todo_list->items);
+ todo_list->items = ctx.items;
+ todo_list->nr = ctx.items_nr;
+ todo_list->alloc = ctx.items_alloc;
+
+ return 0;
+}
+
int complete_action(struct repository *r, struct replay_opts *opts, unsigned flags,
const char *shortrevisions, const char *onto_name,
- struct commit *onto, const char *orig_head,
+ struct commit *onto, const struct object_id *orig_head,
struct string_list *commands, unsigned autosquash,
+ unsigned update_refs,
struct todo_list *todo_list)
{
- const char *shortonto, *todo_file = rebase_path_todo();
+ char shortonto[GIT_MAX_HEXSZ + 1];
+ const char *todo_file = rebase_path_todo();
struct todo_list new_todo = TODO_LIST_INIT;
- struct strbuf *buf = &todo_list->buf;
+ struct strbuf *buf = &todo_list->buf, buf2 = STRBUF_INIT;
struct object_id oid = onto->object.oid;
int res;
- shortonto = find_unique_abbrev(&oid, DEFAULT_ABBREV);
+ repo_find_unique_abbrev_r(r, shortonto, &oid,
+ DEFAULT_ABBREV);
if (buf->len == 0) {
struct todo_item *item = append_new_todo(todo_list);
@@ -5088,6 +6243,9 @@ int complete_action(struct repository *r, struct replay_opts *opts, unsigned fla
item->arg_len = item->arg_offset = item->flags = item->offset_in_buf = 0;
}
+ if (update_refs && todo_list_add_update_ref_commands(todo_list))
+ return -1;
+
if (autosquash && todo_list_rearrange_squash(todo_list))
return -1;
@@ -5095,7 +6253,7 @@ int complete_action(struct repository *r, struct replay_opts *opts, unsigned fla
todo_list_add_exec_commands(todo_list, commands);
if (count_commands(todo_list) == 0) {
- apply_autostash(opts);
+ apply_autostash(rebase_path_autostash());
sequencer_remove_state(opts);
return error(_("nothing to do"));
@@ -5106,27 +6264,33 @@ int complete_action(struct repository *r, struct replay_opts *opts, unsigned fla
if (res == -1)
return -1;
else if (res == -2) {
- apply_autostash(opts);
+ apply_autostash(rebase_path_autostash());
sequencer_remove_state(opts);
return -1;
} else if (res == -3) {
- apply_autostash(opts);
+ apply_autostash(rebase_path_autostash());
sequencer_remove_state(opts);
todo_list_release(&new_todo);
return error(_("nothing to do"));
- }
-
- if (todo_list_parse_insn_buffer(r, new_todo.buf.buf, &new_todo) ||
- todo_list_check(todo_list, &new_todo)) {
- fprintf(stderr, _(edit_todo_list_advice));
+ } else if (res == -4) {
checkout_onto(r, opts, onto_name, &onto->object.oid, orig_head);
todo_list_release(&new_todo);
return -1;
}
+ /* Expand the commit IDs */
+ todo_list_to_strbuf(r, &new_todo, &buf2, -1, 0);
+ strbuf_swap(&new_todo.buf, &buf2);
+ strbuf_release(&buf2);
+ /* Nothing is done yet, and we're reparsing, so let's reset the count */
+ new_todo.total_nr = 0;
+ if (todo_list_parse_insn_buffer(r, new_todo.buf.buf, &new_todo) < 0)
+ BUG("invalid todo list after expanding IDs:\n%s",
+ new_todo.buf.buf);
+
if (opts->allow_ff && skip_unnecessary_picks(r, &new_todo, &oid)) {
todo_list_release(&new_todo);
return error(_("could not skip unnecessary pick commands"));
@@ -5143,7 +6307,7 @@ int complete_action(struct repository *r, struct replay_opts *opts, unsigned fla
if (checkout_onto(r, opts, onto_name, &oid, orig_head))
goto cleanup;
- if (require_clean_work_tree(r, "rebase", "", 1, 1))
+ if (require_clean_work_tree(r, "rebase", NULL, 1, 1))
goto cleanup;
todo_list_write_total_nr(&new_todo);
@@ -5161,7 +6325,7 @@ struct subject2item_entry {
char subject[FLEX_ARRAY];
};
-static int subject2item_cmp(const void *fndata,
+static int subject2item_cmp(const void *fndata UNUSED,
const struct hashmap_entry *eptr,
const struct hashmap_entry *entry_or_key,
const void *key)
@@ -5176,6 +6340,12 @@ static int subject2item_cmp(const void *fndata,
define_commit_slab(commit_todo_item, struct todo_item *);
+static int skip_fixupish(const char *subject, const char **p) {
+ return skip_prefix(subject, "fixup! ", p) ||
+ skip_prefix(subject, "amend! ", p) ||
+ skip_prefix(subject, "squash! ", p);
+}
+
/*
* Rearrange the todo list that has both "pick commit-id msg" and "pick
* commit-id fixup!/squash! msg" in it so that the latter is put immediately
@@ -5188,7 +6358,7 @@ define_commit_slab(commit_todo_item, struct todo_item *);
int todo_list_rearrange_squash(struct todo_list *todo_list)
{
struct hashmap subject2item;
- int rearranged = 0, *next, *tail, i, nr = 0, alloc = 0;
+ int rearranged = 0, *next, *tail, i, nr = 0;
char **subjects;
struct commit_todo_item commit_todo;
struct todo_item *items = NULL;
@@ -5226,23 +6396,22 @@ int todo_list_rearrange_squash(struct todo_list *todo_list)
return error(_("the script was already rearranged."));
}
- *commit_todo_item_at(&commit_todo, item->commit) = item;
-
- parse_commit(item->commit);
- commit_buffer = logmsg_reencode(item->commit, NULL, "UTF-8");
+ repo_parse_commit(the_repository, item->commit);
+ commit_buffer = repo_logmsg_reencode(the_repository,
+ item->commit, NULL,
+ "UTF-8");
find_commit_subject(commit_buffer, &subject);
format_subject(&buf, subject, " ");
subject = subjects[i] = strbuf_detach(&buf, &subject_len);
- unuse_commit_buffer(item->commit, commit_buffer);
- if ((skip_prefix(subject, "fixup! ", &p) ||
- skip_prefix(subject, "squash! ", &p))) {
+ repo_unuse_commit_buffer(the_repository, item->commit,
+ commit_buffer);
+ if (skip_fixupish(subject, &p)) {
struct commit *commit2;
for (;;) {
while (isspace(*p))
p++;
- if (!skip_prefix(p, "fixup! ", &p) &&
- !skip_prefix(p, "squash! ", &p))
+ if (!skip_fixupish(p, &p))
break;
}
@@ -5272,13 +6441,21 @@ int todo_list_rearrange_squash(struct todo_list *todo_list)
}
if (i2 >= 0) {
rearranged = 1;
- todo_list->items[i].command =
- starts_with(subject, "fixup!") ?
- TODO_FIXUP : TODO_SQUASH;
- if (next[i2] < 0)
+ if (starts_with(subject, "fixup!")) {
+ todo_list->items[i].command = TODO_FIXUP;
+ } else if (starts_with(subject, "amend!")) {
+ todo_list->items[i].command = TODO_FIXUP;
+ todo_list->items[i].flags = TODO_REPLACE_FIXUP_MSG;
+ } else {
+ todo_list->items[i].command = TODO_SQUASH;
+ }
+ if (tail[i2] < 0) {
+ next[i] = next[i2];
next[i2] = i;
- else
+ } else {
+ next[i] = next[tail[i2]];
next[tail[i2]] = i;
+ }
tail[i2] = i;
} else if (!hashmap_get_from_hash(&subject2item,
strhash(subject), subject)) {
@@ -5288,9 +6465,13 @@ int todo_list_rearrange_squash(struct todo_list *todo_list)
strhash(entry->subject));
hashmap_put(&subject2item, &entry->entry);
}
+
+ *commit_todo_item_at(&commit_todo, item->commit) = item;
}
if (rearranged) {
+ ALLOC_ARRAY(items, todo_list->nr);
+
for (i = 0; i < todo_list->nr; i++) {
enum todo_command command = todo_list->items[i].command;
int cur = i;
@@ -5303,16 +6484,15 @@ int todo_list_rearrange_squash(struct todo_list *todo_list)
continue;
while (cur >= 0) {
- ALLOC_GROW(items, nr + 1, alloc);
items[nr++] = todo_list->items[cur];
cur = next[cur];
}
}
+ assert(nr == todo_list->nr);
+ todo_list->alloc = nr;
FREE_AND_NULL(todo_list->items);
todo_list->items = items;
- todo_list->nr = nr;
- todo_list->alloc = alloc;
}
free(next);
@@ -5320,9 +6500,81 @@ int todo_list_rearrange_squash(struct todo_list *todo_list)
for (i = 0; i < todo_list->nr; i++)
free(subjects[i]);
free(subjects);
- hashmap_free_entries(&subject2item, struct subject2item_entry, entry);
+ hashmap_clear_and_free(&subject2item, struct subject2item_entry, entry);
clear_commit_todo_item(&commit_todo);
return 0;
}
+
+int sequencer_determine_whence(struct repository *r, enum commit_whence *whence)
+{
+ if (refs_ref_exists(get_main_ref_store(r), "CHERRY_PICK_HEAD")) {
+ struct object_id cherry_pick_head, rebase_head;
+
+ if (file_exists(git_path_seq_dir()))
+ *whence = FROM_CHERRY_PICK_MULTI;
+ if (file_exists(rebase_path()) &&
+ !repo_get_oid(r, "REBASE_HEAD", &rebase_head) &&
+ !repo_get_oid(r, "CHERRY_PICK_HEAD", &cherry_pick_head) &&
+ oideq(&rebase_head, &cherry_pick_head))
+ *whence = FROM_REBASE_PICK;
+ else
+ *whence = FROM_CHERRY_PICK_SINGLE;
+
+ return 1;
+ }
+
+ return 0;
+}
+
+int sequencer_get_update_refs_state(const char *wt_dir,
+ struct string_list *refs)
+{
+ int result = 0;
+ FILE *fp = NULL;
+ struct strbuf ref = STRBUF_INIT;
+ struct strbuf hash = STRBUF_INIT;
+ struct update_ref_record *rec = NULL;
+
+ char *path = rebase_path_update_refs(wt_dir);
+
+ fp = fopen(path, "r");
+ if (!fp)
+ goto cleanup;
+
+ while (strbuf_getline(&ref, fp) != EOF) {
+ struct string_list_item *item;
+
+ CALLOC_ARRAY(rec, 1);
+
+ if (strbuf_getline(&hash, fp) == EOF ||
+ get_oid_hex(hash.buf, &rec->before)) {
+ warning(_("update-refs file at '%s' is invalid"),
+ path);
+ result = -1;
+ goto cleanup;
+ }
+
+ if (strbuf_getline(&hash, fp) == EOF ||
+ get_oid_hex(hash.buf, &rec->after)) {
+ warning(_("update-refs file at '%s' is invalid"),
+ path);
+ result = -1;
+ goto cleanup;
+ }
+
+ item = string_list_insert(refs, ref.buf);
+ item->util = rec;
+ rec = NULL;
+ }
+
+cleanup:
+ if (fp)
+ fclose(fp);
+ free(path);
+ free(rec);
+ strbuf_release(&ref);
+ strbuf_release(&hash);
+ return result;
+}