From patchwork Fri Nov 24 11:10:43 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Christian Couder X-Patchwork-Id: 13467555 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="eumVBbGw" Received: from mail-lj1-x22e.google.com (mail-lj1-x22e.google.com [IPv6:2a00:1450:4864:20::22e]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id A8A24D59 for ; Fri, 24 Nov 2023 03:11:24 -0800 (PST) Received: by mail-lj1-x22e.google.com with SMTP id 38308e7fff4ca-2c88b7e69dfso21729601fa.0 for ; Fri, 24 Nov 2023 03:11:24 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1700824282; x=1701429082; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=qT+fXmuNPz7byh3QAF9QTCPzt8jdU1sLclCdHia5MiQ=; b=eumVBbGwW6MUOR8Pn1pUJChvylUrhaA9EGy4VmPtABSKumNCHdZXww+BC/IFfazV42 ezY4YrCYSDwmDwgL0hSn8jiatoS6MvaV2HFYdhIQ4EjiEC2QEsaqZN0GgPsTieyLf9Jj Sb0WKt5J5HruXo2g/Iykv4aDQRUZB9iD7vmZ5T5B69MqsFpZfQKJO3Zl0WdcaVA/9+8l 48z1JcSUz4bbut7JJn8YRC2u9ESNlpFuQj+T/l2t1Bu85K9oB8JWEnNSgmiKjkwHk7yU 66dsbz4P5SzBgeonC2fQwSozutLnNsQXzGQV3CMyArOYeioMBKLtOwasbCXv2V+cV3HU 4p6w== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1700824282; x=1701429082; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=qT+fXmuNPz7byh3QAF9QTCPzt8jdU1sLclCdHia5MiQ=; b=WC21rr26sAY+YlyV8NkLH/Jv09dcHmjrevcRU3w0fDhpk5h95q3VqxAEuNrs4OuLgO fS7vqX9wdCeccNlMZbOod0Y29HQpP6/sR5Fy94r7hjS39dGI3ASaMcSV6teb1vEK1HP4 ioVsK+qGl/1J+UwF4pQrS7Qg/tS0h4kx0pJA/5CC9lrYaaqqCrKLJQn7uGFHS7AqioOn WZYq40i2moUzhZ4FcGqJylBmS6joWPiyinD3R/WMGDOSzLux1tImm1TF4ZrnD5uvcmdN /xeahge0VMFZD0heXqcn7LIZ7F1jSqlGzm1GCnyD9ntTks1e1ajUKKWGiAzBQZZ7233i AwyQ== X-Gm-Message-State: AOJu0Yw2e9zep3bYyI4+a6i+bgaRT1zszO8ztu0HqgjFHiYZndHbzuoa JugCur6lHwzXuhJatZhKX3QTCzFNmhY= X-Google-Smtp-Source: AGHT+IG8sIm6DYU4mYOdCWvRc5MF6owcDFoc1CZKqZwko0jsZtPTxncPJJlz1h6cgzjDYzImiF3dEA== X-Received: by 2002:a2e:3502:0:b0:2c8:881c:28e3 with SMTP id z2-20020a2e3502000000b002c8881c28e3mr1646992ljz.23.1700824282424; Fri, 24 Nov 2023 03:11:22 -0800 (PST) Received: from localhost.localdomain ([2001:861:3f04:7ca0:c06d:3245:2b4d:9ec5]) by smtp.gmail.com with ESMTPSA id r4-20020a05600c458400b0040648217f4fsm5390295wmo.39.2023.11.24.03.11.21 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 24 Nov 2023 03:11:21 -0800 (PST) From: Christian Couder To: git@vger.kernel.org Cc: Junio C Hamano , Patrick Steinhardt , Johannes Schindelin , Elijah Newren , John Cai , Derrick Stolee , Phillip Wood , Calvin Wan , Toon Claes , Dragan Simic , Linus Arver , Christian Couder Subject: [PATCH v8 14/14] replay: stop assuming replayed branches do not diverge Date: Fri, 24 Nov 2023 12:10:43 +0100 Message-ID: <20231124111044.3426007-15-christian.couder@gmail.com> X-Mailer: git-send-email 2.43.0.14.g93e034faee In-Reply-To: <20231124111044.3426007-1-christian.couder@gmail.com> References: <20231115143327.2441397-1-christian.couder@gmail.com> <20231124111044.3426007-1-christian.couder@gmail.com> Precedence: bulk X-Mailing-List: git@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 From: Elijah Newren The replay command is able to replay multiple branches but when some of them are based on other replayed branches, their commit should be replayed onto already replayed commits. For this purpose, let's store the replayed commit and its original commit in a key value store, so that we can easily find and reuse a replayed commit instead of the original one. Co-authored-by: Christian Couder Signed-off-by: Elijah Newren Signed-off-by: Christian Couder --- builtin/replay.c | 44 ++++++++++++++++++++++++++-------- t/t3650-replay-basics.sh | 52 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 86 insertions(+), 10 deletions(-) diff --git a/builtin/replay.c b/builtin/replay.c index df14657e2f..6bc4b47f09 100644 --- a/builtin/replay.c +++ b/builtin/replay.c @@ -223,20 +223,33 @@ static void determine_replay_mode(struct rev_cmdline_info *cmd_info, strset_clear(&rinfo.positive_refs); } +static struct commit *mapped_commit(kh_oid_map_t *replayed_commits, + struct commit *commit, + struct commit *fallback) +{ + khint_t pos = kh_get_oid_map(replayed_commits, commit->object.oid); + if (pos == kh_end(replayed_commits)) + return fallback; + return kh_value(replayed_commits, pos); +} + static struct commit *pick_regular_commit(struct commit *pickme, - struct commit *last_commit, + kh_oid_map_t *replayed_commits, + struct commit *onto, struct merge_options *merge_opt, struct merge_result *result) { - struct commit *base; + struct commit *base, *replayed_base; struct tree *pickme_tree, *base_tree; base = pickme->parents->item; + replayed_base = mapped_commit(replayed_commits, base, onto); + result->tree = repo_get_commit_tree(the_repository, replayed_base); pickme_tree = repo_get_commit_tree(the_repository, pickme); base_tree = repo_get_commit_tree(the_repository, base); - merge_opt->branch1 = short_commit_name(last_commit); + merge_opt->branch1 = short_commit_name(replayed_base); merge_opt->branch2 = short_commit_name(pickme); merge_opt->ancestor = xstrfmt("parent of %s", merge_opt->branch2); @@ -250,7 +263,7 @@ static struct commit *pick_regular_commit(struct commit *pickme, merge_opt->ancestor = NULL; if (!result->clean) return NULL; - return create_commit(result->tree, pickme, last_commit); + return create_commit(result->tree, pickme, replayed_base); } int cmd_replay(int argc, const char **argv, const char *prefix) @@ -266,6 +279,7 @@ int cmd_replay(int argc, const char **argv, const char *prefix) struct merge_options merge_opt; struct merge_result result; struct strset *update_refs = NULL; + kh_oid_map_t *replayed_commits; int ret = 0; const char * const replay_usage[] = { @@ -363,21 +377,30 @@ int cmd_replay(int argc, const char **argv, const char *prefix) init_merge_options(&merge_opt, the_repository); memset(&result, 0, sizeof(result)); merge_opt.show_rename_progress = 0; - - result.tree = repo_get_commit_tree(the_repository, onto); 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 to root commit is not supported yet!")); if (commit->parents->next) die(_("replaying merge commits is not supported yet!")); - last_commit = pick_regular_commit(commit, last_commit, &merge_opt, &result); + last_commit = pick_regular_commit(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; @@ -406,13 +429,14 @@ int cmd_replay(int argc, const char **argv, const char *prefix) } merge_finalize(&merge_opt, &result); - ret = result.clean; - -cleanup: + kh_destroy_oid_map(replayed_commits); if (update_refs) { strset_clear(update_refs); free(update_refs); } + ret = result.clean; + +cleanup: release_revisions(&revs); /* Return */ diff --git a/t/t3650-replay-basics.sh b/t/t3650-replay-basics.sh index d6286f9580..389670262e 100755 --- a/t/t3650-replay-basics.sh +++ b/t/t3650-replay-basics.sh @@ -143,4 +143,56 @@ test_expect_success 'using replay on bare repo to also rebase a contained branch test_cmp expect result-bare ' +test_expect_success 'using replay to rebase multiple divergent branches' ' + git replay --onto main ^topic1 topic2 topic4 >result && + + test_line_count = 2 result && + cut -f 3 -d " " result >new-branch-tips && + + git log --format=%s $(head -n 1 new-branch-tips) >actual && + test_write_lines E D M L B A >expect && + test_cmp expect actual && + + git log --format=%s $(tail -n 1 new-branch-tips) >actual && + test_write_lines J I M L B A >expect && + test_cmp expect actual && + + printf "update refs/heads/topic2 " >expect && + printf "%s " $(head -n 1 new-branch-tips) >>expect && + git rev-parse topic2 >>expect && + printf "update refs/heads/topic4 " >>expect && + printf "%s " $(tail -n 1 new-branch-tips) >>expect && + git rev-parse topic4 >>expect && + + test_cmp expect result +' + +test_expect_success 'using replay on bare repo to rebase multiple divergent branches, including contained ones' ' + git -C bare replay --contained --onto main ^main topic2 topic3 topic4 >result && + + test_line_count = 4 result && + cut -f 3 -d " " result >new-branch-tips && + + >expect && + for i in 2 1 3 4 + do + printf "update refs/heads/topic$i " >>expect && + printf "%s " $(grep topic$i result | cut -f 3 -d " ") >>expect && + git -C bare rev-parse topic$i >>expect || return 1 + done && + + test_cmp expect result && + + test_write_lines F C M L B A >expect1 && + test_write_lines E D C M L B A >expect2 && + test_write_lines H G F C M L B A >expect3 && + test_write_lines J I M L B A >expect4 && + + for i in 1 2 3 4 + do + git -C bare log --format=%s $(grep topic$i result | cut -f 3 -d " ") >actual && + test_cmp expect$i actual || return 1 + done +' + test_done