Add merge style fast-forward-only (#28954)

With this option, it is possible to require a linear commit history with
the following benefits over the next best option `Rebase+fast-forward`:
The original commits continue existing, with the original signatures
continuing to stay valid instead of being rewritten, there is no merge
commit, and reverting commits becomes easier.

Closes #24906
This commit is contained in:
Chris Copeland
2024-02-12 14:37:23 -08:00
committed by GitHub
parent f9c3459831
commit 47b5965862
25 changed files with 204 additions and 11 deletions

View File

@ -65,6 +65,7 @@ func getRepoEditOptionFromRepo(repo *repo_model.Repository) *api.EditRepoOption
allowRebase := false
allowRebaseMerge := false
allowSquash := false
allowFastForwardOnly := false
if unit, err := repo.GetUnit(db.DefaultContext, unit_model.TypePullRequests); err == nil {
config := unit.PullRequestsConfig()
hasPullRequests = true
@ -73,6 +74,7 @@ func getRepoEditOptionFromRepo(repo *repo_model.Repository) *api.EditRepoOption
allowRebase = config.AllowRebase
allowRebaseMerge = config.AllowRebaseMerge
allowSquash = config.AllowSquash
allowFastForwardOnly = config.AllowFastForwardOnly
}
archived := repo.IsArchived
return &api.EditRepoOption{
@ -92,6 +94,7 @@ func getRepoEditOptionFromRepo(repo *repo_model.Repository) *api.EditRepoOption
AllowRebase: &allowRebase,
AllowRebaseMerge: &allowRebaseMerge,
AllowSquash: &allowSquash,
AllowFastForwardOnly: &allowFastForwardOnly,
Archived: &archived,
}
}

View File

@ -365,6 +365,90 @@ func TestCantMergeUnrelated(t *testing.T) {
})
}
func TestFastForwardOnlyMerge(t *testing.T) {
onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
session := loginUser(t, "user1")
testRepoFork(t, session, "user2", "repo1", "user1", "repo1")
testEditFileToNewBranch(t, session, "user1", "repo1", "master", "update", "README.md", "Hello, World 2\n")
// Use API to create a pr from update to master
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository)
req := NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls", "user1", "repo1"), &api.CreatePullRequestOption{
Head: "update",
Base: "master",
Title: "create a pr that can be fast-forward-only merged",
}).AddTokenAuth(token)
session.MakeRequest(t, req, http.StatusCreated)
user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{
Name: "user1",
})
repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{
OwnerID: user1.ID,
Name: "repo1",
})
pr := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{
HeadRepoID: repo1.ID,
BaseRepoID: repo1.ID,
HeadBranch: "update",
BaseBranch: "master",
})
gitRepo, err := git.OpenRepository(git.DefaultContext, repo_model.RepoPath(user1.Name, repo1.Name))
assert.NoError(t, err)
err = pull.Merge(context.Background(), pr, user1, gitRepo, repo_model.MergeStyleFastForwardOnly, "", "FAST-FORWARD-ONLY", false)
assert.NoError(t, err)
gitRepo.Close()
})
}
func TestCantFastForwardOnlyMergeDiverging(t *testing.T) {
onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
session := loginUser(t, "user1")
testRepoFork(t, session, "user2", "repo1", "user1", "repo1")
testEditFileToNewBranch(t, session, "user1", "repo1", "master", "diverging", "README.md", "Hello, World diverged\n")
testEditFile(t, session, "user1", "repo1", "master", "README.md", "Hello, World 2\n")
// Use API to create a pr from diverging to update
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository)
req := NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls", "user1", "repo1"), &api.CreatePullRequestOption{
Head: "diverging",
Base: "master",
Title: "create a pr from a diverging branch",
}).AddTokenAuth(token)
session.MakeRequest(t, req, http.StatusCreated)
user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{
Name: "user1",
})
repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{
OwnerID: user1.ID,
Name: "repo1",
})
pr := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{
HeadRepoID: repo1.ID,
BaseRepoID: repo1.ID,
HeadBranch: "diverging",
BaseBranch: "master",
})
gitRepo, err := git.OpenRepository(git.DefaultContext, repo_model.RepoPath(user1.Name, repo1.Name))
assert.NoError(t, err)
err = pull.Merge(context.Background(), pr, user1, gitRepo, repo_model.MergeStyleFastForwardOnly, "", "DIVERGING", false)
assert.Error(t, err, "Merge should return an error due to being for a diverging branch")
assert.True(t, models.IsErrMergeDivergingFastForwardOnly(err), "Merge error is not a diverging fast-forward-only error")
gitRepo.Close()
})
}
func TestConflictChecking(t *testing.T) {
onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})