replay: extract logic to pick commits

We're about to add a new git-history(1) command that will reuse some of
the same infrastructure as git-replay(1). To prepare for this, extract
the logic to pick a commit into a new "replay.c" file so that it can be
shared between both commands.

Rename the function to have a "replay_" prefix to clearly indicate its
subsystem.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
seen
Patrick Steinhardt 2025-10-01 17:57:28 +02:00 committed by Junio C Hamano
parent 10dd743593
commit d4c6bf316d
5 changed files with 138 additions and 107 deletions

View File

@ -1137,6 +1137,7 @@ LIB_OBJS += refs/ref-cache.o
LIB_OBJS += refspec.o LIB_OBJS += refspec.o
LIB_OBJS += remote.o LIB_OBJS += remote.o
LIB_OBJS += replace-object.o LIB_OBJS += replace-object.o
LIB_OBJS += replay.o
LIB_OBJS += repo-settings.o LIB_OBJS += repo-settings.o
LIB_OBJS += repository.o LIB_OBJS += repository.o
LIB_OBJS += rerere.o LIB_OBJS += rerere.o

View File

@ -2,7 +2,6 @@
* "git replay" builtin command * "git replay" builtin command
*/ */


#define USE_THE_REPOSITORY_VARIABLE
#define DISABLE_SIGN_COMPARE_WARNINGS #define DISABLE_SIGN_COMPARE_WARNINGS


#include "git-compat-util.h" #include "git-compat-util.h"
@ -15,18 +14,12 @@
#include "object-name.h" #include "object-name.h"
#include "parse-options.h" #include "parse-options.h"
#include "refs.h" #include "refs.h"
#include "replay.h"
#include "revision.h" #include "revision.h"
#include "strmap.h" #include "strmap.h"
#include <oidset.h> #include <oidset.h>
#include <tree.h> #include <tree.h>


static const char *short_commit_name(struct repository *repo,
struct commit *commit)
{
return repo_find_unique_abbrev(repo, &commit->object.oid,
DEFAULT_ABBREV);
}

static struct commit *peel_committish(struct repository *repo, const char *name) static struct commit *peel_committish(struct repository *repo, const char *name)
{ {
struct object *obj; struct object *obj;
@ -39,59 +32,6 @@ static struct commit *peel_committish(struct repository *repo, const char *name)
OBJ_COMMIT); OBJ_COMMIT);
} }


static char *get_author(const char *message)
{
size_t len;
const char *a;

a = find_commit_header(message, "author", &len);
if (a)
return xmemdupz(a, len);

return NULL;
}

static struct commit *create_commit(struct repository *repo,
struct tree *tree,
struct commit *based_on,
struct commit *parent)
{
struct object_id ret;
struct object *obj = NULL;
struct commit_list *parents = NULL;
char *author;
char *sign_commit = NULL; /* FIXME: cli users might want to sign again */
struct commit_extra_header *extra = NULL;
struct strbuf msg = STRBUF_INIT;
const char *out_enc = get_commit_output_encoding();
const char *message = repo_logmsg_reencode(repo, based_on,
NULL, out_enc);
const char *orig_message = NULL;
const char *exclude_gpgsig[] = { "gpgsig", NULL };

commit_list_insert(parent, &parents);
extra = read_commit_extra_headers(based_on, exclude_gpgsig);
find_commit_subject(message, &orig_message);
strbuf_addstr(&msg, orig_message);
author = get_author(message);
reset_ident_date();
if (commit_tree_extended(msg.buf, msg.len, &tree->object.oid, parents,
&ret, author, NULL, sign_commit, extra)) {
error(_("failed to write commit object"));
goto out;
}

obj = parse_object(repo, &ret);

out:
repo_unuse_commit_buffer(the_repository, based_on, message);
free_commit_extra_headers(extra);
free_commit_list(parents);
strbuf_release(&msg);
free(author);
return (struct commit *)obj;
}

struct ref_info { struct ref_info {
struct commit *onto; struct commit *onto;
struct strset positive_refs; struct strset positive_refs;
@ -240,50 +180,6 @@ static void determine_replay_mode(struct repository *repo,
strset_clear(&rinfo.positive_refs); 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 repository *repo,
struct commit *pickme,
kh_oid_map_t *replayed_commits,
struct commit *onto,
struct merge_options *merge_opt,
struct merge_result *result)
{
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(repo, replayed_base);
pickme_tree = repo_get_commit_tree(repo, pickme);
base_tree = repo_get_commit_tree(repo, base);

merge_opt->branch1 = short_commit_name(repo, replayed_base);
merge_opt->branch2 = short_commit_name(repo, pickme);
merge_opt->ancestor = xstrfmt("parent of %s", merge_opt->branch2);

merge_incore_nonrecursive(merge_opt,
base_tree,
result->tree,
pickme_tree,
result);

free((char*)merge_opt->ancestor);
merge_opt->ancestor = NULL;
if (!result->clean)
return NULL;
return create_commit(repo, result->tree, pickme, replayed_base);
}

static int add_ref_to_transaction(struct ref_transaction *transaction, static int add_ref_to_transaction(struct ref_transaction *transaction,
const char *refname, const char *refname,
const struct object_id *new_oid, const struct object_id *new_oid,
@ -459,7 +355,7 @@ int cmd_replay(int argc,
if (commit->parents->next) if (commit->parents->next)
die(_("replaying merge commits is not supported yet!")); die(_("replaying merge commits is not supported yet!"));


last_commit = pick_regular_commit(repo, commit, replayed_commits, last_commit = replay_pick_regular_commit(repo, commit, replayed_commits,
onto, &merge_opt, &result); onto, &merge_opt, &result);
if (!last_commit) if (!last_commit)
break; break;

View File

@ -463,6 +463,7 @@ libgit_sources = [
'reftable/writer.c', 'reftable/writer.c',
'remote.c', 'remote.c',
'replace-object.c', 'replace-object.c',
'replay.c',
'repo-settings.c', 'repo-settings.c',
'repository.c', 'repository.c',
'rerere.c', 'rerere.c',

115
replay.c Normal file
View File

@ -0,0 +1,115 @@
#define USE_THE_REPOSITORY_VARIABLE

#include "git-compat-util.h"
#include "commit.h"
#include "environment.h"
#include "gettext.h"
#include "ident.h"
#include "object.h"
#include "object-name.h"
#include "replay.h"
#include "tree.h"

static const char *short_commit_name(struct repository *repo,
struct commit *commit)
{
return repo_find_unique_abbrev(repo, &commit->object.oid,
DEFAULT_ABBREV);
}

static char *get_author(const char *message)
{
size_t len;
const char *a;

a = find_commit_header(message, "author", &len);
if (a)
return xmemdupz(a, len);

return NULL;
}

static struct commit *create_commit(struct repository *repo,
struct tree *tree,
struct commit *based_on,
struct commit *parent)
{
struct object_id ret;
struct object *obj = NULL;
struct commit_list *parents = NULL;
char *author;
char *sign_commit = NULL; /* FIXME: cli users might want to sign again */
struct commit_extra_header *extra = NULL;
struct strbuf msg = STRBUF_INIT;
const char *out_enc = get_commit_output_encoding();
const char *message = repo_logmsg_reencode(repo, based_on,
NULL, out_enc);
const char *orig_message = NULL;
const char *exclude_gpgsig[] = { "gpgsig", NULL };

commit_list_insert(parent, &parents);
extra = read_commit_extra_headers(based_on, exclude_gpgsig);
find_commit_subject(message, &orig_message);
strbuf_addstr(&msg, orig_message);
author = get_author(message);
reset_ident_date();
if (commit_tree_extended(msg.buf, msg.len, &tree->object.oid, parents,
&ret, author, NULL, sign_commit, extra)) {
error(_("failed to write commit object"));
goto out;
}

obj = parse_object(repo, &ret);

out:
repo_unuse_commit_buffer(the_repository, based_on, message);
free_commit_extra_headers(extra);
free_commit_list(parents);
strbuf_release(&msg);
free(author);
return (struct commit *)obj;
}

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);
}

struct commit *replay_pick_regular_commit(struct repository *repo,
struct commit *pickme,
kh_oid_map_t *replayed_commits,
struct commit *onto,
struct merge_options *merge_opt,
struct merge_result *result)
{
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(repo, replayed_base);
pickme_tree = repo_get_commit_tree(repo, pickme);
base_tree = repo_get_commit_tree(repo, base);

merge_opt->branch1 = short_commit_name(repo, replayed_base);
merge_opt->branch2 = short_commit_name(repo, pickme);
merge_opt->ancestor = xstrfmt("parent of %s", merge_opt->branch2);

merge_incore_nonrecursive(merge_opt,
base_tree,
result->tree,
pickme_tree,
result);

free((char*)merge_opt->ancestor);
merge_opt->ancestor = NULL;
if (!result->clean)
return NULL;
return create_commit(repo, result->tree, pickme, replayed_base);
}

18
replay.h Normal file
View File

@ -0,0 +1,18 @@
#ifndef REPLAY_H
#define REPLAY_H

#include "khash.h"
#include "merge-ort.h"
#include "repository.h"

struct commit;
struct tree;

struct commit *replay_pick_regular_commit(struct repository *repo,
struct commit *pickme,
kh_oid_map_t *replayed_commits,
struct commit *onto,
struct merge_options *merge_opt,
struct merge_result *result);

#endif