summaryrefslogtreecommitdiff
path: root/sequencer.c
diff options
context:
space:
mode:
Diffstat (limited to 'sequencer.c')
-rw-r--r--sequencer.c1659
1 files changed, 1168 insertions, 491 deletions
diff --git a/sequencer.c b/sequencer.c
index 61a8e00..2c19846 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -1,23 +1,33 @@
-#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 "hook.h"
-#include "exec-cmd.h"
#include "utf8.h"
#include "cache-tree.h"
#include "diff.h"
+#include "path.h"
#include "revision.h"
#include "rerere.h"
+#include "merge.h"
#include "merge-ort.h"
#include "merge-ort-wrappers.h"
#include "refs.h"
+#include "sparse-index.h"
#include "strvec.h"
#include "quote.h"
#include "trailer.h"
@@ -27,7 +37,6 @@
#include "notes-utils.h"
#include "sigchain.h"
#include "unpack-trees.h"
-#include "worktree.h"
#include "oidmap.h"
#include "oidset.h"
#include "commit-slab.h"
@@ -35,9 +44,19 @@
#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 ";
@@ -126,6 +145,11 @@ static GIT_PATH_FUNC(rebase_path_amend, "rebase-merge/amend")
*/
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,
@@ -148,6 +172,20 @@ 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.
*/
@@ -169,37 +207,57 @@ static GIT_PATH_FUNC(rebase_path_no_reschedule_failed_exec, "rebase-merge/no-res
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")) {
@@ -224,11 +282,7 @@ static int git_sequencer_config(const char *k, const char *v, void *cb)
if (opts->action == REPLAY_REVERT && !strcmp(k, "revert.reference"))
opts->commit_use_reference = git_config_bool(k, v);
- status = git_gpg_config(k, v, NULL);
- if (status)
- return status;
-
- return git_diff_basic_config(k, v, NULL);
+ return git_diff_basic_config(k, v, ctx, NULL);
}
void sequencer_init_config(struct replay_opts *opts)
@@ -278,12 +332,12 @@ static int has_conforming_footer(struct strbuf *sb, struct strbuf *sob,
sb->buf[sb->len - ignore_footer] = '\0';
}
- trailer_info_get(&info, sb->buf, &opts);
+ trailer_info_get(&opts, sb->buf, &info);
if (ignore_footer)
sb->buf[sb->len - ignore_footer] = saved_char;
- if (info.trailer_start == info.trailer_end)
+ if (info.trailer_block_start == info.trailer_block_end)
return 0;
for (i = 0; i < info.trailer_nr; i++)
@@ -312,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) {
@@ -334,14 +401,6 @@ int sequencer_remove_state(struct replay_opts *opts)
}
}
- free(opts->gpg_sign);
- free(opts->default_strategy);
- 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))
@@ -371,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)
@@ -381,8 +440,9 @@ 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);
@@ -398,44 +458,59 @@ 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);
}
+const char *rebase_resolvemsg =
+N_("Resolve all conflicts manually, mark them as resolved with\n"
+"\"git add/rm <conflicted_files>\", then run \"git rebase --continue\".\n"
+"You can instead skip this commit: run \"git rebase --skip\".\n"
+"To abort and get back to the state before \"git rebase\", run "
+"\"git rebase --abort\".");
+
static void print_advice(struct repository *r, int show_hint,
struct replay_opts *opts)
{
- char *msg = getenv("GIT_CHERRY_PICK_HELP");
+ const char *msg;
+
+ if (is_rebase_i(opts))
+ msg = rebase_resolvemsg;
+ else
+ msg = getenv("GIT_CHERRY_PICK_HELP");
if (msg) {
- advise("%s\n", msg);
+ advise_if_enabled(ADVICE_MERGE_CONFLICT, "%s", msg);
/*
* A conflict has occurred but the porcelain
* (typically rebase --interactive) wants to take care
* of the commit itself so remove CHERRY_PICK_HEAD
*/
refs_delete_ref(get_main_ref_store(r), "", "CHERRY_PICK_HEAD",
- NULL, 0);
+ NULL, REF_NO_DEREF);
return;
}
if (show_hint) {
if (opts->no_commit)
- advise(_("after resolving the conflicts, mark the corrected paths\n"
- "with 'git add <paths>' or 'git rm <paths>'"));
+ advise_if_enabled(ADVICE_MERGE_CONFLICT,
+ _("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\"."));
+ advise_if_enabled(ADVICE_MERGE_CONFLICT,
+ _("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\"."));
+ advise_if_enabled(ADVICE_MERGE_CONFLICT,
+ _("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
BUG("unexpected pick action in print_advice()");
}
@@ -497,7 +572,7 @@ 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)));
@@ -515,7 +590,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", "");
@@ -535,7 +610,7 @@ 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 ||
@@ -603,15 +678,16 @@ void append_conflicts_hint(struct index_state *istate,
if (cleanup_mode == COMMIT_MSG_CLEANUP_SCISSORS) {
strbuf_addch(msgbuf, '\n');
wt_status_append_cut_line(msgbuf);
- strbuf_addch(msgbuf, comment_line_char);
+ strbuf_addstr(msgbuf, comment_line_str);
}
strbuf_addch(msgbuf, '\n');
- strbuf_commented_addf(msgbuf, "Conflicts:\n");
+ strbuf_commented_addf(msgbuf, comment_line_str, "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_str,
+ "\t%s\n", ce->name);
while (i < istate->cache_nr &&
!strcmp(ce->name, istate->cache[i]->name))
i++;
@@ -646,11 +722,13 @@ 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);
+ if (!head_tree)
+ return error(_("unable to read tree (%s)"), oid_to_hex(head));
+ 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 (i = 0; i < opts->xopts_nr; i++)
- parse_merge_opt(&o, opts->xopts[i]);
+ for (i = 0; i < opts->xopts.nr; i++)
+ parse_merge_opt(&o, opts->xopts.v[i]);
if (!opts->strategy || !strcmp(opts->strategy, "ort")) {
memset(&result, 0, sizeof(result));
@@ -709,29 +787,42 @@ static struct object_id *get_cache_tree_oid(struct index_state *istate)
static int is_index_unchanged(struct repository *r)
{
struct object_id head_oid, *cache_tree_oid;
+ const struct object_id *head_tree_oid;
struct commit *head_commit;
struct index_state *istate = r->index;
+ const char *head_name;
+
+ if (!resolve_ref_unsafe("HEAD", RESOLVE_REF_READING, &head_oid, NULL)) {
+ /* Check to see if this is an unborn branch */
+ head_name = resolve_ref_unsafe("HEAD",
+ RESOLVE_REF_READING | RESOLVE_REF_NO_RECURSE,
+ &head_oid, NULL);
+ if (!head_name ||
+ !starts_with(head_name, "refs/heads/") ||
+ !is_null_oid(&head_oid))
+ return error(_("could not resolve HEAD commit"));
+ head_tree_oid = the_hash_algo->empty_tree;
+ } else {
+ head_commit = lookup_commit(r, &head_oid);
- if (!resolve_ref_unsafe("HEAD", RESOLVE_REF_READING, &head_oid, NULL))
- return error(_("could not resolve HEAD commit"));
-
- head_commit = lookup_commit(r, &head_oid);
+ /*
+ * If head_commit is NULL, check_commit, called from
+ * lookup_commit, would have indicated that head_commit is not
+ * a commit object already. repo_parse_commit() will return failure
+ * without further complaints in such a case. Otherwise, if
+ * the commit is invalid, repo_parse_commit() will complain. So
+ * there is nothing for us to say here. Just return failure.
+ */
+ if (repo_parse_commit(r, head_commit))
+ return -1;
- /*
- * If head_commit is NULL, check_commit, called from
- * lookup_commit, would have indicated that head_commit is not
- * a commit object already. parse_commit() will return failure
- * without further complaints in such a case. Otherwise, if
- * the commit is invalid, parse_commit() will complain. So
- * there is nothing for us to say here. Just return failure.
- */
- if (parse_commit(head_commit))
- return -1;
+ head_tree_oid = get_commit_tree_oid(head_commit);
+ }
if (!(cache_tree_oid = get_cache_tree_oid(istate)))
return -1;
- return oideq(cache_tree_oid, get_commit_tree_oid(head_commit));
+ return oideq(cache_tree_oid, head_tree_oid);
}
static int write_author_script(const char *message)
@@ -875,7 +966,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;
@@ -1010,6 +1101,8 @@ static int run_git_commit(const char *defmsg,
gpg_opt, gpg_opt);
}
+ 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 ?
@@ -1088,7 +1181,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_str : NULL);
}
/*
@@ -1119,7 +1213,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_str : NULL);
if (!skip_prefix(sb->buf, tmpl.buf, &start))
start = sb->buf;
strbuf_release(&tmpl);
@@ -1288,13 +1383,15 @@ void print_commit_summary(struct repository *r,
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);
@@ -1302,7 +1399,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);
@@ -1332,7 +1429,7 @@ void print_commit_summary(struct repository *r,
rev.diffopt.detect_rename = DIFF_DETECT_RENAME;
diff_setup_done(&rev.diffopt);
- refs = get_main_ref_store(the_repository);
+ refs = get_main_ref_store(r);
head = refs_resolve_ref_unsafe(refs, "HEAD", 0, NULL, NULL);
if (!head)
die(_("unable to resolve HEAD after creating commit"));
@@ -1358,7 +1455,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);
@@ -1368,7 +1465,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;
@@ -1411,8 +1508,8 @@ static int try_to_commit(struct repository *r,
if (flags & AMEND_MSG) {
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;
@@ -1423,7 +1520,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;
@@ -1488,7 +1586,8 @@ 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_str : NULL);
if ((flags & EDIT_MSG) && message_is_empty(msg, cleanup)) {
res = 1; /* run 'git commit' to display error message */
goto out;
@@ -1549,8 +1648,8 @@ static int try_to_commit(struct repository *r,
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;
}
@@ -1598,7 +1697,7 @@ static int do_commit(struct repository *r,
strbuf_release(&sb);
if (!res) {
refs_delete_ref(get_main_ref_store(r), "",
- "CHERRY_PICK_HEAD", NULL, 0);
+ "CHERRY_PICK_HEAD", NULL, REF_NO_DEREF);
unlink(git_path_merge_msg(r));
if (!is_rebase_i(opts))
print_commit_summary(r, NULL, &oid,
@@ -1620,12 +1719,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);
@@ -1650,34 +1749,25 @@ static int allow_empty(struct repository *r,
int index_unchanged, originally_empty;
/*
- * Four cases:
- *
- * (1) we do not allow empty at all and error out.
- *
- * (2) we allow ones that were initially empty, and
- * just drop the ones that become empty
- *
- * (3) we allow ones that were initially empty, but
- * halt for the ones that become empty;
+ * For a commit that is initially empty, allow_empty determines if it
+ * should be kept or not
*
- * (4) we allow both.
+ * For a commit that becomes empty, keep_redundant_commits and
+ * drop_redundant_commits determine whether the commit should be kept or
+ * dropped. If neither is specified, halt.
*/
- if (!opts->allow_empty)
- return 0; /* let "git commit" barf as necessary */
-
index_unchanged = is_index_unchanged(r);
if (index_unchanged < 0)
return index_unchanged;
if (!index_unchanged)
return 0; /* we do not have to say --allow-empty */
- if (opts->keep_redundant_commits)
- return 1;
-
originally_empty = is_original_commit_empty(commit);
if (originally_empty < 0)
return originally_empty;
if (originally_empty)
+ return opts->allow_empty;
+ else if (opts->keep_redundant_commits)
return 1;
else if (opts->drop_redundant_commits)
return 2;
@@ -1689,26 +1779,29 @@ 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)
{
if (command < TODO_COMMENT)
return todo_command_info[command].str;
+ if (command == TODO_COMMENT)
+ return comment_line_str;
die(_("unknown command: %d"), command);
}
@@ -1716,7 +1809,7 @@ static char command_to_char(const enum todo_command command)
{
if (command < TODO_COMMENT)
return todo_command_info[command].c;
- return comment_line_char;
+ return 0;
}
static int is_noop(const enum todo_command command)
@@ -1770,7 +1863,7 @@ static int is_fixup_flag(enum todo_command command, unsigned flag)
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) {
+ while (starts_with_mem(s, len, comment_line_str)) {
size_t count;
const char *n = memchr(s, '\n', len);
if (!n)
@@ -1781,7 +1874,7 @@ static void add_commented_lines(struct strbuf *buf, const void *str, size_t len)
s += count;
len -= count;
}
- strbuf_add_commented_lines(buf, s, len);
+ strbuf_add_commented_lines(buf, s, len, comment_line_str);
}
/* Does the current fixup chain contain a squash command? */
@@ -1876,11 +1969,11 @@ static int append_squash_message(struct strbuf *buf, const char *body,
(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, "\n%s ", comment_line_str);
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);
+ strbuf_add_commented_lines(buf, body, commented_len, comment_line_str);
/* buf->buf may be reallocated so store an offset into the buffer */
fixup_off = buf->len;
strbuf_addstr(buf, body + commented_len);
@@ -1933,10 +2026,10 @@ static int update_squash_messages(struct repository *r,
return error(_("could not read '%s'"),
rebase_path_squash_msg());
- eol = buf.buf[0] != comment_line_char ?
+ eol = !starts_with(buf.buf, comment_line_str) ?
buf.buf : strchrnul(buf.buf, '\n');
- strbuf_addf(&header, "%c ", comment_line_char);
+ strbuf_addf(&header, "%s ", comment_line_str);
strbuf_addf(&header, _(combined_commit_msg_fmt),
opts->current_fixup_count + 2);
strbuf_splice(&buf, 0, eol - buf.buf, header.buf, header.len);
@@ -1948,35 +2041,37 @@ static int update_squash_messages(struct repository *r,
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 (command == TODO_FIXUP && !flag && write_message(body, strlen(body),
rebase_path_fixup_msg(), 0) < 0) {
- unuse_commit_buffer(head_commit, head_message);
+ 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, "%s ", comment_line_str);
strbuf_addf(&buf, _(combined_commit_msg_fmt), 2);
- strbuf_addf(&buf, "\n%c ", comment_line_char);
+ strbuf_addf(&buf, "\n%s ", comment_line_str);
strbuf_addstr(&buf, is_fixup_flag(command, flag) ?
_(skip_first_commit_msg_str) :
_(first_commit_msg_str));
strbuf_addstr(&buf, "\n\n");
if (is_fixup_flag(command, flag))
- strbuf_add_commented_lines(&buf, body, strlen(body));
+ strbuf_add_commented_lines(&buf, body, strlen(body),
+ comment_line_str);
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);
@@ -1984,14 +2079,15 @@ static int update_squash_messages(struct repository *r,
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, "\n%s ", comment_line_str);
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_str);
} else
return error(_("unknown command: %d"), command);
- unuse_commit_buffer(commit, message);
+ repo_unuse_commit_buffer(r, commit, message);
if (!res)
res = write_message(buf.buf, buf.len, rebase_path_squash_msg(),
@@ -2018,7 +2114,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;
@@ -2070,7 +2166,8 @@ static void refer_to_commit(struct replay_opts *opts,
.abbrev = DEFAULT_ABBREV,
.date_mode.type = DATE_SHORT,
};
- format_commit_message(commit, "%h (%s, %ad)", msgbuf, &ctx);
+ repo_format_commit_message(the_repository, commit,
+ "%h (%s, %ad)", msgbuf, &ctx);
} else {
strbuf_addstr(msgbuf, oid_to_hex(&commit->object.oid));
}
@@ -2103,7 +2200,7 @@ static int do_pick_commit(struct repository *r,
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)) {
@@ -2165,7 +2262,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"),
@@ -2180,6 +2277,8 @@ static int do_pick_commit(struct repository *r,
*/
if (command == TODO_REVERT) {
+ const char *orig_subject;
+
base = commit;
base_label = msg.label;
next = parent;
@@ -2187,6 +2286,15 @@ static int do_pick_commit(struct repository *r,
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);
@@ -2228,8 +2336,10 @@ static int do_pick_commit(struct repository *r,
reword = 1;
else if (is_fixup(command)) {
if (update_squash_messages(r, command, commit,
- opts, item->flags))
- return -1;
+ opts, item->flags)) {
+ res = -1;
+ goto leave;
+ }
flags |= AMEND_MSG;
if (!final_fixup)
msg_file = rebase_path_squash_msg();
@@ -2239,9 +2349,11 @@ static int do_pick_commit(struct repository *r,
} 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;
@@ -2274,12 +2386,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
@@ -2302,7 +2413,7 @@ 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;
@@ -2318,9 +2429,10 @@ static int do_pick_commit(struct repository *r,
} else if (allow == 2) {
drop_commit = 1;
refs_delete_ref(get_main_ref_store(r), "", "CHERRY_PICK_HEAD",
- NULL, 0);
+ NULL, REF_NO_DEREF);
unlink(git_path_merge_msg(r));
- unlink(git_path_auto_merge(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);
@@ -2353,6 +2465,7 @@ fast_forward_edit:
leave:
free_message(commit, &msg);
free(author);
+ strbuf_release(&msgbuf);
update_abort_safety_file();
return res;
@@ -2381,7 +2494,7 @@ 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);
@@ -2389,7 +2502,7 @@ static int read_and_refresh_cache(struct repository *r,
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));
}
}
@@ -2412,7 +2525,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++;
}
@@ -2426,12 +2538,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(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;
- return skip_prefix(*bol, str, bol) ||
- ((nick && **bol == nick) &&
- (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r' || !*p) &&
- (*bol = p));
+ 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,
@@ -2446,7 +2585,7 @@ static int parse_insn_line(struct repository *r, struct todo_item *item,
/* left-trim */
bol += strspn(bol, " \t");
- if (bol == eol || *bol == '\r' || *bol == comment_line_char) {
+ if (bol == eol || *bol == '\r' || starts_with_mem(bol, eol - bol, comment_line_str)) {
item->command = TODO_COMMENT;
item->commit = NULL;
item->arg_offset = bol - buf;
@@ -2460,7 +2599,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");
@@ -2481,20 +2621,27 @@ 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 == ' ' || *bol == '\t')) {
+ if (skip_prefix(bol, "-C", &bol)) {
bol += strspn(bol, " \t");
item->flags |= TODO_REPLACE_FIXUP_MSG;
- } else if (skip_prefix(bol, "-c", &bol) &&
- (*bol == ' ' || *bol == '\t')) {
+ } else if (skip_prefix(bol, "-c", &bol)) {
bol += strspn(bol, " \t");
item->flags |= TODO_EDIT_FIXUP_MSG;
}
@@ -2518,7 +2665,7 @@ 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;
@@ -2534,7 +2681,7 @@ static int parse_insn_line(struct repository *r, struct todo_item *item,
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;
@@ -2568,7 +2715,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');
@@ -2589,10 +2736,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;
@@ -2676,7 +2826,7 @@ void sequencer_post_commit_cleanup(struct repository *r, int 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, 0) &&
+ "CHERRY_PICK_HEAD", NULL, REF_NO_DEREF) &&
verbose)
warning(_("cancelling a cherry picking in progress"));
opts.action = REPLAY_PICK;
@@ -2685,14 +2835,15 @@ void sequencer_post_commit_cleanup(struct repository *r, int verbose)
if (refs_ref_exists(get_main_ref_store(r), "REVERT_HEAD")) {
if (!refs_delete_ref(get_main_ref_store(r), "", "REVERT_HEAD",
- NULL, 0) &&
+ NULL, REF_NO_DEREF) &&
verbose)
warning(_("cancelling a revert in progress"));
opts.action = REPLAY_REVERT;
need_cleanup = 1;
}
- unlink(git_path_auto_merge(r));
+ refs_delete_ref(get_main_ref_store(r), "", "AUTO_MERGE",
+ NULL, REF_NO_DEREF);
if (!need_cleanup)
return;
@@ -2779,7 +2930,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;
@@ -2787,36 +2940,38 @@ 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.drop-redundant-commits"))
+ opts->drop_redundant_commits =
+ 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;
@@ -2830,22 +2985,27 @@ static int populate_opts_cb(const char *key, const char *value, void *data)
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)
@@ -2933,7 +3093,7 @@ static int read_populate_opts(struct replay_opts *opts)
}
if (read_oneliner(&buf, rebase_path_squash_onto(), 0)) {
- if (get_oid_committish(buf.buf, &opts->squash_onto) < 0) {
+ if (repo_get_oid_committish(the_repository, buf.buf, &opts->squash_onto) < 0) {
ret = error(_("unusable squash-onto"));
goto done_rebase_i;
}
@@ -2961,12 +3121,13 @@ done_rebase_i:
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);
}
@@ -2989,7 +3150,7 @@ int write_basic_state(struct replay_opts *opts, const char *head_name,
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)
@@ -3033,7 +3194,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;
@@ -3044,8 +3207,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)
@@ -3095,25 +3260,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)
@@ -3134,7 +3281,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);
@@ -3142,18 +3289,15 @@ static int rollback_is_safe(void)
static int reset_merge(const struct object_id *oid)
{
- int ret;
- struct strvec argv = STRVEC_INIT;
+ struct child_process cmd = CHILD_PROCESS_INIT;
- strvec_pushl(&argv, "reset", "--merge", NULL);
+ cmd.git_cmd = 1;
+ strvec_pushl(&cmd.args, "reset", "--merge", NULL);
if (!is_null_oid(oid))
- strvec_push(&argv, oid_to_hex(oid));
-
- ret = run_command_v_opt(argv.v, RUN_GIT_CMD);
- strvec_clear(&argv);
+ strvec_push(&cmd.args, oid_to_hex(oid));
- return ret;
+ return run_command(&cmd);
}
static int rollback_single_pick(struct repository *r)
@@ -3293,7 +3437,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);
@@ -3303,7 +3448,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);
@@ -3316,7 +3461,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;
@@ -3341,57 +3486,57 @@ 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");
+ "options.no-commit", NULL, "true");
if (opts->edit >= 0)
- res |= git_config_set_in_file_gently(opts_file, "options.edit",
+ res |= git_config_set_in_file_gently(opts_file, "options.edit", NULL,
opts->edit ? "true" : "false");
if (opts->allow_empty)
res |= git_config_set_in_file_gently(opts_file,
- "options.allow-empty", "true");
+ "options.allow-empty", NULL, "true");
if (opts->allow_empty_message)
res |= git_config_set_in_file_gently(opts_file,
- "options.allow-empty-message", "true");
+ "options.allow-empty-message", NULL, "true");
+ if (opts->drop_redundant_commits)
+ res |= git_config_set_in_file_gently(opts_file,
+ "options.drop-redundant-commits", NULL, "true");
if (opts->keep_redundant_commits)
res |= git_config_set_in_file_gently(opts_file,
- "options.keep-redundant-commits", "true");
+ "options.keep-redundant-commits", NULL, "true");
if (opts->signoff)
res |= git_config_set_in_file_gently(opts_file,
- "options.signoff", "true");
+ "options.signoff", NULL, "true");
if (opts->record_origin)
res |= git_config_set_in_file_gently(opts_file,
- "options.record-origin", "true");
+ "options.record-origin", NULL, "true");
if (opts->allow_ff)
res |= git_config_set_in_file_gently(opts_file,
- "options.allow-ff", "true");
+ "options.allow-ff", NULL, "true");
if (opts->mainline) {
struct strbuf buf = STRBUF_INIT;
strbuf_addf(&buf, "%d", opts->mainline);
res |= git_config_set_in_file_gently(opts_file,
- "options.mainline", buf.buf);
+ "options.mainline", NULL, buf.buf);
strbuf_release(&buf);
}
if (opts->strategy)
res |= git_config_set_in_file_gently(opts_file,
- "options.strategy", opts->strategy);
+ "options.strategy", NULL, opts->strategy);
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);
- }
+ "options.gpg-sign", NULL, opts->gpg_sign);
+ 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], "^$", NULL, 0);
if (opts->allow_rerere_auto)
res |= git_config_set_in_file_gently(opts_file,
- "options.allow-rerere-auto",
+ "options.allow-rerere-auto", NULL,
opts->allow_rerere_auto == RERERE_AUTOUPDATE ?
"true" : "false");
if (opts->explicit_cleanup)
res |= git_config_set_in_file_gently(opts_file,
- "options.default-msg-cleanup",
+ "options.default-msg-cleanup", NULL,
describe_cleanup_mode(opts->default_msg_cleanup));
return res;
}
@@ -3400,18 +3545,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;
char hex[GIT_MAX_HEXSZ + 1];
int res = 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;
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;
@@ -3419,25 +3565,26 @@ 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;
@@ -3448,7 +3595,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);
@@ -3485,7 +3632,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
@@ -3517,15 +3664,18 @@ static int error_failed_squash(struct repository *r,
static int do_exec(struct repository *r, const char *command_line)
{
- 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;
- status = run_command_v_opt(child_argv, RUN_USING_SHELL);
+ 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);
@@ -3537,14 +3687,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"
@@ -3585,7 +3735,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);
}
@@ -3612,7 +3761,7 @@ static int do_label(struct repository *r, const char *name, int len)
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,
@@ -3633,17 +3782,28 @@ 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, ...)
{
va_list ap;
static struct strbuf buf = STRBUF_INIT;
- char *reflog_action = getenv(GIT_REFLOG_ACTION);
va_start(ap, fmt);
strbuf_reset(&buf);
- strbuf_addstr(&buf, reflog_action ? reflog_action : action_name(opts));
+ strbuf_addstr(&buf, sequencer_reflog_action(opts));
if (sub_action)
strbuf_addf(&buf, " (%s)", sub_action);
if (fmt) {
@@ -3655,6 +3815,28 @@ static const char *reflog_message(struct replay_opts *opts,
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,
struct replay_opts *opts)
@@ -3686,6 +3868,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++)
@@ -3693,12 +3876,12 @@ 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)) {
- ret = error(_("could not read '%s'"), ref_name.buf);
+ commit = lookup_label(r, name, len, &ref_name);
+ if (!commit) {
+ ret = -1;
goto cleanup;
}
+ oid = commit->object.oid;
}
setup_unpack_trees_porcelain(&unpack_tree_opts, "reset");
@@ -3709,10 +3892,11 @@ static int do_reset(struct repository *r,
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)) {
- ret = error_resolve_conflict(_(action_name(opts)));
+ ret = error_resolve_conflict(action_name(opts));
goto cleanup;
}
@@ -3727,6 +3911,8 @@ static int do_reset(struct repository *r,
}
tree = parse_tree_indirect(&oid);
+ if (!tree)
+ return error(_("unable to read tree (%s)"), oid_to_hex(&oid));
prime_cache_tree(r, r->index, tree);
if (write_locked_index(r->index, &lock, COMMIT_LOCK) < 0)
@@ -3745,26 +3931,6 @@ cleanup:
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,
@@ -3773,9 +3939,9 @@ static int do_merge(struct repository *r,
int run_commit_flags = 0;
struct strbuf ref_name = STRBUF_INIT;
struct commit *head_commit, *merge_commit, *i;
- struct commit_list *bases, *j;
+ struct commit_list *bases = NULL, *j;
struct commit_list *to_merge = NULL, **tail = &to_merge;
- const char *strategy = !opts->xopts_nr &&
+ const char *strategy = !opts->xopts.nr &&
(!opts->strategy ||
!strcmp(opts->strategy, "recursive") ||
!strcmp(opts->strategy, "ort")) ?
@@ -3812,7 +3978,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;
@@ -3885,7 +4051,8 @@ static int do_merge(struct repository *r,
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;
@@ -3898,7 +4065,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));
@@ -3958,9 +4125,9 @@ static int do_merge(struct repository *r,
strvec_push(&cmd.args, "octopus");
else {
strvec_push(&cmd.args, strategy);
- for (k = 0; k < opts->xopts_nr; k++)
+ for (k = 0; k < opts->xopts.nr; k++)
strvec_pushf(&cmd.args,
- "-X%s", opts->xopts[k]);
+ "-X%s", opts->xopts.v[k]);
}
if (!(flags & TODO_EDIT_MERGE_MSG))
strvec_push(&cmd.args, "--no-edit");
@@ -3983,20 +4150,26 @@ static int do_merge(struct repository *r,
strbuf_release(&ref_name);
refs_delete_ref(get_main_ref_store(r), "", "CHERRY_PICK_HEAD",
- NULL, 0);
+ NULL, REF_NO_DEREF);
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);
+ if (repo_get_merge_bases(r, head_commit, merge_commit, &bases) < 0) {
+ ret = -1;
+ goto leave_merge;
+ }
+
if (bases && oideq(&merge_commit->object.oid,
&bases->item->object.oid)) {
ret = 0;
@@ -4036,6 +4209,7 @@ static int do_merge(struct repository *r,
if (ret < 0) {
error(_("could not even attempt to merge '%.*s'"),
merge_arg_len, arg);
+ unlink(git_path_merge_msg(r));
goto leave_merge;
}
/*
@@ -4081,6 +4255,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;
@@ -4107,12 +4499,17 @@ static enum todo_command peek_command(struct todo_list *todo_list, int offset)
return -1;
}
-void create_autostash(struct repository *r, const char *path)
+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)
@@ -4133,26 +4530,42 @@ void create_autostash(struct repository *r, const char *path)
if (capture_command(&stash, &buf, GIT_MAX_HEXSZ))
die(_("Cannot autostash"));
strbuf_trim_trailing_newline(&buf);
- if (get_oid(buf.buf, &oid))
+ 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 (safe_create_leading_directories_const(path))
- die(_("Could not create directory for '%s'"),
- path);
- write_file(path, "%s", oid_to_hex(&oid));
+ 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"));
- if (discard_index(r->index) < 0 ||
- repo_read_index(r) < 0)
+ 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 child_process child = CHILD_PROCESS_INIT;
@@ -4230,6 +4643,41 @@ int apply_autostash_oid(const char *stash_oid)
return apply_save_autostash_oid(stash_oid, 1);
}
+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 (!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 save_autostash_ref(struct repository *r, const char *refname)
+{
+ return apply_save_autostash_ref(r, refname, 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 struct object_id *orig_head)
@@ -4241,7 +4689,7 @@ static int checkout_onto(struct repository *r, struct replay_opts *opts,
RESET_HEAD_RUN_POST_CHECKOUT_HOOK,
.head_msg = reflog_message(opts, "start", "checkout %s",
onto_name),
- .default_reflog_action = "rebase"
+ .default_reflog_action = sequencer_reflog_action(opts)
};
if (reset_head(r, &ropts)) {
apply_autostash(rebase_path_autostash());
@@ -4258,9 +4706,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);
@@ -4305,16 +4753,75 @@ 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;
- char *prev_reflog_action;
- /* Note that 0 for 3rd parameter of setenv means set only if not set */
- setenv(GIT_REFLOG_ACTION, action_name(opts), 0);
- prev_reflog_action = xstrdup(getenv(GIT_REFLOG_ACTION));
+ opts->reflog_message = sequencer_reflog_action(opts);
if (opts->allow_ff)
assert(!(opts->signoff || opts->no_commit ||
opts->record_origin || should_edit(opts) ||
@@ -4323,12 +4830,17 @@ static int pick_commits(struct repository *r,
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) {
@@ -4346,13 +4858,12 @@ static int pick_commits(struct repository *r,
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));
- unlink(git_path_auto_merge(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)
@@ -4361,68 +4872,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, opts,
- is_final_fixup(todo_list),
- &check_todo);
- if (is_rebase_i(opts))
- setenv(GIT_REFLOG_ACTION, prev_reflog_action, 1);
- 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;
@@ -4456,6 +4909,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);
@@ -4464,39 +4923,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);
+ 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);
@@ -4543,8 +4995,8 @@ 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);
@@ -4555,8 +5007,7 @@ cleanup_head_ref:
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;
@@ -4566,18 +5017,9 @@ cleanup_head_ref:
/* 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";
- strvec_push(&hook.args, post_rewrite_hook);
- strvec_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(rebase_path_autostash());
@@ -4591,6 +5033,9 @@ cleanup_head_ref:
strbuf_release(&buf);
strbuf_release(&head_ref);
+
+ if (do_update_refs(r, opts->quiet))
+ return -1;
}
/*
@@ -4602,14 +5047,14 @@ cleanup_head_ref:
static int continue_single_pick(struct repository *r, struct replay_opts *opts)
{
- struct strvec argv = STRVEC_INIT;
- int ret;
+ struct child_process cmd = CHILD_PROCESS_INIT;
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"));
- strvec_push(&argv, "commit");
+ cmd.git_cmd = 1;
+ strvec_push(&cmd.args, "commit");
/*
* continue_single_pick() handles the case of recovering from a
@@ -4622,11 +5067,9 @@ static int continue_single_pick(struct repository *r, struct replay_opts *opts)
* Include --cleanup=strip as well because we don't want the
* "# Conflicts:" messages.
*/
- strvec_pushl(&argv, "--no-edit", "--cleanup=strip", NULL);
+ strvec_pushl(&cmd.args, "--no-edit", "--cleanup=strip", NULL);
- ret = run_command_v_opt(argv.v, RUN_GIT_CMD);
- strvec_clear(&argv);
- return ret;
+ return run_command(&cmd);
}
static int commit_staged_changes(struct repository *r,
@@ -4641,11 +5084,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());
@@ -4720,18 +5168,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;
}
}
@@ -4743,7 +5204,7 @@ static int commit_staged_changes(struct repository *r,
if (refs_ref_exists(get_main_ref_store(r),
"CHERRY_PICK_HEAD") &&
refs_delete_ref(get_main_ref_store(r), "",
- "CHERRY_PICK_HEAD", NULL, 0))
+ "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'"),
@@ -4757,7 +5218,8 @@ static int commit_staged_changes(struct repository *r,
return error(_("could not commit staged changes."));
unlink(rebase_path_amend());
unlink(git_path_merge_head(r));
- unlink(git_path_auto_merge(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());
@@ -4795,6 +5257,7 @@ int sequencer_continue(struct repository *r, struct replay_opts *opts)
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;
@@ -4846,7 +5309,7 @@ static int single_pick(struct repository *r,
TODO_PICK : TODO_REVERT;
item.commit = cmit;
- setenv(GIT_REFLOG_ACTION, action_name(opts), 0);
+ opts->reflog_message = sequencer_reflog_action(opts);
return do_pick_commit(r, &item, opts, 0, &check_todo);
}
@@ -4869,7 +5332,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,
@@ -4912,7 +5375,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;
@@ -4989,7 +5452,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;
@@ -5009,6 +5473,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,
@@ -5027,7 +5492,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.
*
@@ -5043,7 +5508,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
@@ -5064,6 +5530,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
@@ -5072,14 +5540,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-");
@@ -5141,7 +5629,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",
@@ -5149,6 +5638,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);
@@ -5185,7 +5676,7 @@ static int make_script_with_merges(struct pretty_print_context *pp,
if (!is_empty && (commit->object.flags & PATCHSAME)) {
if (flags & TODO_LIST_WARN_SKIPPED_CHERRY_PICKS)
warning(_("skipped previously applied commit %s"),
- short_commit_name(commit));
+ short_commit_name(the_repository, commit));
skipped_commit = 1;
continue;
}
@@ -5203,8 +5694,8 @@ static int make_script_with_merges(struct pretty_print_context *pp,
oid_to_hex(&commit->object.oid),
oneline.buf);
if (is_empty)
- strbuf_addf(&buf, " %c empty",
- comment_line_char);
+ strbuf_addf(&buf, " %s empty",
+ comment_line_str);
FLEX_ALLOC_STR(entry, string, buf.buf);
oidcpy(&entry->entry.oid, &commit->object.oid);
@@ -5294,7 +5785,7 @@ static int make_script_with_merges(struct pretty_print_context *pp,
entry = oidmap_get(&state.commit2label, &commit->object.oid);
if (entry)
- strbuf_addf(out, "\n%c Branch %s\n", comment_line_char, entry->string);
+ strbuf_addf(out, "\n%s Branch %s\n", comment_line_str, entry->string);
else
strbuf_addch(out, '\n');
@@ -5421,7 +5912,7 @@ int sequencer_make_script(struct repository *r, struct strbuf *out, int argc,
if (!is_empty && (commit->object.flags & PATCHSAME)) {
if (flags & TODO_LIST_WARN_SKIPPED_CHERRY_PICKS)
warning(_("skipped previously applied commit %s"),
- short_commit_name(commit));
+ short_commit_name(r, commit));
skipped_commit = 1;
continue;
}
@@ -5431,7 +5922,7 @@ int sequencer_make_script(struct repository *r, struct strbuf *out, int argc,
oid_to_hex(&commit->object.oid));
pretty_print_commit(&pp, commit, out);
if (is_empty)
- strbuf_addf(out, " %c empty", comment_line_char);
+ strbuf_addf(out, " %s empty", comment_line_str);
strbuf_addch(out, '\n');
}
if (skipped_commit)
@@ -5463,8 +5954,8 @@ static 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;
}
@@ -5513,7 +6004,8 @@ static 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;
@@ -5542,7 +6034,7 @@ static void todo_list_to_strbuf(struct repository *r, struct todo_list *todo_lis
/* 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) {
@@ -5605,7 +6097,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));
}
@@ -5638,10 +6130,135 @@ 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 struct object_id *orig_head,
struct string_list *commands, unsigned autosquash,
+ unsigned update_refs,
struct todo_list *todo_list)
{
char shortonto[GIT_MAX_HEXSZ + 1];
@@ -5651,7 +6268,8 @@ int complete_action(struct repository *r, struct replay_opts *opts, unsigned fla
struct object_id oid = onto->object.oid;
int res;
- find_unique_abbrev_r(shortonto, &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);
@@ -5660,6 +6278,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;
@@ -5699,7 +6320,8 @@ int complete_action(struct repository *r, struct replay_opts *opts, unsigned fla
todo_list_to_strbuf(r, &new_todo, &buf2, -1, 0);
strbuf_swap(&new_todo.buf, &buf2);
strbuf_release(&buf2);
- new_todo.total_nr -= new_todo.nr;
+ /* 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);
@@ -5720,7 +6342,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);
@@ -5738,7 +6360,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)
@@ -5771,7 +6393,7 @@ static int skip_fixupish(const char *subject, const char **p) {
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;
@@ -5809,14 +6431,15 @@ 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);
+ repo_unuse_commit_buffer(the_repository, item->commit,
+ commit_buffer);
if (skip_fixupish(subject, &p)) {
struct commit *commit2;
@@ -5877,9 +6500,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;
@@ -5892,16 +6519,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);
@@ -5924,8 +6550,8 @@ int sequencer_determine_whence(struct repository *r, enum commit_whence *whence)
if (file_exists(git_path_seq_dir()))
*whence = FROM_CHERRY_PICK_MULTI;
if (file_exists(rebase_path()) &&
- !get_oid("REBASE_HEAD", &rebase_head) &&
- !get_oid("CHERRY_PICK_HEAD", &cherry_pick_head) &&
+ !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
@@ -5936,3 +6562,54 @@ int sequencer_determine_whence(struct repository *r, enum commit_whence *whence)
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;
+}