gitea源码

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485
  1. // Copyright 2023 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package repository
  4. import (
  5. "bytes"
  6. "context"
  7. "fmt"
  8. "os"
  9. "path/filepath"
  10. "strings"
  11. "time"
  12. "code.gitea.io/gitea/models/db"
  13. "code.gitea.io/gitea/models/organization"
  14. "code.gitea.io/gitea/models/perm"
  15. access_model "code.gitea.io/gitea/models/perm/access"
  16. repo_model "code.gitea.io/gitea/models/repo"
  17. system_model "code.gitea.io/gitea/models/system"
  18. "code.gitea.io/gitea/models/unit"
  19. user_model "code.gitea.io/gitea/models/user"
  20. "code.gitea.io/gitea/models/webhook"
  21. "code.gitea.io/gitea/modules/git"
  22. "code.gitea.io/gitea/modules/git/gitcmd"
  23. "code.gitea.io/gitea/modules/gitrepo"
  24. "code.gitea.io/gitea/modules/graceful"
  25. "code.gitea.io/gitea/modules/log"
  26. "code.gitea.io/gitea/modules/options"
  27. repo_module "code.gitea.io/gitea/modules/repository"
  28. "code.gitea.io/gitea/modules/setting"
  29. api "code.gitea.io/gitea/modules/structs"
  30. "code.gitea.io/gitea/modules/templates/vars"
  31. )
  32. // CreateRepoOptions contains the create repository options
  33. type CreateRepoOptions struct {
  34. Name string
  35. Description string
  36. OriginalURL string
  37. GitServiceType api.GitServiceType
  38. Gitignores string
  39. IssueLabels string
  40. License string
  41. Readme string
  42. DefaultBranch string
  43. IsPrivate bool
  44. IsMirror bool
  45. IsTemplate bool
  46. AutoInit bool
  47. Status repo_model.RepositoryStatus
  48. TrustModel repo_model.TrustModelType
  49. MirrorInterval string
  50. ObjectFormatName string
  51. }
  52. func prepareRepoCommit(ctx context.Context, repo *repo_model.Repository, tmpDir string, opts CreateRepoOptions) error {
  53. commitTimeStr := time.Now().Format(time.RFC3339)
  54. authorSig := repo.Owner.NewGitSig()
  55. // Because this may call hooks we should pass in the environment
  56. env := append(os.Environ(),
  57. "GIT_AUTHOR_NAME="+authorSig.Name,
  58. "GIT_AUTHOR_EMAIL="+authorSig.Email,
  59. "GIT_AUTHOR_DATE="+commitTimeStr,
  60. "GIT_COMMITTER_NAME="+authorSig.Name,
  61. "GIT_COMMITTER_EMAIL="+authorSig.Email,
  62. "GIT_COMMITTER_DATE="+commitTimeStr,
  63. )
  64. // Clone to temporary path and do the init commit.
  65. if stdout, _, err := gitcmd.NewCommand("clone").AddDynamicArguments(repo.RepoPath(), tmpDir).
  66. RunStdString(ctx, &gitcmd.RunOpts{Dir: "", Env: env}); err != nil {
  67. log.Error("Failed to clone from %v into %s: stdout: %s\nError: %v", repo, tmpDir, stdout, err)
  68. return fmt.Errorf("git clone: %w", err)
  69. }
  70. // README
  71. data, err := options.Readme(opts.Readme)
  72. if err != nil {
  73. return fmt.Errorf("GetRepoInitFile[%s]: %w", opts.Readme, err)
  74. }
  75. cloneLink := repo.CloneLink(ctx, nil /* no doer so do not generate user-related SSH link */)
  76. match := map[string]string{
  77. "Name": repo.Name,
  78. "Description": repo.Description,
  79. "CloneURL.SSH": cloneLink.SSH,
  80. "CloneURL.HTTPS": cloneLink.HTTPS,
  81. "OwnerName": repo.OwnerName,
  82. }
  83. res, err := vars.Expand(string(data), match)
  84. if err != nil {
  85. // here we could just log the error and continue the rendering
  86. log.Error("unable to expand template vars for repo README: %s, err: %v", opts.Readme, err)
  87. }
  88. if err = os.WriteFile(filepath.Join(tmpDir, "README.md"),
  89. []byte(res), 0o644); err != nil {
  90. return fmt.Errorf("write README.md: %w", err)
  91. }
  92. // .gitignore
  93. if len(opts.Gitignores) > 0 {
  94. var buf bytes.Buffer
  95. names := strings.SplitSeq(opts.Gitignores, ",")
  96. for name := range names {
  97. data, err = options.Gitignore(name)
  98. if err != nil {
  99. return fmt.Errorf("GetRepoInitFile[%s]: %w", name, err)
  100. }
  101. buf.WriteString("# ---> " + name + "\n")
  102. buf.Write(data)
  103. buf.WriteString("\n")
  104. }
  105. if buf.Len() > 0 {
  106. if err = os.WriteFile(filepath.Join(tmpDir, ".gitignore"), buf.Bytes(), 0o644); err != nil {
  107. return fmt.Errorf("write .gitignore: %w", err)
  108. }
  109. }
  110. }
  111. // LICENSE
  112. if len(opts.License) > 0 {
  113. data, err = repo_module.GetLicense(opts.License, &repo_module.LicenseValues{
  114. Owner: repo.OwnerName,
  115. Email: authorSig.Email,
  116. Repo: repo.Name,
  117. Year: time.Now().Format("2006"),
  118. })
  119. if err != nil {
  120. return fmt.Errorf("getLicense[%s]: %w", opts.License, err)
  121. }
  122. if err = os.WriteFile(filepath.Join(tmpDir, "LICENSE"), data, 0o644); err != nil {
  123. return fmt.Errorf("write LICENSE: %w", err)
  124. }
  125. }
  126. return nil
  127. }
  128. // InitRepository initializes README and .gitignore if needed.
  129. func initRepository(ctx context.Context, u *user_model.User, repo *repo_model.Repository, opts CreateRepoOptions) (err error) {
  130. // Init git bare new repository.
  131. if err = gitrepo.InitRepository(ctx, repo, repo.ObjectFormatName); err != nil {
  132. return fmt.Errorf("git.InitRepository: %w", err)
  133. } else if err = gitrepo.CreateDelegateHooks(ctx, repo); err != nil {
  134. return fmt.Errorf("createDelegateHooks: %w", err)
  135. }
  136. // Initialize repository according to user's choice.
  137. if opts.AutoInit {
  138. tmpDir, cleanup, err := setting.AppDataTempDir("git-repo-content").MkdirTempRandom("repos-" + repo.Name)
  139. if err != nil {
  140. return fmt.Errorf("failed to create temp dir for repository %s: %w", repo.FullName(), err)
  141. }
  142. defer cleanup()
  143. if err = prepareRepoCommit(ctx, repo, tmpDir, opts); err != nil {
  144. return fmt.Errorf("prepareRepoCommit: %w", err)
  145. }
  146. // Apply changes and commit.
  147. if err = initRepoCommit(ctx, tmpDir, repo, u, opts.DefaultBranch); err != nil {
  148. return fmt.Errorf("initRepoCommit: %w", err)
  149. }
  150. }
  151. // Re-fetch the repository from database before updating it (else it would
  152. // override changes that were done earlier with sql)
  153. if repo, err = repo_model.GetRepositoryByID(ctx, repo.ID); err != nil {
  154. return fmt.Errorf("getRepositoryByID: %w", err)
  155. }
  156. if !opts.AutoInit {
  157. repo.IsEmpty = true
  158. }
  159. repo.DefaultBranch = setting.Repository.DefaultBranch
  160. repo.DefaultWikiBranch = setting.Repository.DefaultBranch
  161. if len(opts.DefaultBranch) > 0 {
  162. repo.DefaultBranch = opts.DefaultBranch
  163. if err = gitrepo.SetDefaultBranch(ctx, repo, repo.DefaultBranch); err != nil {
  164. return fmt.Errorf("setDefaultBranch: %w", err)
  165. }
  166. if !repo.IsEmpty {
  167. if _, err := repo_module.SyncRepoBranches(ctx, repo.ID, u.ID); err != nil {
  168. return fmt.Errorf("SyncRepoBranches: %w", err)
  169. }
  170. }
  171. }
  172. if err = repo_model.UpdateRepositoryColsNoAutoTime(ctx, repo, "is_empty", "default_branch", "default_wiki_branch"); err != nil {
  173. return fmt.Errorf("updateRepository: %w", err)
  174. }
  175. if err = repo_module.UpdateRepoSize(ctx, repo); err != nil {
  176. log.Error("Failed to update size for repository: %v", err)
  177. }
  178. return nil
  179. }
  180. // CreateRepositoryDirectly creates a repository for the user/organization.
  181. // if needsUpdateToReady is true, it will update the repository status to ready when success
  182. func CreateRepositoryDirectly(ctx context.Context, doer, owner *user_model.User,
  183. opts CreateRepoOptions, needsUpdateToReady bool,
  184. ) (*repo_model.Repository, error) {
  185. if !doer.CanCreateRepoIn(owner) {
  186. return nil, repo_model.ErrReachLimitOfRepo{
  187. Limit: owner.MaxRepoCreation,
  188. }
  189. }
  190. if len(opts.DefaultBranch) == 0 {
  191. opts.DefaultBranch = setting.Repository.DefaultBranch
  192. }
  193. // Check if label template exist
  194. if len(opts.IssueLabels) > 0 {
  195. if _, err := repo_module.LoadTemplateLabelsByDisplayName(opts.IssueLabels); err != nil {
  196. return nil, err
  197. }
  198. }
  199. if opts.ObjectFormatName == "" {
  200. opts.ObjectFormatName = git.Sha1ObjectFormat.Name()
  201. }
  202. repo := &repo_model.Repository{
  203. OwnerID: owner.ID,
  204. Owner: owner,
  205. OwnerName: owner.Name,
  206. Name: opts.Name,
  207. LowerName: strings.ToLower(opts.Name),
  208. Description: opts.Description,
  209. OriginalURL: opts.OriginalURL,
  210. OriginalServiceType: opts.GitServiceType,
  211. IsPrivate: opts.IsPrivate,
  212. IsFsckEnabled: !opts.IsMirror,
  213. IsTemplate: opts.IsTemplate,
  214. CloseIssuesViaCommitInAnyBranch: setting.Repository.DefaultCloseIssuesViaCommitsInAnyBranch,
  215. Status: opts.Status,
  216. IsEmpty: !opts.AutoInit,
  217. TrustModel: opts.TrustModel,
  218. IsMirror: opts.IsMirror,
  219. DefaultBranch: opts.DefaultBranch,
  220. DefaultWikiBranch: setting.Repository.DefaultBranch,
  221. ObjectFormatName: opts.ObjectFormatName,
  222. }
  223. // 1 - create the repository database operations first
  224. err := db.WithTx(ctx, func(ctx context.Context) error {
  225. return createRepositoryInDB(ctx, doer, owner, repo, false)
  226. })
  227. if err != nil {
  228. return nil, err
  229. }
  230. // last - clean up if something goes wrong
  231. // WARNING: Don't override all later err with local variables
  232. defer func() {
  233. if err != nil {
  234. // we can not use the ctx because it maybe canceled or timeout
  235. cleanupRepository(repo.ID)
  236. }
  237. }()
  238. // No need for init mirror.
  239. if opts.IsMirror {
  240. return repo, nil
  241. }
  242. // 2 - check whether the repository with the same storage exists
  243. var isExist bool
  244. isExist, err = gitrepo.IsRepositoryExist(ctx, repo)
  245. if err != nil {
  246. log.Error("Unable to check if %s exists. Error: %v", repo.FullName(), err)
  247. return nil, err
  248. }
  249. if isExist {
  250. log.Error("Files already exist in %s and we are not going to adopt or delete.", repo.FullName())
  251. // Don't return directly, we need err in defer to cleanupRepository
  252. err = repo_model.ErrRepoFilesAlreadyExist{
  253. Uname: repo.OwnerName,
  254. Name: repo.Name,
  255. }
  256. return nil, err
  257. }
  258. // 3 - init git repository in storage
  259. if err = initRepository(ctx, doer, repo, opts); err != nil {
  260. return nil, fmt.Errorf("initRepository: %w", err)
  261. }
  262. // 4 - Initialize Issue Labels if selected
  263. if len(opts.IssueLabels) > 0 {
  264. if err = repo_module.InitializeLabels(ctx, repo.ID, opts.IssueLabels, false); err != nil {
  265. return nil, fmt.Errorf("InitializeLabels: %w", err)
  266. }
  267. }
  268. // 5 - Update the git repository
  269. if err = updateGitRepoAfterCreate(ctx, repo); err != nil {
  270. return nil, fmt.Errorf("updateGitRepoAfterCreate: %w", err)
  271. }
  272. // 6 - update licenses
  273. var licenses []string
  274. if len(opts.License) > 0 {
  275. licenses = append(licenses, opts.License)
  276. var stdout string
  277. stdout, _, err = gitcmd.NewCommand("rev-parse", "HEAD").RunStdString(ctx, &gitcmd.RunOpts{Dir: repo.RepoPath()})
  278. if err != nil {
  279. log.Error("CreateRepository(git rev-parse HEAD) in %v: Stdout: %s\nError: %v", repo, stdout, err)
  280. return nil, fmt.Errorf("CreateRepository(git rev-parse HEAD): %w", err)
  281. }
  282. if err = repo_model.UpdateRepoLicenses(ctx, repo, stdout, licenses); err != nil {
  283. return nil, err
  284. }
  285. }
  286. // 7 - update repository status to be ready
  287. if needsUpdateToReady {
  288. repo.Status = repo_model.RepositoryReady
  289. if err = repo_model.UpdateRepositoryColsWithAutoTime(ctx, repo, "status"); err != nil {
  290. return nil, fmt.Errorf("UpdateRepositoryCols: %w", err)
  291. }
  292. }
  293. return repo, nil
  294. }
  295. // createRepositoryInDB creates a repository for the user/organization.
  296. func createRepositoryInDB(ctx context.Context, doer, u *user_model.User, repo *repo_model.Repository, isFork bool) (err error) {
  297. if err = repo_model.IsUsableRepoName(repo.Name); err != nil {
  298. return err
  299. }
  300. has, err := repo_model.IsRepositoryModelExist(ctx, u, repo.Name)
  301. if err != nil {
  302. return fmt.Errorf("IsRepositoryExist: %w", err)
  303. } else if has {
  304. return repo_model.ErrRepoAlreadyExist{
  305. Uname: u.Name,
  306. Name: repo.Name,
  307. }
  308. }
  309. if err = db.Insert(ctx, repo); err != nil {
  310. return err
  311. }
  312. if err = repo_model.DeleteRedirect(ctx, u.ID, repo.Name); err != nil {
  313. return err
  314. }
  315. // insert units for repo
  316. defaultUnits := unit.DefaultRepoUnits
  317. switch {
  318. case isFork:
  319. defaultUnits = unit.DefaultForkRepoUnits
  320. case repo.IsMirror:
  321. defaultUnits = unit.DefaultMirrorRepoUnits
  322. case repo.IsTemplate:
  323. defaultUnits = unit.DefaultTemplateRepoUnits
  324. }
  325. units := make([]repo_model.RepoUnit, 0, len(defaultUnits))
  326. for _, tp := range defaultUnits {
  327. switch tp {
  328. case unit.TypeIssues:
  329. units = append(units, repo_model.RepoUnit{
  330. RepoID: repo.ID,
  331. Type: tp,
  332. Config: &repo_model.IssuesConfig{
  333. EnableTimetracker: setting.Service.DefaultEnableTimetracking,
  334. AllowOnlyContributorsToTrackTime: setting.Service.DefaultAllowOnlyContributorsToTrackTime,
  335. EnableDependencies: setting.Service.DefaultEnableDependencies,
  336. },
  337. })
  338. case unit.TypePullRequests:
  339. units = append(units, repo_model.RepoUnit{
  340. RepoID: repo.ID,
  341. Type: tp,
  342. Config: &repo_model.PullRequestsConfig{
  343. AllowMerge: true, AllowRebase: true, AllowRebaseMerge: true, AllowSquash: true, AllowFastForwardOnly: true,
  344. DefaultMergeStyle: repo_model.MergeStyle(setting.Repository.PullRequest.DefaultMergeStyle),
  345. AllowRebaseUpdate: true,
  346. },
  347. })
  348. case unit.TypeProjects:
  349. units = append(units, repo_model.RepoUnit{
  350. RepoID: repo.ID,
  351. Type: tp,
  352. Config: &repo_model.ProjectsConfig{ProjectsMode: repo_model.ProjectsModeAll},
  353. })
  354. default:
  355. units = append(units, repo_model.RepoUnit{
  356. RepoID: repo.ID,
  357. Type: tp,
  358. })
  359. }
  360. }
  361. if err = db.Insert(ctx, units); err != nil {
  362. return err
  363. }
  364. // Remember visibility preference.
  365. u.LastRepoVisibility = repo.IsPrivate
  366. if err = user_model.UpdateUserCols(ctx, u, "last_repo_visibility"); err != nil {
  367. return fmt.Errorf("UpdateUserCols: %w", err)
  368. }
  369. if err = user_model.IncrUserRepoNum(ctx, u.ID); err != nil {
  370. return fmt.Errorf("IncrUserRepoNum: %w", err)
  371. }
  372. u.NumRepos++
  373. // Give access to all members in teams with access to all repositories.
  374. if u.IsOrganization() {
  375. teams, err := organization.FindOrgTeams(ctx, u.ID)
  376. if err != nil {
  377. return fmt.Errorf("FindOrgTeams: %w", err)
  378. }
  379. for _, t := range teams {
  380. if t.IncludesAllRepositories {
  381. if err := addRepositoryToTeam(ctx, t, repo); err != nil {
  382. return fmt.Errorf("AddRepository: %w", err)
  383. }
  384. }
  385. }
  386. if isAdmin, err := access_model.IsUserRepoAdmin(ctx, repo, doer); err != nil {
  387. return fmt.Errorf("IsUserRepoAdmin: %w", err)
  388. } else if !isAdmin {
  389. // Make creator repo admin if it wasn't assigned automatically
  390. if err = AddOrUpdateCollaborator(ctx, repo, doer, perm.AccessModeAdmin); err != nil {
  391. return fmt.Errorf("AddCollaborator: %w", err)
  392. }
  393. }
  394. } else if err = access_model.RecalculateAccesses(ctx, repo); err != nil {
  395. // Organization automatically called this in AddRepository method.
  396. return fmt.Errorf("RecalculateAccesses: %w", err)
  397. }
  398. if setting.Service.AutoWatchNewRepos {
  399. if err = repo_model.WatchRepo(ctx, doer, repo, true); err != nil {
  400. return fmt.Errorf("WatchRepo: %w", err)
  401. }
  402. }
  403. if err = webhook.CopyDefaultWebhooksToRepo(ctx, repo.ID); err != nil {
  404. return fmt.Errorf("CopyDefaultWebhooksToRepo: %w", err)
  405. }
  406. return nil
  407. }
  408. func cleanupRepository(repoID int64) {
  409. if errDelete := DeleteRepositoryDirectly(graceful.GetManager().ShutdownContext(), repoID); errDelete != nil {
  410. log.Error("cleanupRepository failed: %v", errDelete)
  411. // add system notice
  412. if err := system_model.CreateRepositoryNotice("DeleteRepositoryDirectly failed when cleanup repository: %v", errDelete); err != nil {
  413. log.Error("CreateRepositoryNotice: %v", err)
  414. }
  415. }
  416. }
  417. func updateGitRepoAfterCreate(ctx context.Context, repo *repo_model.Repository) error {
  418. if err := CheckDaemonExportOK(ctx, repo); err != nil {
  419. return fmt.Errorf("checkDaemonExportOK: %w", err)
  420. }
  421. if stdout, _, err := gitcmd.NewCommand("update-server-info").
  422. RunStdString(ctx, &gitcmd.RunOpts{Dir: repo.RepoPath()}); err != nil {
  423. log.Error("CreateRepository(git update-server-info) in %v: Stdout: %s\nError: %v", repo, stdout, err)
  424. return fmt.Errorf("CreateRepository(git update-server-info): %w", err)
  425. }
  426. return nil
  427. }