On April 13, 2022 12:44 PM, Edmundo Carmona Antoranz wrote: >Let me explain with an easy-to-follow example: > >$ git checkout v2.35.0 >. >. >. >HEAD is now at 89bece5c8c Git 2.35 >$ git commit --amend --no-edit >[detached HEAD c58a5e5621] Git 2.35 > Author: Junio C Hamano <someone@somewhere> > Date: Mon Jan 24 09:25:25 2022 -0800 > 2 files changed, 11 insertions(+), 1 deletion(-) $ git rebase --rebase-merges --onto >HEAD v2.35.0 v2.36.0-rc1 Auto-merging GIT-VERSION-GEN CONFLICT (content): >Merge conflict in GIT-VERSION-GEN CONFLICT (content): Merge conflict in >RelNotes >error: could not apply 4c53a8c20f... Git 2.35.1 >hint: Resolve all conflicts manually, mark them as resolved with >hint: "git add/rm <conflicted_files>", then run "git rebase --continue". >hint: You can instead skip this commit: run "git rebase --skip". >hint: To abort and get back to the state before "git rebase", run "git rebase -- >abort". >Could not apply 4c53a8c20f... Git 2.35.1 > >If HEAD and v2.35.0 share the same tree, it _should_ be possible to recreate the >commits that make up the range v2.35.0..v2.36.0-rc1 on top of HEAD without >requiring any real "rebasing". Just creating new revisions with the same >information except for different parents (and possibly a committer?). > >This is what git replay does. To achieve the same in this example: > >$ git checkout v2.35.0 >. >. >. >HEAD is now at 89bece5c8c Git 2.35 >$ git commit --amend --no-edit >[detached HEAD c682d8a22e] Git 2.35 > Author: Junio C Hamano <someone@somewhere> > Date: Mon Jan 24 09:25:25 2022 -0800 > 2 files changed, 11 insertions(+), 1 deletion(-) $ git replay HEAD v2.35.0 v2.36.0-rc1 >8312ecf6404ab1bacd5521a2d8681a2410d13ede > >The ID that is printed out is the equivalent of V2.36.0-rc1 after replaying. > >This is a RFC because: >- Perhaps it is already possible to do it with git rebase > to achieve the same? But I haven't seen a recipe that > gets it done in stackoverflow, at least. >- If it is not possible, I think getting this logic in rebase > (with a flag, for example --replay) makes sense. > >Let me know what you think. >Interesting? Not? >Keep it as a builtin (polishing it, it's just a rough cut at the time) or get it into >rebase? >--- > Makefile | 1 + > builtin.h | 1 + > builtin/replay.c | 148 >+++++++++++++++++++++++++++++++++++++++++++++++ > git.c | 1 + > 4 files changed, 151 insertions(+) > create mode 100644 builtin/replay.c > >diff --git a/Makefile b/Makefile >index f8bccfab5e..71924d0c43 100644 >--- a/Makefile >+++ b/Makefile >@@ -1194,6 +1194,7 @@ BUILTIN_OBJS += builtin/remote-fd.o BUILTIN_OBJS += >builtin/remote.o BUILTIN_OBJS += builtin/repack.o BUILTIN_OBJS += >builtin/replace.o >+BUILTIN_OBJS += builtin/replay.o > BUILTIN_OBJS += builtin/rerere.o > BUILTIN_OBJS += builtin/reset.o > BUILTIN_OBJS += builtin/rev-list.o >diff --git a/builtin.h b/builtin.h >index 40e9ecc848..0c1915c4c9 100644 >--- a/builtin.h >+++ b/builtin.h >@@ -207,6 +207,7 @@ int cmd_remote(int argc, const char **argv, const char >*prefix); int cmd_remote_ext(int argc, const char **argv, const char *prefix); int >cmd_remote_fd(int argc, const char **argv, const char *prefix); int >cmd_repack(int argc, const char **argv, const char *prefix); >+int cmd_replay(int argc, const char **argv, const char *prefix); > int cmd_rerere(int argc, const char **argv, const char *prefix); int cmd_reset(int >argc, const char **argv, const char *prefix); int cmd_restore(int argc, const char >**argv, const char *prefix); diff --git a/builtin/replay.c b/builtin/replay.c new file >mode 100644 index 0000000000..ed970fa057 >--- /dev/null >+++ b/builtin/replay.c >@@ -0,0 +1,148 @@ >+/* >+ * "git replay" builtin command >+ * >+ * Copyright (c) 2022 Edmundo Carmona Antoranz >+ * Released under the terms of GPLv2 >+ */ >+ >+#include "builtin.h" >+#include "revision.h" >+#include "commit.h" >+#include "cache.h" >+ >+static struct commit **new_commits; >+static unsigned long mappings_size = 0; static struct commit_list >+*old_commits = NULL; >+ >+static unsigned int replay_indexof(struct commit *commit, >+ struct commit_list *list) >+{ >+ int res; >+ if (list == NULL) >+ return -1; >+ if (!oidcmp(&list->item->object.oid, >+ &commit->object.oid)) >+ return 0; >+ res = replay_indexof(commit, list->next); >+ return res < 0 ? res : res + 1; >+} >+ >+static struct commit *replay_find_commit(const char *name) { >+ struct commit *commit = lookup_commit_reference_by_name(name); >+ if (!commit) >+ die(_("no such branch/commit '%s'"), name); >+ return commit; >+} >+ >+static struct commit* replay_commit(struct commit * orig_commit) { >+ struct pretty_print_context ctx = {0}; >+ struct strbuf body = STRBUF_INIT; >+ struct strbuf author = STRBUF_INIT; >+ struct strbuf committer = STRBUF_INIT; >+ struct object_id new_commit_oid; >+ struct commit *new_commit; >+ >+ struct commit_list *new_parents_head = NULL; >+ struct commit_list **new_parents = &new_parents_head; >+ struct commit_list *parents = orig_commit->parents; >+ while (parents) { >+ struct commit *parent = parents->item; >+ int commit_index; >+ struct commit *new_parent; >+ >+ commit_index = replay_indexof(parent, old_commits); >+ >+ if (commit_index < 0) >+ // won't be replayed, use the original parent >+ new_parent = parent; >+ else { >+ // it might have been translated already >+ if (!new_commits[commit_index]) >+ new_commits[commit_index] = >replay_commit(parent); >+ new_parent = new_commits[commit_index]; >+ } >+ new_parents = commit_list_append(new_parent, new_parents); >+ parents = parents->next; >+ } >+ >+ format_commit_message(orig_commit, "%B", &body, &ctx); >+ // TODO timezones >+ format_commit_message(orig_commit, "%an <%ae> %at +0000", >&author, &ctx); >+ // TODO consider committer (control with an option) >+ format_commit_message(orig_commit, "%cn <%ce> %ct +0000", >&committer, >+&ctx); >+ >+ commit_tree_extended(body.buf, >+ body.len, >+ get_commit_tree_oid(orig_commit), >+ new_parents_head, >+ &new_commit_oid, >+ author.buf, >+ committer.buf, >+ NULL, NULL); >+ >+ new_commit = lookup_commit_or_die(&new_commit_oid, >+ "new commit"); >+ >+ strbuf_release(&author); >+ strbuf_release(&body); >+ strbuf_release(&committer); >+ >+ return new_commit; >+} >+ >+static struct commit* replay(struct commit *new_base, struct commit *old_base, >+ struct commit *tip) >+{ >+ struct rev_info revs; >+ struct commit *commit; >+ >+ init_revisions(&revs, NULL); >+ >+ old_base->object.flags |= UNINTERESTING; >+ add_pending_object(&revs, &old_base->object, "old-base"); >+ add_pending_object(&revs, &tip->object, "tip"); >+ >+ if (prepare_revision_walk(&revs)) >+ die("Could not get revisions to replay"); >+ >+ while ((commit = get_revision(&revs)) != NULL) >+ commit_list_insert(commit, &old_commits); >+ >+ // save the mapping between the new and the old base >+ commit_list_insert(old_base, &old_commits); >+ mappings_size = commit_list_count(old_commits); >+ new_commits = calloc(mappings_size, sizeof(struct commit)); >+ new_commits[replay_indexof(old_base, old_commits)] = new_base; >+ >+ return replay_commit(tip); >+} >+ >+ >+int cmd_replay(int argc, const char **argv, const char *prefix) { >+ struct commit *new_base; >+ struct commit *old_base; >+ struct commit *tip; >+ struct commit *new_tip; >+ >+ if (argc < 4) { >+ die("Not enough parameters"); >+ } >+ >+ new_base = replay_find_commit(argv[1]); >+ old_base = replay_find_commit(argv[2]); >+ tip = replay_find_commit(argv[3]); >+ >+ if (oidcmp(get_commit_tree_oid(new_base), >+ get_commit_tree_oid(old_base))) >+ die("The old and the new base do not have the same tree"); >+ >+ // get the list of revisions between old_base and tip >+ new_tip = replay(new_base, old_base, tip); >+ >+ printf("%s\n", oid_to_hex(&new_tip->object.oid)); >+ >+ return 0; >+} >diff --git a/git.c b/git.c >index 3d8e48cf55..14de8d666f 100644 >--- a/git.c >+++ b/git.c >@@ -590,6 +590,7 @@ static struct cmd_struct commands[] = { > { "remote-fd", cmd_remote_fd, NO_PARSEOPT }, > { "repack", cmd_repack, RUN_SETUP }, > { "replace", cmd_replace, RUN_SETUP }, >+ { "replay", cmd_replay, RUN_SETUP }, > { "rerere", cmd_rerere, RUN_SETUP }, > { "reset", cmd_reset, RUN_SETUP }, > { "restore", cmd_restore, RUN_SETUP | NEED_WORK_TREE }, >-- >2.35.1 I'm sorry if I'm missing something here but how is this different from cherry-pick A..B? --Randall