You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

create.go 9.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314
  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. repo_model "code.gitea.io/gitea/models/repo"
  14. user_model "code.gitea.io/gitea/models/user"
  15. "code.gitea.io/gitea/modules/git"
  16. "code.gitea.io/gitea/modules/log"
  17. "code.gitea.io/gitea/modules/options"
  18. repo_module "code.gitea.io/gitea/modules/repository"
  19. "code.gitea.io/gitea/modules/setting"
  20. api "code.gitea.io/gitea/modules/structs"
  21. "code.gitea.io/gitea/modules/templates/vars"
  22. "code.gitea.io/gitea/modules/util"
  23. )
  24. // CreateRepoOptions contains the create repository options
  25. type CreateRepoOptions struct {
  26. Name string
  27. Description string
  28. OriginalURL string
  29. GitServiceType api.GitServiceType
  30. Gitignores string
  31. IssueLabels string
  32. License string
  33. Readme string
  34. DefaultBranch string
  35. IsPrivate bool
  36. IsMirror bool
  37. IsTemplate bool
  38. AutoInit bool
  39. Status repo_model.RepositoryStatus
  40. TrustModel repo_model.TrustModelType
  41. MirrorInterval string
  42. }
  43. func prepareRepoCommit(ctx context.Context, repo *repo_model.Repository, tmpDir, repoPath string, opts CreateRepoOptions) error {
  44. commitTimeStr := time.Now().Format(time.RFC3339)
  45. authorSig := repo.Owner.NewGitSig()
  46. // Because this may call hooks we should pass in the environment
  47. env := append(os.Environ(),
  48. "GIT_AUTHOR_NAME="+authorSig.Name,
  49. "GIT_AUTHOR_EMAIL="+authorSig.Email,
  50. "GIT_AUTHOR_DATE="+commitTimeStr,
  51. "GIT_COMMITTER_NAME="+authorSig.Name,
  52. "GIT_COMMITTER_EMAIL="+authorSig.Email,
  53. "GIT_COMMITTER_DATE="+commitTimeStr,
  54. )
  55. // Clone to temporary path and do the init commit.
  56. if stdout, _, err := git.NewCommand(ctx, "clone").AddDynamicArguments(repoPath, tmpDir).
  57. SetDescription(fmt.Sprintf("prepareRepoCommit (git clone): %s to %s", repoPath, tmpDir)).
  58. RunStdString(&git.RunOpts{Dir: "", Env: env}); err != nil {
  59. log.Error("Failed to clone from %v into %s: stdout: %s\nError: %v", repo, tmpDir, stdout, err)
  60. return fmt.Errorf("git clone: %w", err)
  61. }
  62. // README
  63. data, err := options.Readme(opts.Readme)
  64. if err != nil {
  65. return fmt.Errorf("GetRepoInitFile[%s]: %w", opts.Readme, err)
  66. }
  67. cloneLink := repo.CloneLink()
  68. match := map[string]string{
  69. "Name": repo.Name,
  70. "Description": repo.Description,
  71. "CloneURL.SSH": cloneLink.SSH,
  72. "CloneURL.HTTPS": cloneLink.HTTPS,
  73. "OwnerName": repo.OwnerName,
  74. }
  75. res, err := vars.Expand(string(data), match)
  76. if err != nil {
  77. // here we could just log the error and continue the rendering
  78. log.Error("unable to expand template vars for repo README: %s, err: %v", opts.Readme, err)
  79. }
  80. if err = os.WriteFile(filepath.Join(tmpDir, "README.md"),
  81. []byte(res), 0o644); err != nil {
  82. return fmt.Errorf("write README.md: %w", err)
  83. }
  84. // .gitignore
  85. if len(opts.Gitignores) > 0 {
  86. var buf bytes.Buffer
  87. names := strings.Split(opts.Gitignores, ",")
  88. for _, name := range names {
  89. data, err = options.Gitignore(name)
  90. if err != nil {
  91. return fmt.Errorf("GetRepoInitFile[%s]: %w", name, err)
  92. }
  93. buf.WriteString("# ---> " + name + "\n")
  94. buf.Write(data)
  95. buf.WriteString("\n")
  96. }
  97. if buf.Len() > 0 {
  98. if err = os.WriteFile(filepath.Join(tmpDir, ".gitignore"), buf.Bytes(), 0o644); err != nil {
  99. return fmt.Errorf("write .gitignore: %w", err)
  100. }
  101. }
  102. }
  103. // LICENSE
  104. if len(opts.License) > 0 {
  105. data, err = repo_module.GetLicense(opts.License, &repo_module.LicenseValues{
  106. Owner: repo.OwnerName,
  107. Email: authorSig.Email,
  108. Repo: repo.Name,
  109. Year: time.Now().Format("2006"),
  110. })
  111. if err != nil {
  112. return fmt.Errorf("getLicense[%s]: %w", opts.License, err)
  113. }
  114. if err = os.WriteFile(filepath.Join(tmpDir, "LICENSE"), data, 0o644); err != nil {
  115. return fmt.Errorf("write LICENSE: %w", err)
  116. }
  117. }
  118. return nil
  119. }
  120. // InitRepository initializes README and .gitignore if needed.
  121. func initRepository(ctx context.Context, repoPath string, u *user_model.User, repo *repo_model.Repository, opts CreateRepoOptions) (err error) {
  122. if err = repo_module.CheckInitRepository(ctx, repo.OwnerName, repo.Name); err != nil {
  123. return err
  124. }
  125. // Initialize repository according to user's choice.
  126. if opts.AutoInit {
  127. tmpDir, err := os.MkdirTemp(os.TempDir(), "gitea-"+repo.Name)
  128. if err != nil {
  129. return fmt.Errorf("Failed to create temp dir for repository %s: %w", repo.RepoPath(), err)
  130. }
  131. defer func() {
  132. if err := util.RemoveAll(tmpDir); err != nil {
  133. log.Warn("Unable to remove temporary directory: %s: Error: %v", tmpDir, err)
  134. }
  135. }()
  136. if err = prepareRepoCommit(ctx, repo, tmpDir, repoPath, opts); err != nil {
  137. return fmt.Errorf("prepareRepoCommit: %w", err)
  138. }
  139. // Apply changes and commit.
  140. if err = repo_module.InitRepoCommit(ctx, tmpDir, repo, u, opts.DefaultBranch); err != nil {
  141. return fmt.Errorf("initRepoCommit: %w", err)
  142. }
  143. }
  144. // Re-fetch the repository from database before updating it (else it would
  145. // override changes that were done earlier with sql)
  146. if repo, err = repo_model.GetRepositoryByID(ctx, repo.ID); err != nil {
  147. return fmt.Errorf("getRepositoryByID: %w", err)
  148. }
  149. if !opts.AutoInit {
  150. repo.IsEmpty = true
  151. }
  152. repo.DefaultBranch = setting.Repository.DefaultBranch
  153. if len(opts.DefaultBranch) > 0 {
  154. repo.DefaultBranch = opts.DefaultBranch
  155. gitRepo, err := git.OpenRepository(ctx, repo.RepoPath())
  156. if err != nil {
  157. return fmt.Errorf("openRepository: %w", err)
  158. }
  159. defer gitRepo.Close()
  160. if err = gitRepo.SetDefaultBranch(repo.DefaultBranch); err != nil {
  161. return fmt.Errorf("setDefaultBranch: %w", err)
  162. }
  163. if !repo.IsEmpty {
  164. if _, err := repo_module.SyncRepoBranches(ctx, repo.ID, u.ID); err != nil {
  165. return fmt.Errorf("SyncRepoBranches: %w", err)
  166. }
  167. }
  168. }
  169. if err = UpdateRepository(ctx, repo, false); err != nil {
  170. return fmt.Errorf("updateRepository: %w", err)
  171. }
  172. return nil
  173. }
  174. // CreateRepositoryDirectly creates a repository for the user/organization.
  175. func CreateRepositoryDirectly(ctx context.Context, doer, u *user_model.User, opts CreateRepoOptions) (*repo_model.Repository, error) {
  176. if !doer.IsAdmin && !u.CanCreateRepo() {
  177. return nil, repo_model.ErrReachLimitOfRepo{
  178. Limit: u.MaxRepoCreation,
  179. }
  180. }
  181. if len(opts.DefaultBranch) == 0 {
  182. opts.DefaultBranch = setting.Repository.DefaultBranch
  183. }
  184. // Check if label template exist
  185. if len(opts.IssueLabels) > 0 {
  186. if _, err := repo_module.LoadTemplateLabelsByDisplayName(opts.IssueLabels); err != nil {
  187. return nil, err
  188. }
  189. }
  190. repo := &repo_model.Repository{
  191. OwnerID: u.ID,
  192. Owner: u,
  193. OwnerName: u.Name,
  194. Name: opts.Name,
  195. LowerName: strings.ToLower(opts.Name),
  196. Description: opts.Description,
  197. OriginalURL: opts.OriginalURL,
  198. OriginalServiceType: opts.GitServiceType,
  199. IsPrivate: opts.IsPrivate,
  200. IsFsckEnabled: !opts.IsMirror,
  201. IsTemplate: opts.IsTemplate,
  202. CloseIssuesViaCommitInAnyBranch: setting.Repository.DefaultCloseIssuesViaCommitsInAnyBranch,
  203. Status: opts.Status,
  204. IsEmpty: !opts.AutoInit,
  205. TrustModel: opts.TrustModel,
  206. IsMirror: opts.IsMirror,
  207. DefaultBranch: opts.DefaultBranch,
  208. }
  209. var rollbackRepo *repo_model.Repository
  210. if err := db.WithTx(ctx, func(ctx context.Context) error {
  211. if err := repo_module.CreateRepositoryByExample(ctx, doer, u, repo, false, false); err != nil {
  212. return err
  213. }
  214. // No need for init mirror.
  215. if opts.IsMirror {
  216. return nil
  217. }
  218. repoPath := repo_model.RepoPath(u.Name, repo.Name)
  219. isExist, err := util.IsExist(repoPath)
  220. if err != nil {
  221. log.Error("Unable to check if %s exists. Error: %v", repoPath, err)
  222. return err
  223. }
  224. if isExist {
  225. // repo already exists - We have two or three options.
  226. // 1. We fail stating that the directory exists
  227. // 2. We create the db repository to go with this data and adopt the git repo
  228. // 3. We delete it and start afresh
  229. //
  230. // Previously Gitea would just delete and start afresh - this was naughty.
  231. // So we will now fail and delegate to other functionality to adopt or delete
  232. log.Error("Files already exist in %s and we are not going to adopt or delete.", repoPath)
  233. return repo_model.ErrRepoFilesAlreadyExist{
  234. Uname: u.Name,
  235. Name: repo.Name,
  236. }
  237. }
  238. if err = initRepository(ctx, repoPath, doer, repo, opts); err != nil {
  239. if err2 := util.RemoveAll(repoPath); err2 != nil {
  240. log.Error("initRepository: %v", err)
  241. return fmt.Errorf(
  242. "delete repo directory %s/%s failed(2): %v", u.Name, repo.Name, err2)
  243. }
  244. return fmt.Errorf("initRepository: %w", err)
  245. }
  246. // Initialize Issue Labels if selected
  247. if len(opts.IssueLabels) > 0 {
  248. if err = repo_module.InitializeLabels(ctx, repo.ID, opts.IssueLabels, false); err != nil {
  249. rollbackRepo = repo
  250. rollbackRepo.OwnerID = u.ID
  251. return fmt.Errorf("InitializeLabels: %w", err)
  252. }
  253. }
  254. if err := repo_module.CheckDaemonExportOK(ctx, repo); err != nil {
  255. return fmt.Errorf("checkDaemonExportOK: %w", err)
  256. }
  257. if stdout, _, err := git.NewCommand(ctx, "update-server-info").
  258. SetDescription(fmt.Sprintf("CreateRepository(git update-server-info): %s", repoPath)).
  259. RunStdString(&git.RunOpts{Dir: repoPath}); err != nil {
  260. log.Error("CreateRepository(git update-server-info) in %v: Stdout: %s\nError: %v", repo, stdout, err)
  261. rollbackRepo = repo
  262. rollbackRepo.OwnerID = u.ID
  263. return fmt.Errorf("CreateRepository(git update-server-info): %w", err)
  264. }
  265. return nil
  266. }); err != nil {
  267. if rollbackRepo != nil {
  268. if errDelete := DeleteRepositoryDirectly(ctx, doer, rollbackRepo.OwnerID, rollbackRepo.ID); errDelete != nil {
  269. log.Error("Rollback deleteRepository: %v", errDelete)
  270. }
  271. }
  272. return nil, err
  273. }
  274. return repo, nil
  275. }