diff options
author | John Cai <jcai@gitlab.com> | 2023-02-14 07:34:22 +0300 |
---|---|---|
committer | John Cai <jcai@gitlab.com> | 2023-02-16 07:40:05 +0300 |
commit | 85a97c985f990b037be757aad7027b29ceaa2732 (patch) | |
tree | 0c917b2d3c4a36a2c7624524fc7d5be28f1ed8ad | |
parent | 3ff62a602b1c60a2639880635995fd0f8290e3d5 (diff) |
localrepo: Allow unrelated histories in merge tree
In Git, by default git-merge(1) and git-merge_tree(1) throw an error
when an attempted merge is made on two commits that do not have a shared
ancestor. There are times when we want to allow this. For example, if a
project has a default branch `master` that has some history in it, then
the project decides to create a new branch `main` and wants to merge
`master` back into `main`, the two branches will not have shared
history.
Create an option that allows these kinds of merges.
-rw-r--r-- | internal/git/localrepo/merge.go | 40 | ||||
-rw-r--r-- | internal/git/localrepo/merge_test.go | 61 |
2 files changed, 95 insertions, 6 deletions
diff --git a/internal/git/localrepo/merge.go b/internal/git/localrepo/merge.go index f3dc32a92..60a2608cb 100644 --- a/internal/git/localrepo/merge.go +++ b/internal/git/localrepo/merge.go @@ -12,22 +12,50 @@ import ( "gitlab.com/gitlab-org/gitaly/v15/internal/helper/text" ) +type mergeTreeConfig struct { + allowUnrelatedHistories bool +} + +// MergeTreeOption is a function that sets a config in mergeTreeConfig. +type MergeTreeOption func(*mergeTreeConfig) + +// WithAllowUnrelatedHistories lets MergeTree accept two commits that do not +// share a common ancestor. +func WithAllowUnrelatedHistories() MergeTreeOption { + return func(options *mergeTreeConfig) { + options.allowUnrelatedHistories = true + } +} + // MergeTree calls git-merge-tree(1) with arguments, and parses the results from // stdout. func (repo *Repo) MergeTree( ctx context.Context, ours, theirs string, + mergeTreeOptions ...MergeTreeOption, ) (git.ObjectID, error) { + var config mergeTreeConfig + + for _, option := range mergeTreeOptions { + option(&config) + } + + flags := []git.Option{ + git.Flag{Name: "--write-tree"}, + git.Flag{Name: "--name-only"}, + } + + if config.allowUnrelatedHistories { + flags = append(flags, git.Flag{Name: "--allow-unrelated-histories"}) + } + var stdout, stderr bytes.Buffer err := repo.ExecAndWait( ctx, git.Command{ - Name: "merge-tree", - Flags: []git.Option{ - git.Flag{Name: "--write-tree"}, - git.Flag{Name: "--name-only"}, - }, - Args: []string{ours, theirs}, + Name: "merge-tree", + Flags: flags, + Args: []string{ours, theirs}, }, git.WithStderr(&stderr), git.WithStdout(&stdout), diff --git a/internal/git/localrepo/merge_test.go b/internal/git/localrepo/merge_test.go index f4755635b..11112d72d 100644 --- a/internal/git/localrepo/merge_test.go +++ b/internal/git/localrepo/merge_test.go @@ -210,6 +210,67 @@ func TestMergeTree(t *testing.T) { } }) } + + t.Run("allow unrelated histories", func(t *testing.T) { + ctx := testhelper.Context(t) + + repoProto, repoPath := gittest.CreateRepository(t, ctx, cfg, gittest.CreateRepositoryConfig{ + SkipCreationViaService: true, + }) + repo := NewTestRepo(t, cfg, repoProto) + + tree1 := gittest.WriteTree(t, cfg, repoPath, []gittest.TreeEntry{ + { + Mode: "100644", + Path: "file1", + Content: "foo", + }, + }) + tree2 := gittest.WriteTree(t, cfg, repoPath, []gittest.TreeEntry{ + { + Mode: "100644", + Path: "file2", + Content: "baz", + }, + }) + ours := gittest.WriteCommit(t, cfg, repoPath, + gittest.WithTree(tree1), + gittest.WithAuthorName("Woody"), + gittest.WithCommitterName("Woody"), + ) + theirs := gittest.WriteCommit(t, cfg, repoPath, + gittest.WithTree(tree2), + gittest.WithAuthorName("Buzz"), + gittest.WithCommitterName("Buzz"), + ) + + mergeTreeResult, err := repo.MergeTree( + ctx, + string(ours), + string(theirs), + WithAllowUnrelatedHistories(), + ) + require.NoError(t, err) + + gittest.RequireTree( + t, + cfg, + repoPath, + string(mergeTreeResult), + []gittest.TreeEntry{ + { + Mode: "100644", + Path: "file1", + Content: "foo", + }, + { + Mode: "100644", + Path: "file2", + Content: "baz", + }, + }, + ) + }) } func TestParseResult(t *testing.T) { |