return REF_ACTION_UPDATE;
}
+struct replay_revisions_options {
+ const char *advance;
+ const char *onto;
+ int contained;
+};
+
+struct replay_ref_updates {
+ struct replay_ref_update {
+ char *refname;
+ struct object_id old_oid;
+ struct object_id new_oid;
+ } *items;
+ size_t nr, alloc;
+};
+
+static void replay_ref_updates_release(struct replay_ref_updates *updates)
+{
+ for (size_t i = 0; i < updates->nr; i++)
+ free(updates->items[i].refname);
+ free(updates->items);
+}
+
+static int replay_revisions(struct repository *repo, struct rev_info *revs,
+ struct replay_revisions_options *opts,
+ struct replay_ref_updates *updates)
+{
+ kh_oid_map_t *replayed_commits = NULL;
+ struct strset *update_refs = NULL;
+ struct commit *last_commit = NULL;
+ struct commit *commit;
+ struct commit *onto = NULL;
+ struct merge_options merge_opt;
+ struct merge_result result;
+ char *advance;
+ int ret;
+
+ advance = xstrdup_or_null(opts->advance);
+ set_up_replay_mode(repo, &revs->cmdline, opts->onto, &advance,
+ &onto, &update_refs);
+
+ /* FIXME: Should allow replaying commits with the first as a root commit */
+
+ if (prepare_revision_walk(revs) < 0) {
+ ret = error(_("error preparing revisions"));
+ goto out;
+ }
+
+ init_basic_merge_options(&merge_opt, repo);
+ memset(&result, 0, sizeof(result));
+ merge_opt.show_rename_progress = 0;
+ last_commit = onto;
+ replayed_commits = kh_init_oid_map();
+ while ((commit = get_revision(revs))) {
+ const struct name_decoration *decoration;
+ khint_t pos;
+ int hr;
+
+ if (!commit->parents)
+ die(_("replaying down from root commit is not supported yet!"));
+ if (commit->parents->next)
+ die(_("replaying merge commits is not supported yet!"));
+
+ last_commit = pick_regular_commit(repo, commit, replayed_commits,
+ onto, &merge_opt, &result);
+ if (!last_commit)
+ break;
+
+ /* Record commit -> last_commit mapping */
+ pos = kh_put_oid_map(replayed_commits, commit->object.oid, &hr);
+ if (hr == 0)
+ BUG("Duplicate rewritten commit: %s\n",
+ oid_to_hex(&commit->object.oid));
+ kh_value(replayed_commits, pos) = last_commit;
+
+ /* Update any necessary branches */
+ if (advance)
+ continue;
+ decoration = get_name_decoration(&commit->object);
+ if (!decoration)
+ continue;
+ while (decoration) {
+ if (decoration->type == DECORATION_REF_LOCAL &&
+ (opts->contained || strset_contains(update_refs,
+ decoration->name))) {
+ ALLOC_GROW(updates->items, updates->nr + 1, updates->alloc);
+ updates->items[updates->nr].refname = xstrdup(decoration->name);
+ updates->items[updates->nr].old_oid = commit->object.oid;
+ updates->items[updates->nr].new_oid = last_commit->object.oid;
+ updates->nr++;
+ }
+ decoration = decoration->next;
+ }
+ }
+
+ if (!result.clean) {
+ ret = -1;
+ goto out;
+ }
+
+ /* In --advance mode, advance the target ref */
+ if (advance) {
+ ALLOC_GROW(updates->items, updates->nr + 1, updates->alloc);
+ updates->items[updates->nr].refname = xstrdup(advance);
+ updates->items[updates->nr].old_oid = onto->object.oid;
+ updates->items[updates->nr].new_oid = last_commit->object.oid;
+ updates->nr++;
+ }
+
+ ret = 0;
+
+out:
+ if (update_refs) {
+ strset_clear(update_refs);
+ free(update_refs);
+ }
+ kh_destroy_oid_map(replayed_commits);
+ merge_finalize(&merge_opt, &result);
+ free(advance);
+ return ret;
+}
+
static int handle_ref_update(enum ref_action_mode mode,
struct ref_transaction *transaction,
const char *refname,
const char *prefix,
struct repository *repo)
{
- const char *advance_name_opt = NULL;
- char *advance_name = NULL;
- struct commit *onto = NULL;
- const char *onto_name = NULL;
- int contained = 0;
+ struct replay_revisions_options opts = { 0 };
+ struct replay_ref_updates updates = { 0 };
const char *ref_action = NULL;
enum ref_action_mode ref_mode;
-
struct rev_info revs;
- struct commit *last_commit = NULL;
- struct commit *commit;
- struct merge_options merge_opt;
- struct merge_result result;
- struct strset *update_refs = NULL;
- kh_oid_map_t *replayed_commits;
struct ref_transaction *transaction = NULL;
struct strbuf transaction_err = STRBUF_INIT;
struct strbuf reflog_msg = STRBUF_INIT;
NULL
};
struct option replay_options[] = {
- OPT_STRING(0, "advance", &advance_name_opt,
+ OPT_STRING(0, "advance", &opts.advance,
N_("branch"),
N_("make replay advance given branch")),
- OPT_STRING(0, "onto", &onto_name,
+ OPT_STRING(0, "onto", &opts.onto,
N_("revision"),
N_("replay onto given commit")),
- OPT_BOOL(0, "contained", &contained,
+ OPT_BOOL(0, "contained", &opts.contained,
N_("update all branches that point at commits in <revision-range>")),
OPT_STRING(0, "ref-action", &ref_action,
N_("mode"),
argc = parse_options(argc, argv, prefix, replay_options, replay_usage,
PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN_OPT);
- if (!onto_name && !advance_name_opt) {
+ if (!opts.onto && !opts.advance) {
error(_("option --onto or --advance is mandatory"));
usage_with_options(replay_usage, replay_options);
}
- die_for_incompatible_opt2(!!advance_name_opt, "--advance",
- contained, "--contained");
+ die_for_incompatible_opt2(!!opts.advance, "--advance",
+ opts.contained, "--contained");
/* Parse ref action mode from command line or config */
ref_mode = get_ref_action_mode(repo, ref_action);
- advance_name = xstrdup_or_null(advance_name_opt);
-
repo_init_revisions(repo, &revs, prefix);
/*
revs.simplify_history = 0;
}
- set_up_replay_mode(repo, &revs.cmdline,
- onto_name, &advance_name,
- &onto, &update_refs);
-
- /* FIXME: Should allow replaying commits with the first as a root commit */
+ ret = replay_revisions(repo, &revs, &opts, &updates);
+ if (ret)
+ goto cleanup;
/* Build reflog message */
- if (advance_name_opt)
- strbuf_addf(&reflog_msg, "replay --advance %s", advance_name_opt);
- else
- strbuf_addf(&reflog_msg, "replay --onto %s",
- oid_to_hex(&onto->object.oid));
+ if (opts.advance) {
+ strbuf_addf(&reflog_msg, "replay --advance %s", opts.advance);
+ } else {
+ struct object_id oid;
+ if (repo_get_oid_committish(repo, opts.onto, &oid))
+ BUG("--onto commit should have been resolved beforehand already");
+ strbuf_addf(&reflog_msg, "replay --onto %s", oid_to_hex(&oid));
+ }
/* Initialize ref transaction if using update mode */
if (ref_mode == REF_ACTION_UPDATE) {
}
}
- if (prepare_revision_walk(&revs) < 0) {
- ret = error(_("error preparing revisions"));
- goto cleanup;
- }
-
- init_basic_merge_options(&merge_opt, repo);
- memset(&result, 0, sizeof(result));
- merge_opt.show_rename_progress = 0;
- last_commit = onto;
- replayed_commits = kh_init_oid_map();
- while ((commit = get_revision(&revs))) {
- const struct name_decoration *decoration;
- khint_t pos;
- int hr;
-
- if (!commit->parents)
- die(_("replaying down from root commit is not supported yet!"));
- if (commit->parents->next)
- die(_("replaying merge commits is not supported yet!"));
-
- last_commit = pick_regular_commit(repo, commit, replayed_commits,
- onto, &merge_opt, &result);
- if (!last_commit)
- break;
-
- /* Record commit -> last_commit mapping */
- pos = kh_put_oid_map(replayed_commits, commit->object.oid, &hr);
- if (hr == 0)
- BUG("Duplicate rewritten commit: %s\n",
- oid_to_hex(&commit->object.oid));
- kh_value(replayed_commits, pos) = last_commit;
-
- /* Update any necessary branches */
- if (advance_name)
- continue;
- decoration = get_name_decoration(&commit->object);
- if (!decoration)
- continue;
- while (decoration) {
- if (decoration->type == DECORATION_REF_LOCAL &&
- (contained || strset_contains(update_refs,
- decoration->name))) {
- if (handle_ref_update(ref_mode, transaction,
- decoration->name,
- &last_commit->object.oid,
- &commit->object.oid,
- reflog_msg.buf,
- &transaction_err) < 0) {
- ret = error(_("failed to update ref '%s': %s"),
- decoration->name, transaction_err.buf);
- goto cleanup;
- }
- }
- decoration = decoration->next;
- }
- }
-
- /* In --advance mode, advance the target ref */
- if (result.clean == 1 && advance_name) {
- if (handle_ref_update(ref_mode, transaction, advance_name,
- &last_commit->object.oid,
- &onto->object.oid,
- reflog_msg.buf,
- &transaction_err) < 0) {
+ for (size_t i = 0; i < updates.nr; i++) {
+ ret = handle_ref_update(ref_mode, transaction, updates.items[i].refname,
+ &updates.items[i].new_oid, &updates.items[i].old_oid,
+ reflog_msg.buf, &transaction_err);
+ if (ret) {
ret = error(_("failed to update ref '%s': %s"),
- advance_name, transaction_err.buf);
+ updates.items[i].refname, transaction_err.buf);
goto cleanup;
}
}
/* Commit the ref transaction if we have one */
- if (transaction && result.clean == 1) {
+ if (transaction) {
if (ref_transaction_commit(transaction, &transaction_err)) {
ret = error(_("failed to commit ref transaction: %s"),
transaction_err.buf);
}
}
- merge_finalize(&merge_opt, &result);
- kh_destroy_oid_map(replayed_commits);
- if (update_refs) {
- strset_clear(update_refs);
- free(update_refs);
- }
- ret = result.clean;
-
cleanup:
if (transaction)
ref_transaction_free(transaction);
+ replay_ref_updates_release(&updates);
strbuf_release(&transaction_err);
strbuf_release(&reflog_msg);
release_revisions(&revs);
- free(advance_name);
- /* Return */
- if (ret < 0)
- exit(128);
- return ret ? 0 : 1;
+ return ret ? 1 : 0;
}