Files
git/replay.h
Toon Claes 6af34ada96 replay: offer an option to linearize the commit topology
One of the stated goals of git-replay(1) is to allow implementing the
git-rebase(1) functionality on the server side.

The default mode of git-rebase(1) is to act as if `--no-rebase-merges`
was given. This mode drops merge commits instead of replaying them, and
linearizes the history into a sequence of regular (single-parent)
commits.

Add option `--linearize` to git-replay(1) to do the same. Each replayed
commit is stacked on top of the previously replayed one. When a merge is
encountered, the commits reachable from all of its sides are replayed
into the single line and the merge itself is dropped.

If a ref was pointing to a merge commit, that ref is updated to the
merge's last replayed ancestor.

git-replay(1) accepts multiple revision ranges, for example:

    $ git replay --onto main topic1 topic2

Without `--linearize` this replays 'topic1' and 'topic2' onto 'main'
independently and updates both refs.

For now this is disallowed with option `--linearize`. Linearizing more
than one branch at once would concatenate unrelated histories into a
single line, and update each branch to some point in that line. That
won't be the result most users want, especially because the order
depends on the order of the revision walk, not the order of the branch
names on the command line.

For the same reason disallow the use of `--contained` with
`--linearize`.

Users who want to linearize multiple branches are advised to do this in
separate git-replay(1) invocations. Linearizing multiple branches at
once might be added later.

Note that `--linearize` is not modeled after git-rebase(1)'s
`--rebase-merges[=<mode>]` interface. Recreating merges, by preserving
their topology, is a distinct operation that would be a separate mode.
`--linearize` only drops merges and replays commits linearly. So
git-replay(1) uses its own option rather than reusing that interface.

Based-on-patches-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Signed-off-by: Toon Claes <toon@iotcl.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2026-07-28 11:27:41 -07:00

105 lines
2.7 KiB
C

#ifndef REPLAY_H
#define REPLAY_H
#include "hash.h"
struct repository;
struct rev_info;
/*
* Controls what happens when a replayed commit becomes empty (i.e. its tree
* is identical to its parent's tree after the replay).
*/
enum replay_empty_commit_action {
/* Silently discard the empty commit. */
REPLAY_EMPTY_COMMIT_DROP,
/* Keep the empty commit as-is. */
REPLAY_EMPTY_COMMIT_KEEP,
/* Abort with an error. */
REPLAY_EMPTY_COMMIT_ABORT,
};
/*
* A set of options that can be passed to `replay_revisions()`.
*/
struct replay_revisions_options {
/*
* Starting point at which to create the new commits; must be a branch
* name. The branch will be updated to point to the rewritten commits.
* This option is mutually exclusive with `onto` and `revert`.
*/
const char *advance;
/*
* Starting point at which to create the new commits; must be a
* committish. References pointing at descendants of `onto` will be
* updated to point to the new commits.
*/
const char *onto;
/*
* Reference to update with the result of the replay. This will not
* update any refs from `onto`, `advance`, or `revert`. Ignores
* `contained`.
*/
const char *ref;
/*
* Starting point at which to create revert commits; must be a branch
* name. The branch will be updated to point to the revert commits.
* This option is mutually exclusive with `onto` and `advance`.
*/
const char *revert;
/*
* Update branches that point at commits in the given revision range.
* Requires `onto` to be set.
*/
int contained;
/*
* Controls what to do when a replayed commit becomes empty.
* Defaults to REPLAY_EMPTY_COMMIT_DROP.
*/
enum replay_empty_commit_action empty;
/*
* Whether to linearize the commits (i.e. drop merge commits).
*/
int linearize;
};
/* This struct is used as an out-parameter by `replay_revisions()`. */
struct replay_result {
/*
* The set of reference updates that are caused by replaying the
* commits.
*/
struct replay_ref_update {
char *refname;
struct object_id old_oid;
struct object_id new_oid;
} *updates;
size_t updates_nr, updates_alloc;
};
void replay_result_release(struct replay_result *result);
void replay_result_queue_update(struct replay_result *result,
const char *refname,
const struct object_id *old_oid,
const struct object_id *new_oid);
/*
* Replay a set of commits onto a new location. Leaves both the working tree,
* index and references untouched. Reference updates caused by the replay will
* be recorded in the `updates` out pointer.
*
* Returns 0 on success, 1 on conflict and a negative error code otherwise.
*/
int replay_revisions(struct rev_info *revs,
struct replay_revisions_options *opts,
struct replay_result *out);
#endif