|
5 | 5 | package integrations
|
6 | 6 |
|
7 | 7 | import (
|
| 8 | + "bytes" |
| 9 | + "fmt" |
8 | 10 | "net/http"
|
9 | 11 | "net/http/httptest"
|
10 | 12 | "net/url"
|
| 13 | + "os" |
11 | 14 | "path"
|
12 | 15 | "strings"
|
13 | 16 | "testing"
|
| 17 | + "time" |
14 | 18 |
|
15 | 19 | "code.gitea.io/gitea/models"
|
| 20 | + "code.gitea.io/gitea/modules/git" |
| 21 | + api "code.gitea.io/gitea/modules/structs" |
16 | 22 | "code.gitea.io/gitea/modules/test"
|
| 23 | + "code.gitea.io/gitea/services/pull" |
17 | 24 |
|
18 | 25 | "github.com/stretchr/testify/assert"
|
19 | 26 | "github.com/unknwon/i18n"
|
@@ -202,3 +209,133 @@ func TestCantMergeWorkInProgress(t *testing.T) {
|
202 | 209 | assert.Equal(t, replacer.Replace(expected), text, "Unable to find WIP text")
|
203 | 210 | })
|
204 | 211 | }
|
| 212 | + |
| 213 | +func TestCantMergeConflict(t *testing.T) { |
| 214 | + onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { |
| 215 | + prepareTestEnv(t) |
| 216 | + session := loginUser(t, "user1") |
| 217 | + testRepoFork(t, session, "user2", "repo1", "user1", "repo1") |
| 218 | + testEditFileToNewBranch(t, session, "user1", "repo1", "master", "conflict", "README.md", "Hello, World (Edited Once)\n") |
| 219 | + testEditFileToNewBranch(t, session, "user1", "repo1", "master", "base", "README.md", "Hello, World (Edited Twice)\n") |
| 220 | + |
| 221 | + // Use API to create a conflicting pr |
| 222 | + token := getTokenForLoggedInUser(t, session) |
| 223 | + req := NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls?token=%s", "user1", "repo1", token), &api.CreatePullRequestOption{ |
| 224 | + Head: "conflict", |
| 225 | + Base: "base", |
| 226 | + Title: "create a conflicting pr", |
| 227 | + }) |
| 228 | + session.MakeRequest(t, req, 201) |
| 229 | + |
| 230 | + // Now this PR will be marked conflict - or at least a race will do - so drop down to pure code at this point... |
| 231 | + user1 := models.AssertExistsAndLoadBean(t, &models.User{ |
| 232 | + Name: "user1", |
| 233 | + }).(*models.User) |
| 234 | + repo1 := models.AssertExistsAndLoadBean(t, &models.Repository{ |
| 235 | + OwnerID: user1.ID, |
| 236 | + Name: "repo1", |
| 237 | + }).(*models.Repository) |
| 238 | + |
| 239 | + pr := models.AssertExistsAndLoadBean(t, &models.PullRequest{ |
| 240 | + HeadRepoID: repo1.ID, |
| 241 | + BaseRepoID: repo1.ID, |
| 242 | + HeadBranch: "conflict", |
| 243 | + BaseBranch: "base", |
| 244 | + }).(*models.PullRequest) |
| 245 | + |
| 246 | + gitRepo, err := git.OpenRepository(models.RepoPath(user1.Name, repo1.Name)) |
| 247 | + assert.NoError(t, err) |
| 248 | + |
| 249 | + err = pull.Merge(pr, user1, gitRepo, models.MergeStyleMerge, "CONFLICT") |
| 250 | + assert.Error(t, err, "Merge should return an error due to conflict") |
| 251 | + assert.True(t, models.IsErrMergeConflicts(err), "Merge error is not a conflict error") |
| 252 | + |
| 253 | + err = pull.Merge(pr, user1, gitRepo, models.MergeStyleRebase, "CONFLICT") |
| 254 | + assert.Error(t, err, "Merge should return an error due to conflict") |
| 255 | + assert.True(t, models.IsErrRebaseConflicts(err), "Merge error is not a conflict error") |
| 256 | + }) |
| 257 | +} |
| 258 | + |
| 259 | +func TestCantMergeUnrelated(t *testing.T) { |
| 260 | + onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { |
| 261 | + prepareTestEnv(t) |
| 262 | + session := loginUser(t, "user1") |
| 263 | + testRepoFork(t, session, "user2", "repo1", "user1", "repo1") |
| 264 | + testEditFileToNewBranch(t, session, "user1", "repo1", "master", "base", "README.md", "Hello, World (Edited Twice)\n") |
| 265 | + |
| 266 | + // Now we want to create a commit on a branch that is totally unrelated to our current head |
| 267 | + // Drop down to pure code at this point |
| 268 | + user1 := models.AssertExistsAndLoadBean(t, &models.User{ |
| 269 | + Name: "user1", |
| 270 | + }).(*models.User) |
| 271 | + repo1 := models.AssertExistsAndLoadBean(t, &models.Repository{ |
| 272 | + OwnerID: user1.ID, |
| 273 | + Name: "repo1", |
| 274 | + }).(*models.Repository) |
| 275 | + path := models.RepoPath(user1.Name, repo1.Name) |
| 276 | + |
| 277 | + _, err := git.NewCommand("read-tree", "--empty").RunInDir(path) |
| 278 | + assert.NoError(t, err) |
| 279 | + |
| 280 | + stdin := bytes.NewBufferString("Unrelated File") |
| 281 | + var stdout strings.Builder |
| 282 | + err = git.NewCommand("hash-object", "-w", "--stdin").RunInDirFullPipeline(path, &stdout, nil, stdin) |
| 283 | + assert.NoError(t, err) |
| 284 | + sha := strings.TrimSpace(stdout.String()) |
| 285 | + |
| 286 | + _, err = git.NewCommand("update-index", "--add", "--replace", "--cacheinfo", "100644", sha, "somewher-over-the-rainbow").RunInDir(path) |
| 287 | + assert.NoError(t, err) |
| 288 | + |
| 289 | + treeSha, err := git.NewCommand("write-tree").RunInDir(path) |
| 290 | + assert.NoError(t, err) |
| 291 | + treeSha = strings.TrimSpace(treeSha) |
| 292 | + |
| 293 | + commitTimeStr := time.Now().Format(time.RFC3339) |
| 294 | + doerSig := user1.NewGitSig() |
| 295 | + env := append(os.Environ(), |
| 296 | + "GIT_AUTHOR_NAME="+doerSig.Name, |
| 297 | + "GIT_AUTHOR_EMAIL="+doerSig.Email, |
| 298 | + "GIT_AUTHOR_DATE="+commitTimeStr, |
| 299 | + "GIT_COMMITTER_NAME="+doerSig.Name, |
| 300 | + "GIT_COMMITTER_EMAIL="+doerSig.Email, |
| 301 | + "GIT_COMMITTER_DATE="+commitTimeStr, |
| 302 | + ) |
| 303 | + |
| 304 | + messageBytes := new(bytes.Buffer) |
| 305 | + _, _ = messageBytes.WriteString("Unrelated") |
| 306 | + _, _ = messageBytes.WriteString("\n") |
| 307 | + |
| 308 | + stdout.Reset() |
| 309 | + err = git.NewCommand("commit-tree", treeSha).RunInDirTimeoutEnvFullPipeline(env, -1, path, &stdout, nil, messageBytes) |
| 310 | + assert.NoError(t, err) |
| 311 | + commitSha := strings.TrimSpace(stdout.String()) |
| 312 | + |
| 313 | + _, err = git.NewCommand("branch", "unrelated", commitSha).RunInDir(path) |
| 314 | + assert.NoError(t, err) |
| 315 | + |
| 316 | + testEditFileToNewBranch(t, session, "user1", "repo1", "master", "conflict", "README.md", "Hello, World (Edited Once)\n") |
| 317 | + |
| 318 | + // Use API to create a conflicting pr |
| 319 | + token := getTokenForLoggedInUser(t, session) |
| 320 | + req := NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls?token=%s", "user1", "repo1", token), &api.CreatePullRequestOption{ |
| 321 | + Head: "unrelated", |
| 322 | + Base: "base", |
| 323 | + Title: "create an unrelated pr", |
| 324 | + }) |
| 325 | + session.MakeRequest(t, req, 201) |
| 326 | + |
| 327 | + // Now this PR could be marked conflict - or at least a race may occur - so drop down to pure code at this point... |
| 328 | + gitRepo, err := git.OpenRepository(path) |
| 329 | + assert.NoError(t, err) |
| 330 | + pr := models.AssertExistsAndLoadBean(t, &models.PullRequest{ |
| 331 | + HeadRepoID: repo1.ID, |
| 332 | + BaseRepoID: repo1.ID, |
| 333 | + HeadBranch: "unrelated", |
| 334 | + BaseBranch: "base", |
| 335 | + }).(*models.PullRequest) |
| 336 | + |
| 337 | + err = pull.Merge(pr, user1, gitRepo, models.MergeStyleMerge, "UNRELATED") |
| 338 | + assert.Error(t, err, "Merge should return an error due to unrelated") |
| 339 | + assert.True(t, models.IsErrMergeUnrelatedHistories(err), "Merge error is not a unrelated histories error") |
| 340 | + }) |
| 341 | +} |
0 commit comments