From cc8efb02ff49a95eff1568b2e7936d22eea5852a Mon Sep 17 00:00:00 2001 From: Toon Claes Date: Fri, 14 Nov 2025 12:41:38 +0100 Subject: [PATCH] WIP: implement git-replay --- internal/git/gitcmd/command_description.go | 3 + internal/git/localrepo/rebase.go | 74 ++++++++++++++++++++-- 2 files changed, 72 insertions(+), 5 deletions(-) diff --git a/internal/git/gitcmd/command_description.go b/internal/git/gitcmd/command_description.go index 33c9a8e4d2..81b30c189f 100644 --- a/internal/git/gitcmd/command_description.go +++ b/internal/git/gitcmd/command_description.go @@ -259,6 +259,9 @@ var commandDescriptions = map[string]commandDescription{ "refs": { flags: scNoRefUpdates, }, + "replay": { + flags: scNoRefUpdates, + }, "range-diff": { flags: scNoRefUpdates | scNoEndOfOptions, }, diff --git a/internal/git/localrepo/rebase.go b/internal/git/localrepo/rebase.go index 62023922c6..1f8462e828 100644 --- a/internal/git/localrepo/rebase.go +++ b/internal/git/localrepo/rebase.go @@ -74,20 +74,84 @@ func (repo *Repo) Rebase(ctx context.Context, upstream, branch string, options . return upstreamOID, nil } - newOID, err := repo.rebaseUsingMergeTree(ctx, config, upstreamOID, branchOID) + objectHash, err := repo.ObjectHash(ctx) if err != nil { - return "", structerr.NewInternal("rebase using merge-tree: %w", err) + return "", structerr.NewInternal("getting object hash %w", err) + } + + var newOID git.ObjectID + if true { + baseOID, err := objectHash.FromHex(mergeBase) + if err != nil { + return "", structerr.NewInternal("parse merge-base: %w", err) + } + + newOID, err = repo.rebaseUsingReplay(ctx, config, upstreamOID, branchOID, baseOID, objectHash) + if err != nil { + return "", structerr.NewInternal("rebase using replay: %w", err) + } + } else { + newOID, err = repo.rebaseUsingMergeTree(ctx, config, upstreamOID, branchOID, objectHash) + if err != nil { + return "", structerr.NewInternal("rebase using merge-tree: %w", err) + } } return newOID, nil } -func (repo *Repo) rebaseUsingMergeTree(ctx context.Context, cfg rebaseConfig, upstreamOID, branchOID git.ObjectID) (git.ObjectID, error) { - objectHash, err := repo.ObjectHash(ctx) +func (repo *Repo) rebaseUsingReplay(ctx context.Context, cfg rebaseConfig, upstreamOID, branchOID, baseOID git.ObjectID, objectHash git.ObjectHash) (git.ObjectID, error) { + flags := []gitcmd.Option{ + gitcmd.ValueFlag{Name: "--onto", Value: upstreamOID.String()}, + } + + committer := cfg.committer + if committer == nil { + // TODO committer = getSignatureFromCommitAuthor(theirsCommit.GetCommitter()) + } + + env := []string{ + fmt.Sprintf("GIT_COMMITTER_DATE=%s", git.FormatTime(committer.When)), + fmt.Sprintf("GIT_COMMITTER_NAME=%s", committer.Name), + fmt.Sprintf("GIT_COMMITTER_EMAIL=%s", committer.Email), + } + + var stderr bytes.Buffer + cmd, err := repo.Exec(ctx, gitcmd.Command{ + Name: "replay", + Flags: flags, + Args: []string{fmt.Sprintf("%s...%s", baseOID, branchOID)}, + }, gitcmd.WithStderr(&stderr), gitcmd.WithSetupStdout(), gitcmd.WithEnv(env...)) if err != nil { - return "", structerr.NewInternal("getting object hash %w", err) + return "", structerr.NewInternal("start git rev-list: %w", err) } + var newOID git.ObjectID + scanner := bufio.NewScanner(cmd) + for scanner.Scan() { + // if len(newHash) > 0 { + // return "", structerr.NewInternal("rebase replay only one ref expected") + // } + parts := strings.Split(strings.TrimSpace(scanner.Text()), " ") + if parts[0] != "update" { + return "", structerr.NewInternal("rebase replay expected 'update' instead of %q", parts[0]) + } + newOID, err = objectHash.FromHex(parts[2]) + if err != nil { + return "", structerr.NewInternal("rebase replay new OID %q not parsed: %w", parts[2], err) + } + } + if err := scanner.Err(); err != nil { + return "", fmt.Errorf("scanning rev-list output: %w", err) + } + if err := cmd.Wait(); err != nil { + return "", structerr.NewInternal("git replay: %w [%v]", err, stderr.String()).WithMetadata("stderr", stderr.String()) + } + + return newOID, nil +} + +func (repo *Repo) rebaseUsingMergeTree(ctx context.Context, cfg rebaseConfig, upstreamOID, branchOID git.ObjectID, objectHash git.ObjectHash) (git.ObjectID, error) { // Flags of git-rev-list to get the pick-only todo_list for a rebase. // Currently we drop clean cherry-picks and merge commits, which is // also what git2go does. -- GitLab