gitea源码

git_general_test.go 38KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941
  1. // Copyright 2017 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package integration
  4. import (
  5. "context"
  6. "encoding/hex"
  7. "fmt"
  8. "io"
  9. mathRand "math/rand/v2"
  10. "net/http"
  11. "net/url"
  12. "os"
  13. "os/exec"
  14. "path"
  15. "path/filepath"
  16. "slices"
  17. "strconv"
  18. "strings"
  19. "testing"
  20. "time"
  21. auth_model "code.gitea.io/gitea/models/auth"
  22. issues_model "code.gitea.io/gitea/models/issues"
  23. "code.gitea.io/gitea/models/perm"
  24. repo_model "code.gitea.io/gitea/models/repo"
  25. "code.gitea.io/gitea/models/unittest"
  26. user_model "code.gitea.io/gitea/models/user"
  27. "code.gitea.io/gitea/modules/commitstatus"
  28. "code.gitea.io/gitea/modules/git"
  29. "code.gitea.io/gitea/modules/git/gitcmd"
  30. "code.gitea.io/gitea/modules/lfs"
  31. "code.gitea.io/gitea/modules/setting"
  32. api "code.gitea.io/gitea/modules/structs"
  33. "code.gitea.io/gitea/tests"
  34. "github.com/kballard/go-shellquote"
  35. "github.com/stretchr/testify/assert"
  36. "github.com/stretchr/testify/require"
  37. )
  38. const (
  39. testFileSizeSmall = 10
  40. testFileSizeLarge = 10 * 1024 * 1024 // 10M
  41. )
  42. func TestGitGeneral(t *testing.T) {
  43. onGiteaRun(t, testGitGeneral)
  44. }
  45. func testGitGeneral(t *testing.T, u *url.URL) {
  46. username := "user2"
  47. baseAPITestContext := NewAPITestContext(t, username, "repo1", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
  48. u.Path = baseAPITestContext.GitPath()
  49. forkedUserCtx := NewAPITestContext(t, "user4", "repo1", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
  50. t.Run("HTTP", func(t *testing.T) {
  51. defer tests.PrintCurrentTest(t)()
  52. ensureAnonymousClone(t, u)
  53. httpContext := baseAPITestContext
  54. httpContext.Reponame = "repo-tmp-17"
  55. forkedUserCtx.Reponame = httpContext.Reponame
  56. dstPath := t.TempDir()
  57. t.Run("CreateRepoInDifferentUser", doAPICreateRepository(forkedUserCtx, false))
  58. t.Run("AddUserAsCollaborator", doAPIAddCollaborator(forkedUserCtx, httpContext.Username, perm.AccessModeRead))
  59. t.Run("ForkFromDifferentUser", doAPIForkRepository(httpContext, forkedUserCtx.Username))
  60. u.Path = httpContext.GitPath()
  61. u.User = url.UserPassword(username, userPassword)
  62. t.Run("Clone", doGitClone(dstPath, u))
  63. dstPath2 := t.TempDir()
  64. t.Run("Partial Clone", doPartialGitClone(dstPath2, u))
  65. pushedFilesStandard := standardCommitAndPushTest(t, dstPath, testFileSizeSmall, testFileSizeLarge)
  66. pushedFilesLFS := lfsCommitAndPushTest(t, dstPath, testFileSizeSmall, testFileSizeLarge)
  67. rawTest(t, &httpContext, pushedFilesStandard[0], pushedFilesStandard[1], pushedFilesLFS[0], pushedFilesLFS[1])
  68. mediaTest(t, &httpContext, pushedFilesStandard[0], pushedFilesStandard[1], pushedFilesLFS[0], pushedFilesLFS[1])
  69. t.Run("CreateAgitFlowPull", doCreateAgitFlowPull(dstPath, &httpContext, "test/head"))
  70. t.Run("CreateProtectedBranch", doCreateProtectedBranch(&httpContext, dstPath))
  71. t.Run("BranchProtectMerge", doBranchProtectPRMerge(&httpContext, dstPath))
  72. t.Run("AutoMerge", doAutoPRMerge(&httpContext, dstPath))
  73. t.Run("CreatePRAndSetManuallyMerged", doCreatePRAndSetManuallyMerged(httpContext, httpContext, dstPath, "master", "test-manually-merge"))
  74. t.Run("MergeFork", func(t *testing.T) {
  75. defer tests.PrintCurrentTest(t)()
  76. t.Run("CreatePRAndMerge", doMergeFork(httpContext, forkedUserCtx, "master", httpContext.Username+":master"))
  77. rawTest(t, &forkedUserCtx, pushedFilesStandard[0], pushedFilesStandard[1], pushedFilesLFS[0], pushedFilesLFS[1])
  78. mediaTest(t, &forkedUserCtx, pushedFilesStandard[0], pushedFilesStandard[1], pushedFilesLFS[0], pushedFilesLFS[1])
  79. })
  80. t.Run("PushCreate", doPushCreate(httpContext, u))
  81. })
  82. t.Run("SSH", func(t *testing.T) {
  83. defer tests.PrintCurrentTest(t)()
  84. sshContext := baseAPITestContext
  85. sshContext.Reponame = "repo-tmp-18"
  86. keyname := "my-testing-key"
  87. forkedUserCtx.Reponame = sshContext.Reponame
  88. t.Run("CreateRepoInDifferentUser", doAPICreateRepository(forkedUserCtx, false))
  89. t.Run("AddUserAsCollaborator", doAPIAddCollaborator(forkedUserCtx, sshContext.Username, perm.AccessModeRead))
  90. t.Run("ForkFromDifferentUser", doAPIForkRepository(sshContext, forkedUserCtx.Username))
  91. // Setup key the user ssh key
  92. withKeyFile(t, keyname, func(keyFile string) {
  93. var keyID int64
  94. t.Run("CreateUserKey", doAPICreateUserKey(sshContext, "test-key", keyFile, func(t *testing.T, key api.PublicKey) {
  95. keyID = key.ID
  96. }))
  97. assert.NotZero(t, keyID)
  98. t.Run("LFSAccessTest", doSSHLFSAccessTest(sshContext, keyID))
  99. // Setup remote link
  100. // TODO: get url from api
  101. sshURL := createSSHUrl(sshContext.GitPath(), u)
  102. // Setup clone folder
  103. dstPath := t.TempDir()
  104. t.Run("Clone", doGitClone(dstPath, sshURL))
  105. pushedFilesStandard := standardCommitAndPushTest(t, dstPath, testFileSizeSmall, testFileSizeLarge)
  106. pushedFilesLFS := lfsCommitAndPushTest(t, dstPath, testFileSizeSmall, testFileSizeLarge)
  107. rawTest(t, &sshContext, pushedFilesStandard[0], pushedFilesStandard[1], pushedFilesLFS[0], pushedFilesLFS[1])
  108. mediaTest(t, &sshContext, pushedFilesStandard[0], pushedFilesStandard[1], pushedFilesLFS[0], pushedFilesLFS[1])
  109. t.Run("CreateAgitFlowPull", doCreateAgitFlowPull(dstPath, &sshContext, "test/head2"))
  110. t.Run("CreateProtectedBranch", doCreateProtectedBranch(&sshContext, dstPath))
  111. t.Run("BranchProtectMerge", doBranchProtectPRMerge(&sshContext, dstPath))
  112. t.Run("MergeFork", func(t *testing.T) {
  113. defer tests.PrintCurrentTest(t)()
  114. t.Run("CreatePRAndMerge", doMergeFork(sshContext, forkedUserCtx, "master", sshContext.Username+":master"))
  115. rawTest(t, &forkedUserCtx, pushedFilesStandard[0], pushedFilesStandard[1], pushedFilesLFS[0], pushedFilesLFS[1])
  116. mediaTest(t, &forkedUserCtx, pushedFilesStandard[0], pushedFilesStandard[1], pushedFilesLFS[0], pushedFilesLFS[1])
  117. })
  118. t.Run("PushCreate", doPushCreate(sshContext, sshURL))
  119. })
  120. })
  121. }
  122. func doSSHLFSAccessTest(_ APITestContext, keyID int64) func(*testing.T) {
  123. return func(t *testing.T) {
  124. sshCommand := os.Getenv("GIT_SSH_COMMAND") // it is set in withKeyFile
  125. sshCmdParts, err := shellquote.Split(sshCommand) // and parse the ssh command to construct some mocked arguments
  126. require.NoError(t, err)
  127. t.Run("User2AccessOwned", func(t *testing.T) {
  128. sshCmdUser2Self := append(slices.Clone(sshCmdParts),
  129. "-p", strconv.Itoa(setting.SSH.ListenPort), "git@"+setting.SSH.ListenHost,
  130. "git-lfs-authenticate", "user2/repo1.git", "upload", // accessible to own repo
  131. )
  132. cmd := exec.CommandContext(t.Context(), sshCmdUser2Self[0], sshCmdUser2Self[1:]...)
  133. _, err := cmd.Output()
  134. assert.NoError(t, err) // accessible, no error
  135. })
  136. t.Run("User2AccessOther", func(t *testing.T) {
  137. sshCmdUser2Other := append(slices.Clone(sshCmdParts),
  138. "-p", strconv.Itoa(setting.SSH.ListenPort), "git@"+setting.SSH.ListenHost,
  139. "git-lfs-authenticate", "user5/repo4.git", "upload", // inaccessible to other's (user5/repo4)
  140. )
  141. cmd := exec.CommandContext(t.Context(), sshCmdUser2Other[0], sshCmdUser2Other[1:]...)
  142. _, err := cmd.Output()
  143. var errExit *exec.ExitError
  144. require.ErrorAs(t, err, &errExit) // inaccessible, error
  145. assert.Contains(t, string(errExit.Stderr), fmt.Sprintf("User: 2:user2 with Key: %d:test-key is not authorized to write to user5/repo4.", keyID))
  146. })
  147. }
  148. }
  149. func ensureAnonymousClone(t *testing.T, u *url.URL) {
  150. dstLocalPath := t.TempDir()
  151. t.Run("CloneAnonymous", doGitClone(dstLocalPath, u))
  152. }
  153. func standardCommitAndPushTest(t *testing.T, dstPath string, sizes ...int) (pushedFiles []string) {
  154. t.Run("CommitAndPushStandard", func(t *testing.T) {
  155. defer tests.PrintCurrentTest(t)()
  156. pushedFiles = commitAndPushTest(t, dstPath, "data-file-", sizes...)
  157. })
  158. return pushedFiles
  159. }
  160. func lfsCommitAndPushTest(t *testing.T, dstPath string, sizes ...int) (pushedFiles []string) {
  161. t.Run("CommitAndPushLFS", func(t *testing.T) {
  162. defer tests.PrintCurrentTest(t)()
  163. prefix := "lfs-data-file-"
  164. err := gitcmd.NewCommand("lfs").AddArguments("install").Run(t.Context(), &gitcmd.RunOpts{Dir: dstPath})
  165. assert.NoError(t, err)
  166. _, _, err = gitcmd.NewCommand("lfs").AddArguments("track").AddDynamicArguments(prefix+"*").RunStdString(t.Context(), &gitcmd.RunOpts{Dir: dstPath})
  167. assert.NoError(t, err)
  168. err = git.AddChanges(t.Context(), dstPath, false, ".gitattributes")
  169. assert.NoError(t, err)
  170. err = git.CommitChanges(t.Context(), dstPath, git.CommitChangesOptions{
  171. Committer: &git.Signature{
  172. Email: "user2@example.com",
  173. Name: "User Two",
  174. When: time.Now(),
  175. },
  176. Author: &git.Signature{
  177. Email: "user2@example.com",
  178. Name: "User Two",
  179. When: time.Now(),
  180. },
  181. Message: fmt.Sprintf("Testing commit @ %v", time.Now()),
  182. })
  183. assert.NoError(t, err)
  184. pushedFiles = commitAndPushTest(t, dstPath, prefix, sizes...)
  185. t.Run("Locks", func(t *testing.T) {
  186. defer tests.PrintCurrentTest(t)()
  187. lockTest(t, dstPath)
  188. })
  189. })
  190. return pushedFiles
  191. }
  192. func commitAndPushTest(t *testing.T, dstPath, prefix string, sizes ...int) (pushedFiles []string) {
  193. for _, size := range sizes {
  194. t.Run("PushCommit Size-"+strconv.Itoa(size), func(t *testing.T) {
  195. defer tests.PrintCurrentTest(t)()
  196. pushedFiles = append(pushedFiles, doCommitAndPush(t, size, dstPath, prefix))
  197. })
  198. }
  199. return pushedFiles
  200. }
  201. func rawTest(t *testing.T, ctx *APITestContext, little, big, littleLFS, bigLFS string) {
  202. t.Run("Raw", func(t *testing.T) {
  203. defer tests.PrintCurrentTest(t)()
  204. username := ctx.Username
  205. reponame := ctx.Reponame
  206. session := loginUser(t, username)
  207. // Request raw paths
  208. req := NewRequest(t, "GET", path.Join("/", username, reponame, "/raw/branch/master/", little))
  209. resp := session.MakeRequestNilResponseRecorder(t, req, http.StatusOK)
  210. assert.Equal(t, testFileSizeSmall, resp.Length)
  211. if setting.LFS.StartServer {
  212. req = NewRequest(t, "GET", path.Join("/", username, reponame, "/raw/branch/master/", littleLFS))
  213. resp := session.MakeRequest(t, req, http.StatusOK)
  214. assert.NotEqual(t, testFileSizeSmall, resp.Body.Len())
  215. assert.LessOrEqual(t, resp.Body.Len(), 1024)
  216. if resp.Body.Len() != testFileSizeSmall && resp.Body.Len() <= 1024 {
  217. assert.Contains(t, resp.Body.String(), lfs.MetaFileIdentifier)
  218. }
  219. }
  220. if !testing.Short() {
  221. req = NewRequest(t, "GET", path.Join("/", username, reponame, "/raw/branch/master/", big))
  222. resp := session.MakeRequestNilResponseRecorder(t, req, http.StatusOK)
  223. assert.Equal(t, testFileSizeLarge, resp.Length)
  224. if setting.LFS.StartServer {
  225. req = NewRequest(t, "GET", path.Join("/", username, reponame, "/raw/branch/master/", bigLFS))
  226. resp := session.MakeRequest(t, req, http.StatusOK)
  227. assert.NotEqual(t, testFileSizeLarge, resp.Body.Len())
  228. if resp.Body.Len() != testFileSizeLarge && resp.Body.Len() <= 1024 {
  229. assert.Contains(t, resp.Body.String(), lfs.MetaFileIdentifier)
  230. }
  231. }
  232. }
  233. })
  234. }
  235. func mediaTest(t *testing.T, ctx *APITestContext, little, big, littleLFS, bigLFS string) {
  236. t.Run("Media", func(t *testing.T) {
  237. defer tests.PrintCurrentTest(t)()
  238. username := ctx.Username
  239. reponame := ctx.Reponame
  240. session := loginUser(t, username)
  241. // Request media paths
  242. req := NewRequest(t, "GET", path.Join("/", username, reponame, "/media/branch/master/", little))
  243. resp := session.MakeRequestNilResponseRecorder(t, req, http.StatusOK)
  244. assert.Equal(t, testFileSizeSmall, resp.Length)
  245. req = NewRequest(t, "GET", path.Join("/", username, reponame, "/media/branch/master/", littleLFS))
  246. resp = session.MakeRequestNilResponseRecorder(t, req, http.StatusOK)
  247. assert.Equal(t, testFileSizeSmall, resp.Length)
  248. if !testing.Short() {
  249. req = NewRequest(t, "GET", path.Join("/", username, reponame, "/media/branch/master/", big))
  250. resp = session.MakeRequestNilResponseRecorder(t, req, http.StatusOK)
  251. assert.Equal(t, testFileSizeLarge, resp.Length)
  252. if setting.LFS.StartServer {
  253. req = NewRequest(t, "GET", path.Join("/", username, reponame, "/media/branch/master/", bigLFS))
  254. resp = session.MakeRequestNilResponseRecorder(t, req, http.StatusOK)
  255. assert.Equal(t, testFileSizeLarge, resp.Length)
  256. }
  257. }
  258. })
  259. }
  260. func lockTest(t *testing.T, repoPath string) {
  261. lockFileTest(t, "README.md", repoPath)
  262. }
  263. func lockFileTest(t *testing.T, filename, repoPath string) {
  264. _, _, err := gitcmd.NewCommand("lfs").AddArguments("locks").RunStdString(t.Context(), &gitcmd.RunOpts{Dir: repoPath})
  265. assert.NoError(t, err)
  266. _, _, err = gitcmd.NewCommand("lfs").AddArguments("lock").AddDynamicArguments(filename).RunStdString(t.Context(), &gitcmd.RunOpts{Dir: repoPath})
  267. assert.NoError(t, err)
  268. _, _, err = gitcmd.NewCommand("lfs").AddArguments("locks").RunStdString(t.Context(), &gitcmd.RunOpts{Dir: repoPath})
  269. assert.NoError(t, err)
  270. _, _, err = gitcmd.NewCommand("lfs").AddArguments("unlock").AddDynamicArguments(filename).RunStdString(t.Context(), &gitcmd.RunOpts{Dir: repoPath})
  271. assert.NoError(t, err)
  272. }
  273. func doCommitAndPush(t *testing.T, size int, repoPath, prefix string) string {
  274. name, err := generateCommitWithNewData(t.Context(), size, repoPath, "user2@example.com", "User Two", prefix)
  275. assert.NoError(t, err)
  276. _, _, err = gitcmd.NewCommand("push", "origin", "master").RunStdString(t.Context(), &gitcmd.RunOpts{Dir: repoPath}) // Push
  277. assert.NoError(t, err)
  278. return name
  279. }
  280. func generateCommitWithNewData(ctx context.Context, size int, repoPath, email, fullName, prefix string) (string, error) {
  281. tmpFile, err := os.CreateTemp(repoPath, prefix)
  282. if err != nil {
  283. return "", err
  284. }
  285. defer tmpFile.Close()
  286. var seed [32]byte
  287. rander := mathRand.NewChaCha8(seed) // for testing only, no need to seed
  288. _, err = io.CopyN(tmpFile, rander, int64(size))
  289. if err != nil {
  290. return "", err
  291. }
  292. _ = tmpFile.Close()
  293. // Commit
  294. err = git.AddChanges(ctx, repoPath, false, filepath.Base(tmpFile.Name()))
  295. if err != nil {
  296. return "", err
  297. }
  298. err = git.CommitChanges(ctx, repoPath, git.CommitChangesOptions{
  299. Committer: &git.Signature{
  300. Email: email,
  301. Name: fullName,
  302. When: time.Now(),
  303. },
  304. Author: &git.Signature{
  305. Email: email,
  306. Name: fullName,
  307. When: time.Now(),
  308. },
  309. Message: fmt.Sprintf("Testing commit @ %v", time.Now()),
  310. })
  311. return filepath.Base(tmpFile.Name()), err
  312. }
  313. func doCreateProtectedBranch(baseCtx *APITestContext, dstPath string) func(t *testing.T) {
  314. return func(t *testing.T) {
  315. defer tests.PrintCurrentTest(t)()
  316. ctx := NewAPITestContext(t, baseCtx.Username, baseCtx.Reponame, auth_model.AccessTokenScopeWriteRepository)
  317. t.Run("ProtectBranchWithFilePatterns", doProtectBranch(ctx, "release-*", baseCtx.Username, "", "", "config*"))
  318. // push a new branch without any new commits
  319. t.Run("CreateProtectedBranch-NoChanges", doGitCreateBranch(dstPath, "release-v1.0"))
  320. t.Run("PushProtectedBranch-NoChanges", doGitPushTestRepository(dstPath, "origin", "release-v1.0"))
  321. t.Run("CheckoutMaster-NoChanges", doGitCheckoutBranch(dstPath, "master"))
  322. // push a new branch with a new unprotected file
  323. t.Run("CreateProtectedBranch-UnprotectedFile", doGitCreateBranch(dstPath, "release-v2.0"))
  324. _, err := generateCommitWithNewData(t.Context(), testFileSizeSmall, dstPath, "user2@example.com", "User Two", "abc.txt")
  325. assert.NoError(t, err)
  326. t.Run("PushProtectedBranch-UnprotectedFile", doGitPushTestRepository(dstPath, "origin", "release-v2.0"))
  327. t.Run("CheckoutMaster-UnprotectedFile", doGitCheckoutBranch(dstPath, "master"))
  328. // push a new branch with a new protected file
  329. t.Run("CreateProtectedBranch-ProtectedFile", doGitCreateBranch(dstPath, "release-v3.0"))
  330. _, err = generateCommitWithNewData(t.Context(), testFileSizeSmall, dstPath, "user2@example.com", "User Two", "config")
  331. assert.NoError(t, err)
  332. t.Run("PushProtectedBranch-ProtectedFile", doGitPushTestRepositoryFail(dstPath, "origin", "release-v3.0"))
  333. t.Run("CheckoutMaster-ProtectedFile", doGitCheckoutBranch(dstPath, "master"))
  334. }
  335. }
  336. func doBranchProtectPRMerge(baseCtx *APITestContext, dstPath string) func(t *testing.T) {
  337. return func(t *testing.T) {
  338. defer tests.PrintCurrentTest(t)()
  339. t.Run("CreateBranchProtected", doGitCreateBranch(dstPath, "protected"))
  340. t.Run("PushProtectedBranch", doGitPushTestRepository(dstPath, "origin", "protected"))
  341. ctx := NewAPITestContext(t, baseCtx.Username, baseCtx.Reponame, auth_model.AccessTokenScopeWriteRepository)
  342. // Protect branch without any whitelisting
  343. t.Run("ProtectBranchNoWhitelist", doProtectBranch(ctx, "protected", "", "", "", ""))
  344. // Try to push without permissions, which should fail
  345. t.Run("TryPushWithoutPermissions", func(t *testing.T) {
  346. _, err := generateCommitWithNewData(t.Context(), testFileSizeSmall, dstPath, "user2@example.com", "User Two", "branch-data-file-")
  347. assert.NoError(t, err)
  348. doGitPushTestRepositoryFail(dstPath, "origin", "protected")(t)
  349. })
  350. // Set up permissions for normal push but not force push
  351. t.Run("SetupNormalPushPermissions", doProtectBranch(ctx, "protected", baseCtx.Username, "", "", ""))
  352. // Normal push should work
  353. t.Run("NormalPushWithPermissions", func(t *testing.T) {
  354. _, err := generateCommitWithNewData(t.Context(), testFileSizeSmall, dstPath, "user2@example.com", "User Two", "branch-data-file-")
  355. assert.NoError(t, err)
  356. doGitPushTestRepository(dstPath, "origin", "protected")(t)
  357. })
  358. // Try to force push without force push permissions, which should fail
  359. t.Run("ForcePushWithoutForcePermissions", func(t *testing.T) {
  360. t.Run("CreateDivergentHistory", func(t *testing.T) {
  361. gitcmd.NewCommand("reset", "--hard", "HEAD~1").Run(t.Context(), &gitcmd.RunOpts{Dir: dstPath})
  362. _, err := generateCommitWithNewData(t.Context(), testFileSizeSmall, dstPath, "user2@example.com", "User Two", "branch-data-file-new")
  363. assert.NoError(t, err)
  364. })
  365. doGitPushTestRepositoryFail(dstPath, "-f", "origin", "protected")(t)
  366. })
  367. // Set up permissions for force push but not normal push
  368. t.Run("SetupForcePushPermissions", doProtectBranch(ctx, "protected", "", baseCtx.Username, "", ""))
  369. // Try to force push without normal push permissions, which should fail
  370. t.Run("ForcePushWithoutNormalPermissions", doGitPushTestRepositoryFail(dstPath, "-f", "origin", "protected"))
  371. // Set up permissions for normal and force push (both are required to force push)
  372. t.Run("SetupNormalAndForcePushPermissions", doProtectBranch(ctx, "protected", baseCtx.Username, baseCtx.Username, "", ""))
  373. // Force push should now work
  374. t.Run("ForcePushWithPermissions", doGitPushTestRepository(dstPath, "-f", "origin", "protected"))
  375. t.Run("ProtectProtectedBranchNoWhitelist", doProtectBranch(ctx, "protected", "", "", "", ""))
  376. t.Run("PushToUnprotectedBranch", doGitPushTestRepository(dstPath, "origin", "protected:unprotected"))
  377. var pr api.PullRequest
  378. var err error
  379. t.Run("CreatePullRequest", func(t *testing.T) {
  380. pr, err = doAPICreatePullRequest(ctx, baseCtx.Username, baseCtx.Reponame, "protected", "unprotected")(t)
  381. assert.NoError(t, err)
  382. })
  383. t.Run("GenerateCommit", func(t *testing.T) {
  384. _, err := generateCommitWithNewData(t.Context(), testFileSizeSmall, dstPath, "user2@example.com", "User Two", "branch-data-file-")
  385. assert.NoError(t, err)
  386. })
  387. t.Run("PushToUnprotectedBranch", doGitPushTestRepository(dstPath, "origin", "protected:unprotected-2"))
  388. var pr2 api.PullRequest
  389. t.Run("CreatePullRequest", func(t *testing.T) {
  390. pr2, err = doAPICreatePullRequest(ctx, baseCtx.Username, baseCtx.Reponame, "unprotected", "unprotected-2")(t)
  391. assert.NoError(t, err)
  392. })
  393. t.Run("MergePR2", doAPIMergePullRequest(ctx, baseCtx.Username, baseCtx.Reponame, pr2.Index))
  394. t.Run("MergePR", doAPIMergePullRequest(ctx, baseCtx.Username, baseCtx.Reponame, pr.Index))
  395. t.Run("PullProtected", doGitPull(dstPath, "origin", "protected"))
  396. t.Run("ProtectProtectedBranchUnprotectedFilePaths", doProtectBranch(ctx, "protected", "", "", "unprotected-file-*", ""))
  397. t.Run("GenerateCommit", func(t *testing.T) {
  398. _, err := generateCommitWithNewData(t.Context(), testFileSizeSmall, dstPath, "user2@example.com", "User Two", "unprotected-file-")
  399. assert.NoError(t, err)
  400. })
  401. t.Run("PushUnprotectedFilesToProtectedBranch", doGitPushTestRepository(dstPath, "origin", "protected"))
  402. t.Run("ProtectProtectedBranchWhitelist", doProtectBranch(ctx, "protected", baseCtx.Username, "", "", ""))
  403. t.Run("CheckoutMaster", doGitCheckoutBranch(dstPath, "master"))
  404. t.Run("CreateBranchForced", doGitCreateBranch(dstPath, "toforce"))
  405. t.Run("GenerateCommit", func(t *testing.T) {
  406. _, err := generateCommitWithNewData(t.Context(), testFileSizeSmall, dstPath, "user2@example.com", "User Two", "branch-data-file-")
  407. assert.NoError(t, err)
  408. })
  409. t.Run("FailToForcePushToProtectedBranch", doGitPushTestRepositoryFail(dstPath, "-f", "origin", "toforce:protected"))
  410. t.Run("MergeProtectedToToforce", doGitMerge(dstPath, "protected"))
  411. t.Run("PushToProtectedBranch", doGitPushTestRepository(dstPath, "origin", "toforce:protected"))
  412. t.Run("CheckoutMasterAgain", doGitCheckoutBranch(dstPath, "master"))
  413. }
  414. }
  415. func doProtectBranch(ctx APITestContext, branch, userToWhitelistPush, userToWhitelistForcePush, unprotectedFilePatterns, protectedFilePatterns string) func(t *testing.T) {
  416. return doProtectBranchExt(ctx, branch, doProtectBranchOptions{
  417. UserToWhitelistPush: userToWhitelistPush,
  418. UserToWhitelistForcePush: userToWhitelistForcePush,
  419. UnprotectedFilePatterns: unprotectedFilePatterns,
  420. ProtectedFilePatterns: protectedFilePatterns,
  421. })
  422. }
  423. type doProtectBranchOptions struct {
  424. UserToWhitelistPush, UserToWhitelistForcePush, UnprotectedFilePatterns, ProtectedFilePatterns string
  425. StatusCheckPatterns []string
  426. }
  427. func doProtectBranchExt(ctx APITestContext, ruleName string, opts doProtectBranchOptions) func(t *testing.T) {
  428. // We are going to just use the owner to set the protection.
  429. return func(t *testing.T) {
  430. csrf := GetUserCSRFToken(t, ctx.Session)
  431. formData := map[string]string{
  432. "_csrf": csrf,
  433. "rule_name": ruleName,
  434. "unprotected_file_patterns": opts.UnprotectedFilePatterns,
  435. "protected_file_patterns": opts.ProtectedFilePatterns,
  436. }
  437. if opts.UserToWhitelistPush != "" {
  438. user, err := user_model.GetUserByName(t.Context(), opts.UserToWhitelistPush)
  439. assert.NoError(t, err)
  440. formData["whitelist_users"] = strconv.FormatInt(user.ID, 10)
  441. formData["enable_push"] = "whitelist"
  442. formData["enable_whitelist"] = "on"
  443. }
  444. if opts.UserToWhitelistForcePush != "" {
  445. user, err := user_model.GetUserByName(t.Context(), opts.UserToWhitelistForcePush)
  446. assert.NoError(t, err)
  447. formData["force_push_allowlist_users"] = strconv.FormatInt(user.ID, 10)
  448. formData["enable_force_push"] = "whitelist"
  449. formData["enable_force_push_allowlist"] = "on"
  450. }
  451. if len(opts.StatusCheckPatterns) > 0 {
  452. formData["enable_status_check"] = "on"
  453. formData["status_check_contexts"] = strings.Join(opts.StatusCheckPatterns, "\n")
  454. }
  455. // Send the request to update branch protection settings
  456. req := NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/settings/branches/edit", url.PathEscape(ctx.Username), url.PathEscape(ctx.Reponame)), formData)
  457. ctx.Session.MakeRequest(t, req, http.StatusSeeOther)
  458. // Check if the "master" branch has been locked successfully
  459. flashMsg := ctx.Session.GetCookieFlashMessage()
  460. assert.Equal(t, `Branch protection for rule "`+ruleName+`" has been updated.`, flashMsg.SuccessMsg)
  461. }
  462. }
  463. func doMergeFork(ctx, baseCtx APITestContext, baseBranch, headBranch string) func(t *testing.T) {
  464. return func(t *testing.T) {
  465. defer tests.PrintCurrentTest(t)()
  466. var pr api.PullRequest
  467. var err error
  468. // Create a test pullrequest
  469. t.Run("CreatePullRequest", func(t *testing.T) {
  470. pr, err = doAPICreatePullRequest(ctx, baseCtx.Username, baseCtx.Reponame, baseBranch, headBranch)(t)
  471. assert.NoError(t, err)
  472. })
  473. // Ensure the PR page works
  474. t.Run("EnsureCanSeePull", doEnsureCanSeePull(baseCtx, pr))
  475. // Then get the diff string
  476. var diffHash string
  477. var diffLength int
  478. t.Run("GetDiff", func(t *testing.T) {
  479. req := NewRequest(t, "GET", fmt.Sprintf("/%s/%s/pulls/%d.diff", url.PathEscape(baseCtx.Username), url.PathEscape(baseCtx.Reponame), pr.Index))
  480. resp := ctx.Session.MakeRequestNilResponseHashSumRecorder(t, req, http.StatusOK)
  481. diffHash = string(resp.Hash.Sum(nil))
  482. diffLength = resp.Length
  483. })
  484. // Now: Merge the PR & make sure that doesn't break the PR page or change its diff
  485. t.Run("MergePR", doAPIMergePullRequest(baseCtx, baseCtx.Username, baseCtx.Reponame, pr.Index))
  486. t.Run("EnsureCanSeePull", doEnsureCanSeePull(baseCtx, pr))
  487. t.Run("CheckPR", func(t *testing.T) {
  488. oldMergeBase := pr.MergeBase
  489. pr2, err := doAPIGetPullRequest(baseCtx, baseCtx.Username, baseCtx.Reponame, pr.Index)(t)
  490. assert.NoError(t, err)
  491. assert.Equal(t, oldMergeBase, pr2.MergeBase)
  492. })
  493. t.Run("EnsurDiffNoChange", doEnsureDiffNoChange(baseCtx, pr, diffHash, diffLength))
  494. // Then: Delete the head branch & make sure that doesn't break the PR page or change its diff
  495. t.Run("DeleteHeadBranch", doBranchDelete(baseCtx, baseCtx.Username, baseCtx.Reponame, headBranch))
  496. t.Run("EnsureCanSeePull", doEnsureCanSeePull(baseCtx, pr))
  497. t.Run("EnsureDiffNoChange", doEnsureDiffNoChange(baseCtx, pr, diffHash, diffLength))
  498. // Delete the head repository & make sure that doesn't break the PR page or change its diff
  499. t.Run("DeleteHeadRepository", doAPIDeleteRepository(ctx))
  500. t.Run("EnsureCanSeePull", doEnsureCanSeePull(baseCtx, pr))
  501. t.Run("EnsureDiffNoChange", doEnsureDiffNoChange(baseCtx, pr, diffHash, diffLength))
  502. }
  503. }
  504. func doCreatePRAndSetManuallyMerged(ctx, baseCtx APITestContext, dstPath, baseBranch, headBranch string) func(t *testing.T) {
  505. return func(t *testing.T) {
  506. defer tests.PrintCurrentTest(t)()
  507. var (
  508. pr api.PullRequest
  509. err error
  510. lastCommitID string
  511. )
  512. trueBool := true
  513. falseBool := false
  514. t.Run("AllowSetManuallyMergedAndSwitchOffAutodetectManualMerge", doAPIEditRepository(baseCtx, &api.EditRepoOption{
  515. HasPullRequests: &trueBool,
  516. AllowManualMerge: &trueBool,
  517. AutodetectManualMerge: &falseBool,
  518. }))
  519. t.Run("CreateHeadBranch", doGitCreateBranch(dstPath, headBranch))
  520. t.Run("PushToHeadBranch", doGitPushTestRepository(dstPath, "origin", headBranch))
  521. t.Run("CreateEmptyPullRequest", func(t *testing.T) {
  522. pr, err = doAPICreatePullRequest(ctx, baseCtx.Username, baseCtx.Reponame, baseBranch, headBranch)(t)
  523. assert.NoError(t, err)
  524. })
  525. lastCommitID = pr.Base.Sha
  526. t.Run("ManuallyMergePR", doAPIManuallyMergePullRequest(ctx, baseCtx.Username, baseCtx.Reponame, lastCommitID, pr.Index))
  527. }
  528. }
  529. func doEnsureCanSeePull(ctx APITestContext, pr api.PullRequest) func(t *testing.T) {
  530. return func(t *testing.T) {
  531. req := NewRequest(t, "GET", fmt.Sprintf("/%s/%s/pulls/%d", url.PathEscape(ctx.Username), url.PathEscape(ctx.Reponame), pr.Index))
  532. ctx.Session.MakeRequest(t, req, http.StatusOK)
  533. req = NewRequest(t, "GET", fmt.Sprintf("/%s/%s/pulls/%d/files", url.PathEscape(ctx.Username), url.PathEscape(ctx.Reponame), pr.Index))
  534. ctx.Session.MakeRequest(t, req, http.StatusOK)
  535. req = NewRequest(t, "GET", fmt.Sprintf("/%s/%s/pulls/%d/commits", url.PathEscape(ctx.Username), url.PathEscape(ctx.Reponame), pr.Index))
  536. ctx.Session.MakeRequest(t, req, http.StatusOK)
  537. }
  538. }
  539. func doEnsureDiffNoChange(ctx APITestContext, pr api.PullRequest, diffHash string, diffLength int) func(t *testing.T) {
  540. return func(t *testing.T) {
  541. req := NewRequest(t, "GET", fmt.Sprintf("/%s/%s/pulls/%d.diff", url.PathEscape(ctx.Username), url.PathEscape(ctx.Reponame), pr.Index))
  542. resp := ctx.Session.MakeRequestNilResponseHashSumRecorder(t, req, http.StatusOK)
  543. actual := string(resp.Hash.Sum(nil))
  544. actualLength := resp.Length
  545. equal := diffHash == actual
  546. assert.True(t, equal, "Unexpected change in the diff string: expected hash: %s size: %d but was actually: %s size: %d", hex.EncodeToString([]byte(diffHash)), diffLength, hex.EncodeToString([]byte(actual)), actualLength)
  547. }
  548. }
  549. func doPushCreate(ctx APITestContext, u *url.URL) func(t *testing.T) {
  550. return func(t *testing.T) {
  551. defer tests.PrintCurrentTest(t)()
  552. // create a context for a currently non-existent repository
  553. ctx.Reponame = "repo-tmp-push-create-" + u.Scheme
  554. u.Path = ctx.GitPath()
  555. // Create a temporary directory
  556. tmpDir := t.TempDir()
  557. // Now create local repository to push as our test and set its origin
  558. t.Run("InitTestRepository", doGitInitTestRepository(tmpDir))
  559. t.Run("AddRemote", doGitAddRemote(tmpDir, "origin", u))
  560. // Disable "Push To Create" and attempt to push
  561. setting.Repository.EnablePushCreateUser = false
  562. t.Run("FailToPushAndCreateTestRepository", doGitPushTestRepositoryFail(tmpDir, "origin", "master"))
  563. // Enable "Push To Create"
  564. setting.Repository.EnablePushCreateUser = true
  565. // Assert that cloning from a non-existent repository does not create it and that it definitely wasn't create above
  566. t.Run("FailToCloneFromNonExistentRepository", doGitCloneFail(u))
  567. // Then "Push To Create"x
  568. t.Run("SuccessfullyPushAndCreateTestRepository", doGitPushTestRepository(tmpDir, "origin", "master"))
  569. // Finally, fetch repo from database and ensure the correct repository has been created
  570. repo, err := repo_model.GetRepositoryByOwnerAndName(t.Context(), ctx.Username, ctx.Reponame)
  571. assert.NoError(t, err)
  572. assert.False(t, repo.IsEmpty)
  573. assert.True(t, repo.IsPrivate)
  574. // Now add a remote that is invalid to "Push To Create"
  575. invalidCtx := ctx
  576. invalidCtx.Reponame = "invalid/repo-tmp-push-create-" + u.Scheme
  577. u.Path = invalidCtx.GitPath()
  578. t.Run("AddInvalidRemote", doGitAddRemote(tmpDir, "invalid", u))
  579. // Fail to "Push To Create" the invalid
  580. t.Run("FailToPushAndCreateInvalidTestRepository", doGitPushTestRepositoryFail(tmpDir, "invalid", "master"))
  581. }
  582. }
  583. func doBranchDelete(ctx APITestContext, owner, repo, branch string) func(*testing.T) {
  584. return func(t *testing.T) {
  585. csrf := GetUserCSRFToken(t, ctx.Session)
  586. req := NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/branches/delete?name=%s", url.PathEscape(owner), url.PathEscape(repo), url.QueryEscape(branch)), map[string]string{
  587. "_csrf": csrf,
  588. })
  589. ctx.Session.MakeRequest(t, req, http.StatusOK)
  590. }
  591. }
  592. func doAutoPRMerge(baseCtx *APITestContext, dstPath string) func(t *testing.T) {
  593. return func(t *testing.T) {
  594. defer tests.PrintCurrentTest(t)()
  595. ctx := NewAPITestContext(t, baseCtx.Username, baseCtx.Reponame, auth_model.AccessTokenScopeWriteRepository)
  596. // automerge will merge immediately if the PR is mergeable and there is no "status check" because no status check also means "all checks passed"
  597. // so we must set up a status check to test the auto merge feature
  598. doProtectBranchExt(ctx, "protected", doProtectBranchOptions{StatusCheckPatterns: []string{"*"}})(t)
  599. t.Run("CheckoutProtected", doGitCheckoutBranch(dstPath, "protected"))
  600. t.Run("PullProtected", doGitPull(dstPath, "origin", "protected"))
  601. t.Run("GenerateCommit", func(t *testing.T) {
  602. _, err := generateCommitWithNewData(t.Context(), testFileSizeSmall, dstPath, "user2@example.com", "User Two", "branch-data-file-")
  603. assert.NoError(t, err)
  604. })
  605. t.Run("PushToUnprotectedBranch", doGitPushTestRepository(dstPath, "origin", "protected:unprotected3"))
  606. var pr api.PullRequest
  607. var err error
  608. t.Run("CreatePullRequest", func(t *testing.T) {
  609. pr, err = doAPICreatePullRequest(ctx, baseCtx.Username, baseCtx.Reponame, "protected", "unprotected3")(t)
  610. assert.NoError(t, err)
  611. })
  612. // Request repository commits page
  613. req := NewRequest(t, "GET", fmt.Sprintf("/%s/%s/pulls/%d/commits", baseCtx.Username, baseCtx.Reponame, pr.Index))
  614. resp := ctx.Session.MakeRequest(t, req, http.StatusOK)
  615. doc := NewHTMLParser(t, resp.Body)
  616. // Get first commit URL
  617. commitURL, exists := doc.doc.Find("#commits-table tbody tr td.sha a").Last().Attr("href")
  618. assert.True(t, exists)
  619. assert.NotEmpty(t, commitURL)
  620. commitID := path.Base(commitURL)
  621. addCommitStatus := func(status commitstatus.CommitStatusState) func(*testing.T) {
  622. return doAPICreateCommitStatus(ctx, commitID, api.CreateStatusOption{
  623. State: status,
  624. TargetURL: "http://test.ci/",
  625. Description: "",
  626. Context: "testci",
  627. })
  628. }
  629. // Call API to add Pending status for commit
  630. t.Run("CreateStatus", addCommitStatus(commitstatus.CommitStatusPending))
  631. // Cancel not existing auto merge
  632. ctx.ExpectedCode = http.StatusNotFound
  633. t.Run("CancelAutoMergePRNotExist", doAPICancelAutoMergePullRequest(ctx, baseCtx.Username, baseCtx.Reponame, pr.Index))
  634. // Add auto merge request
  635. ctx.ExpectedCode = http.StatusCreated
  636. t.Run("AutoMergePR", doAPIAutoMergePullRequest(ctx, baseCtx.Username, baseCtx.Reponame, pr.Index))
  637. // Cannot create schedule twice
  638. ctx.ExpectedCode = http.StatusConflict
  639. t.Run("AutoMergePRTwice", doAPIAutoMergePullRequest(ctx, baseCtx.Username, baseCtx.Reponame, pr.Index))
  640. // Cancel auto merge request
  641. ctx.ExpectedCode = http.StatusNoContent
  642. t.Run("CancelAutoMergePR", doAPICancelAutoMergePullRequest(ctx, baseCtx.Username, baseCtx.Reponame, pr.Index))
  643. // Add auto merge request
  644. ctx.ExpectedCode = http.StatusCreated
  645. t.Run("AutoMergePR", doAPIAutoMergePullRequest(ctx, baseCtx.Username, baseCtx.Reponame, pr.Index))
  646. // Check pr status
  647. ctx.ExpectedCode = 0
  648. pr, err = doAPIGetPullRequest(ctx, baseCtx.Username, baseCtx.Reponame, pr.Index)(t)
  649. assert.NoError(t, err)
  650. assert.False(t, pr.HasMerged)
  651. // Call API to add Failure status for commit
  652. t.Run("CreateStatus", addCommitStatus(commitstatus.CommitStatusFailure))
  653. // Check pr status
  654. pr, err = doAPIGetPullRequest(ctx, baseCtx.Username, baseCtx.Reponame, pr.Index)(t)
  655. assert.NoError(t, err)
  656. assert.False(t, pr.HasMerged)
  657. // Call API to add Success status for commit
  658. t.Run("CreateStatus", addCommitStatus(commitstatus.CommitStatusSuccess))
  659. // wait to let gitea merge stuff
  660. time.Sleep(time.Second)
  661. // test pr status
  662. pr, err = doAPIGetPullRequest(ctx, baseCtx.Username, baseCtx.Reponame, pr.Index)(t)
  663. assert.NoError(t, err)
  664. assert.True(t, pr.HasMerged)
  665. }
  666. }
  667. func doCreateAgitFlowPull(dstPath string, ctx *APITestContext, headBranch string) func(t *testing.T) {
  668. return func(t *testing.T) {
  669. defer tests.PrintCurrentTest(t)()
  670. // skip this test if git version is low
  671. if !git.DefaultFeatures().SupportProcReceive {
  672. return
  673. }
  674. gitRepo, err := git.OpenRepository(t.Context(), dstPath)
  675. require.NoError(t, err)
  676. defer gitRepo.Close()
  677. var (
  678. pr1, pr2 *issues_model.PullRequest
  679. commit string
  680. )
  681. repo, err := repo_model.GetRepositoryByOwnerAndName(t.Context(), ctx.Username, ctx.Reponame)
  682. require.NoError(t, err)
  683. pullNum := unittest.GetCount(t, &issues_model.PullRequest{})
  684. t.Run("CreateHeadBranch", doGitCreateBranch(dstPath, headBranch))
  685. t.Run("AddCommit", func(t *testing.T) {
  686. err := os.WriteFile(path.Join(dstPath, "test_file"), []byte("## test content"), 0o666)
  687. require.NoError(t, err)
  688. err = git.AddChanges(t.Context(), dstPath, true)
  689. assert.NoError(t, err)
  690. err = git.CommitChanges(t.Context(), dstPath, git.CommitChangesOptions{
  691. Committer: &git.Signature{
  692. Email: "user2@example.com",
  693. Name: "user2",
  694. When: time.Now(),
  695. },
  696. Author: &git.Signature{
  697. Email: "user2@example.com",
  698. Name: "user2",
  699. When: time.Now(),
  700. },
  701. Message: "Testing commit 1",
  702. })
  703. assert.NoError(t, err)
  704. commit, err = gitRepo.GetRefCommitID("HEAD")
  705. assert.NoError(t, err)
  706. })
  707. t.Run("Push", func(t *testing.T) {
  708. err := gitcmd.NewCommand("push", "origin", "HEAD:refs/for/master", "-o").AddDynamicArguments("topic="+headBranch).Run(t.Context(), &gitcmd.RunOpts{Dir: dstPath})
  709. require.NoError(t, err)
  710. unittest.AssertCount(t, &issues_model.PullRequest{}, pullNum+1)
  711. pr1 = unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{
  712. HeadRepoID: repo.ID,
  713. Flow: issues_model.PullRequestFlowAGit,
  714. })
  715. require.NotEmpty(t, pr1)
  716. prMsg, err := doAPIGetPullRequest(*ctx, ctx.Username, ctx.Reponame, pr1.Index)(t)
  717. require.NoError(t, err)
  718. assert.Equal(t, "user2/"+headBranch, pr1.HeadBranch)
  719. assert.False(t, prMsg.HasMerged)
  720. assert.Contains(t, "Testing commit 1", prMsg.Body)
  721. assert.Equal(t, commit, prMsg.Head.Sha)
  722. _, _, err = gitcmd.NewCommand("push", "origin").AddDynamicArguments("HEAD:refs/for/master/test/"+headBranch).RunStdString(t.Context(), &gitcmd.RunOpts{Dir: dstPath})
  723. require.NoError(t, err)
  724. unittest.AssertCount(t, &issues_model.PullRequest{}, pullNum+2)
  725. pr2 = unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{
  726. HeadRepoID: repo.ID,
  727. Index: pr1.Index + 1,
  728. Flow: issues_model.PullRequestFlowAGit,
  729. })
  730. require.NotEmpty(t, pr2)
  731. prMsg, err = doAPIGetPullRequest(*ctx, ctx.Username, ctx.Reponame, pr2.Index)(t)
  732. require.NoError(t, err)
  733. assert.Equal(t, "user2/test/"+headBranch, pr2.HeadBranch)
  734. assert.False(t, prMsg.HasMerged)
  735. })
  736. if pr1 == nil || pr2 == nil {
  737. return
  738. }
  739. t.Run("AddCommit2", func(t *testing.T) {
  740. err := os.WriteFile(path.Join(dstPath, "test_file"), []byte("## test content \n ## test content 2"), 0o666)
  741. require.NoError(t, err)
  742. err = git.AddChanges(t.Context(), dstPath, true)
  743. assert.NoError(t, err)
  744. err = git.CommitChanges(t.Context(), dstPath, git.CommitChangesOptions{
  745. Committer: &git.Signature{
  746. Email: "user2@example.com",
  747. Name: "user2",
  748. When: time.Now(),
  749. },
  750. Author: &git.Signature{
  751. Email: "user2@example.com",
  752. Name: "user2",
  753. When: time.Now(),
  754. },
  755. Message: "Testing commit 2",
  756. })
  757. assert.NoError(t, err)
  758. commit, err = gitRepo.GetRefCommitID("HEAD")
  759. assert.NoError(t, err)
  760. })
  761. t.Run("Push2", func(t *testing.T) {
  762. err := gitcmd.NewCommand("push", "origin", "HEAD:refs/for/master", "-o").AddDynamicArguments("topic="+headBranch).Run(t.Context(), &gitcmd.RunOpts{Dir: dstPath})
  763. require.NoError(t, err)
  764. unittest.AssertCount(t, &issues_model.PullRequest{}, pullNum+2)
  765. prMsg, err := doAPIGetPullRequest(*ctx, ctx.Username, ctx.Reponame, pr1.Index)(t)
  766. require.NoError(t, err)
  767. assert.False(t, prMsg.HasMerged)
  768. assert.Equal(t, commit, prMsg.Head.Sha)
  769. _, _, err = gitcmd.NewCommand("push", "origin").AddDynamicArguments("HEAD:refs/for/master/test/"+headBranch).RunStdString(t.Context(), &gitcmd.RunOpts{Dir: dstPath})
  770. require.NoError(t, err)
  771. unittest.AssertCount(t, &issues_model.PullRequest{}, pullNum+2)
  772. prMsg, err = doAPIGetPullRequest(*ctx, ctx.Username, ctx.Reponame, pr2.Index)(t)
  773. require.NoError(t, err)
  774. assert.False(t, prMsg.HasMerged)
  775. assert.Equal(t, commit, prMsg.Head.Sha)
  776. })
  777. t.Run("Merge", doAPIMergePullRequest(*ctx, ctx.Username, ctx.Reponame, pr1.Index))
  778. t.Run("CheckoutMasterAgain", doGitCheckoutBranch(dstPath, "master"))
  779. }
  780. }