[RFC] introducing git replay

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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




[Index of Archives]     [Linux Kernel Development]     [Gcc Help]     [IETF Annouce]     [DCCP]     [Netdev]     [Networking]     [Security]     [V4L]     [Bugtraq]     [Yosemite]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Linux SCSI]     [Fedora Users]

  Powered by Linux