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.

repo.go 24KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774
  1. // Copyright 2021 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package repo
  4. import (
  5. "context"
  6. "fmt"
  7. "html/template"
  8. "net"
  9. "net/url"
  10. "path/filepath"
  11. "strconv"
  12. "strings"
  13. "code.gitea.io/gitea/models/db"
  14. "code.gitea.io/gitea/models/unit"
  15. user_model "code.gitea.io/gitea/models/user"
  16. "code.gitea.io/gitea/modules/log"
  17. "code.gitea.io/gitea/modules/markup"
  18. "code.gitea.io/gitea/modules/setting"
  19. api "code.gitea.io/gitea/modules/structs"
  20. "code.gitea.io/gitea/modules/timeutil"
  21. "code.gitea.io/gitea/modules/util"
  22. "xorm.io/builder"
  23. )
  24. // ErrUserDoesNotHaveAccessToRepo represents an error where the user doesn't has access to a given repo.
  25. type ErrUserDoesNotHaveAccessToRepo struct {
  26. UserID int64
  27. RepoName string
  28. }
  29. // IsErrUserDoesNotHaveAccessToRepo checks if an error is a ErrRepoFileAlreadyExists.
  30. func IsErrUserDoesNotHaveAccessToRepo(err error) bool {
  31. _, ok := err.(ErrUserDoesNotHaveAccessToRepo)
  32. return ok
  33. }
  34. func (err ErrUserDoesNotHaveAccessToRepo) Error() string {
  35. return fmt.Sprintf("user doesn't have access to repo [user_id: %d, repo_name: %s]", err.UserID, err.RepoName)
  36. }
  37. func (err ErrUserDoesNotHaveAccessToRepo) Unwrap() error {
  38. return util.ErrPermissionDenied
  39. }
  40. var (
  41. reservedRepoNames = []string{".", "..", "-"}
  42. reservedRepoPatterns = []string{"*.git", "*.wiki", "*.rss", "*.atom"}
  43. )
  44. // IsUsableRepoName returns true when repository is usable
  45. func IsUsableRepoName(name string) error {
  46. if db.AlphaDashDotPattern.MatchString(name) {
  47. // Note: usually this error is normally caught up earlier in the UI
  48. return db.ErrNameCharsNotAllowed{Name: name}
  49. }
  50. return db.IsUsableName(reservedRepoNames, reservedRepoPatterns, name)
  51. }
  52. // TrustModelType defines the types of trust model for this repository
  53. type TrustModelType int
  54. // kinds of TrustModel
  55. const (
  56. DefaultTrustModel TrustModelType = iota // default trust model
  57. CommitterTrustModel
  58. CollaboratorTrustModel
  59. CollaboratorCommitterTrustModel
  60. )
  61. // String converts a TrustModelType to a string
  62. func (t TrustModelType) String() string {
  63. switch t {
  64. case DefaultTrustModel:
  65. return "default"
  66. case CommitterTrustModel:
  67. return "committer"
  68. case CollaboratorTrustModel:
  69. return "collaborator"
  70. case CollaboratorCommitterTrustModel:
  71. return "collaboratorcommitter"
  72. }
  73. return "default"
  74. }
  75. // ToTrustModel converts a string to a TrustModelType
  76. func ToTrustModel(model string) TrustModelType {
  77. switch strings.ToLower(strings.TrimSpace(model)) {
  78. case "default":
  79. return DefaultTrustModel
  80. case "collaborator":
  81. return CollaboratorTrustModel
  82. case "committer":
  83. return CommitterTrustModel
  84. case "collaboratorcommitter":
  85. return CollaboratorCommitterTrustModel
  86. }
  87. return DefaultTrustModel
  88. }
  89. // RepositoryStatus defines the status of repository
  90. type RepositoryStatus int
  91. // all kinds of RepositoryStatus
  92. const (
  93. RepositoryReady RepositoryStatus = iota // a normal repository
  94. RepositoryBeingMigrated // repository is migrating
  95. RepositoryPendingTransfer // repository pending in ownership transfer state
  96. RepositoryBroken // repository is in a permanently broken state
  97. )
  98. // Repository represents a git repository.
  99. type Repository struct {
  100. ID int64 `xorm:"pk autoincr"`
  101. OwnerID int64 `xorm:"UNIQUE(s) index"`
  102. OwnerName string
  103. Owner *user_model.User `xorm:"-"`
  104. LowerName string `xorm:"UNIQUE(s) INDEX NOT NULL"`
  105. Name string `xorm:"INDEX NOT NULL"`
  106. Description string `xorm:"TEXT"`
  107. Website string `xorm:"VARCHAR(2048)"`
  108. OriginalServiceType api.GitServiceType `xorm:"index"`
  109. OriginalURL string `xorm:"VARCHAR(2048)"`
  110. DefaultBranch string
  111. NumWatches int
  112. NumStars int
  113. NumForks int
  114. NumIssues int
  115. NumClosedIssues int
  116. NumOpenIssues int `xorm:"-"`
  117. NumPulls int
  118. NumClosedPulls int
  119. NumOpenPulls int `xorm:"-"`
  120. NumMilestones int `xorm:"NOT NULL DEFAULT 0"`
  121. NumClosedMilestones int `xorm:"NOT NULL DEFAULT 0"`
  122. NumOpenMilestones int `xorm:"-"`
  123. NumProjects int `xorm:"NOT NULL DEFAULT 0"`
  124. NumClosedProjects int `xorm:"NOT NULL DEFAULT 0"`
  125. NumOpenProjects int `xorm:"-"`
  126. NumActionRuns int `xorm:"NOT NULL DEFAULT 0"`
  127. NumClosedActionRuns int `xorm:"NOT NULL DEFAULT 0"`
  128. NumOpenActionRuns int `xorm:"-"`
  129. IsPrivate bool `xorm:"INDEX"`
  130. IsEmpty bool `xorm:"INDEX"`
  131. IsArchived bool `xorm:"INDEX"`
  132. IsMirror bool `xorm:"INDEX"`
  133. *Mirror `xorm:"-"`
  134. Status RepositoryStatus `xorm:"NOT NULL DEFAULT 0"`
  135. RenderingMetas map[string]string `xorm:"-"`
  136. DocumentRenderingMetas map[string]string `xorm:"-"`
  137. Units []*RepoUnit `xorm:"-"`
  138. PrimaryLanguage *LanguageStat `xorm:"-"`
  139. IsFork bool `xorm:"INDEX NOT NULL DEFAULT false"`
  140. ForkID int64 `xorm:"INDEX"`
  141. BaseRepo *Repository `xorm:"-"`
  142. IsTemplate bool `xorm:"INDEX NOT NULL DEFAULT false"`
  143. TemplateID int64 `xorm:"INDEX"`
  144. Size int64 `xorm:"NOT NULL DEFAULT 0"`
  145. CodeIndexerStatus *RepoIndexerStatus `xorm:"-"`
  146. StatsIndexerStatus *RepoIndexerStatus `xorm:"-"`
  147. IsFsckEnabled bool `xorm:"NOT NULL DEFAULT true"`
  148. CloseIssuesViaCommitInAnyBranch bool `xorm:"NOT NULL DEFAULT false"`
  149. Topics []string `xorm:"TEXT JSON"`
  150. TrustModel TrustModelType
  151. // Avatar: ID(10-20)-md5(32) - must fit into 64 symbols
  152. Avatar string `xorm:"VARCHAR(64)"`
  153. CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
  154. UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
  155. }
  156. func init() {
  157. db.RegisterModel(new(Repository))
  158. }
  159. // SanitizedOriginalURL returns a sanitized OriginalURL
  160. func (repo *Repository) SanitizedOriginalURL() string {
  161. if repo.OriginalURL == "" {
  162. return ""
  163. }
  164. u, err := url.Parse(repo.OriginalURL)
  165. if err != nil {
  166. return ""
  167. }
  168. u.User = nil
  169. return u.String()
  170. }
  171. // ColorFormat returns a colored string to represent this repo
  172. func (repo *Repository) ColorFormat(s fmt.State) {
  173. if repo == nil {
  174. log.ColorFprintf(s, "%d:%s/%s",
  175. log.NewColoredIDValue(0),
  176. "<nil>",
  177. "<nil>")
  178. return
  179. }
  180. log.ColorFprintf(s, "%d:%s/%s",
  181. log.NewColoredIDValue(repo.ID),
  182. repo.OwnerName,
  183. repo.Name)
  184. }
  185. // IsBeingMigrated indicates that repository is being migrated
  186. func (repo *Repository) IsBeingMigrated() bool {
  187. return repo.Status == RepositoryBeingMigrated
  188. }
  189. // IsBeingCreated indicates that repository is being migrated or forked
  190. func (repo *Repository) IsBeingCreated() bool {
  191. return repo.IsBeingMigrated()
  192. }
  193. // IsBroken indicates that repository is broken
  194. func (repo *Repository) IsBroken() bool {
  195. return repo.Status == RepositoryBroken
  196. }
  197. // AfterLoad is invoked from XORM after setting the values of all fields of this object.
  198. func (repo *Repository) AfterLoad() {
  199. repo.NumOpenIssues = repo.NumIssues - repo.NumClosedIssues
  200. repo.NumOpenPulls = repo.NumPulls - repo.NumClosedPulls
  201. repo.NumOpenMilestones = repo.NumMilestones - repo.NumClosedMilestones
  202. repo.NumOpenProjects = repo.NumProjects - repo.NumClosedProjects
  203. repo.NumOpenActionRuns = repo.NumActionRuns - repo.NumClosedActionRuns
  204. }
  205. // LoadAttributes loads attributes of the repository.
  206. func (repo *Repository) LoadAttributes(ctx context.Context) error {
  207. // Load owner
  208. if err := repo.GetOwner(ctx); err != nil {
  209. return fmt.Errorf("load owner: %w", err)
  210. }
  211. // Load primary language
  212. stats := make(LanguageStatList, 0, 1)
  213. if err := db.GetEngine(ctx).
  214. Where("`repo_id` = ? AND `is_primary` = ? AND `language` != ?", repo.ID, true, "other").
  215. Find(&stats); err != nil {
  216. return fmt.Errorf("find primary languages: %w", err)
  217. }
  218. stats.LoadAttributes()
  219. for _, st := range stats {
  220. if st.RepoID == repo.ID {
  221. repo.PrimaryLanguage = st
  222. break
  223. }
  224. }
  225. return nil
  226. }
  227. // FullName returns the repository full name
  228. func (repo *Repository) FullName() string {
  229. return repo.OwnerName + "/" + repo.Name
  230. }
  231. // HTMLURL returns the repository HTML URL
  232. func (repo *Repository) HTMLURL() string {
  233. return setting.AppURL + url.PathEscape(repo.OwnerName) + "/" + url.PathEscape(repo.Name)
  234. }
  235. // CommitLink make link to by commit full ID
  236. // note: won't check whether it's an right id
  237. func (repo *Repository) CommitLink(commitID string) (result string) {
  238. if commitID == "" || commitID == "0000000000000000000000000000000000000000" {
  239. result = ""
  240. } else {
  241. result = repo.HTMLURL() + "/commit/" + url.PathEscape(commitID)
  242. }
  243. return result
  244. }
  245. // APIURL returns the repository API URL
  246. func (repo *Repository) APIURL() string {
  247. return setting.AppURL + "api/v1/repos/" + url.PathEscape(repo.OwnerName) + "/" + url.PathEscape(repo.Name)
  248. }
  249. // GetCommitsCountCacheKey returns cache key used for commits count caching.
  250. func (repo *Repository) GetCommitsCountCacheKey(contextName string, isRef bool) string {
  251. var prefix string
  252. if isRef {
  253. prefix = "ref"
  254. } else {
  255. prefix = "commit"
  256. }
  257. return fmt.Sprintf("commits-count-%d-%s-%s", repo.ID, prefix, contextName)
  258. }
  259. // LoadUnits loads repo units into repo.Units
  260. func (repo *Repository) LoadUnits(ctx context.Context) (err error) {
  261. if repo.Units != nil {
  262. return nil
  263. }
  264. repo.Units, err = getUnitsByRepoID(ctx, repo.ID)
  265. if log.IsTrace() {
  266. unitTypeStrings := make([]string, len(repo.Units))
  267. for i, unit := range repo.Units {
  268. unitTypeStrings[i] = unit.Type.String()
  269. }
  270. log.Trace("repo.Units, ID=%d, Types: [%s]", repo.ID, strings.Join(unitTypeStrings, ", "))
  271. }
  272. return err
  273. }
  274. // UnitEnabled if this repository has the given unit enabled
  275. func (repo *Repository) UnitEnabled(ctx context.Context, tp unit.Type) bool {
  276. if err := repo.LoadUnits(ctx); err != nil {
  277. log.Warn("Error loading repository (ID: %d) units: %s", repo.ID, err.Error())
  278. }
  279. for _, unit := range repo.Units {
  280. if unit.Type == tp {
  281. return true
  282. }
  283. }
  284. return false
  285. }
  286. // MustGetUnit always returns a RepoUnit object
  287. func (repo *Repository) MustGetUnit(ctx context.Context, tp unit.Type) *RepoUnit {
  288. ru, err := repo.GetUnit(ctx, tp)
  289. if err == nil {
  290. return ru
  291. }
  292. if tp == unit.TypeExternalWiki {
  293. return &RepoUnit{
  294. Type: tp,
  295. Config: new(ExternalWikiConfig),
  296. }
  297. } else if tp == unit.TypeExternalTracker {
  298. return &RepoUnit{
  299. Type: tp,
  300. Config: new(ExternalTrackerConfig),
  301. }
  302. } else if tp == unit.TypePullRequests {
  303. return &RepoUnit{
  304. Type: tp,
  305. Config: new(PullRequestsConfig),
  306. }
  307. } else if tp == unit.TypeIssues {
  308. return &RepoUnit{
  309. Type: tp,
  310. Config: new(IssuesConfig),
  311. }
  312. }
  313. return &RepoUnit{
  314. Type: tp,
  315. Config: new(UnitConfig),
  316. }
  317. }
  318. // GetUnit returns a RepoUnit object
  319. func (repo *Repository) GetUnit(ctx context.Context, tp unit.Type) (*RepoUnit, error) {
  320. if err := repo.LoadUnits(ctx); err != nil {
  321. return nil, err
  322. }
  323. for _, unit := range repo.Units {
  324. if unit.Type == tp {
  325. return unit, nil
  326. }
  327. }
  328. return nil, ErrUnitTypeNotExist{tp}
  329. }
  330. // GetOwner returns the repository owner
  331. func (repo *Repository) GetOwner(ctx context.Context) (err error) {
  332. if repo.Owner != nil {
  333. return nil
  334. }
  335. repo.Owner, err = user_model.GetUserByID(ctx, repo.OwnerID)
  336. return err
  337. }
  338. // MustOwner always returns a valid *user_model.User object to avoid
  339. // conceptually impossible error handling.
  340. // It creates a fake object that contains error details
  341. // when error occurs.
  342. func (repo *Repository) MustOwner(ctx context.Context) *user_model.User {
  343. if err := repo.GetOwner(ctx); err != nil {
  344. return &user_model.User{
  345. Name: "error",
  346. FullName: err.Error(),
  347. }
  348. }
  349. return repo.Owner
  350. }
  351. // ComposeMetas composes a map of metas for properly rendering issue links and external issue trackers.
  352. func (repo *Repository) ComposeMetas() map[string]string {
  353. if len(repo.RenderingMetas) == 0 {
  354. metas := map[string]string{
  355. "user": repo.OwnerName,
  356. "repo": repo.Name,
  357. "repoPath": repo.RepoPath(),
  358. "mode": "comment",
  359. }
  360. unit, err := repo.GetUnit(db.DefaultContext, unit.TypeExternalTracker)
  361. if err == nil {
  362. metas["format"] = unit.ExternalTrackerConfig().ExternalTrackerFormat
  363. switch unit.ExternalTrackerConfig().ExternalTrackerStyle {
  364. case markup.IssueNameStyleAlphanumeric:
  365. metas["style"] = markup.IssueNameStyleAlphanumeric
  366. case markup.IssueNameStyleRegexp:
  367. metas["style"] = markup.IssueNameStyleRegexp
  368. metas["regexp"] = unit.ExternalTrackerConfig().ExternalTrackerRegexpPattern
  369. default:
  370. metas["style"] = markup.IssueNameStyleNumeric
  371. }
  372. }
  373. repo.MustOwner(db.DefaultContext)
  374. if repo.Owner.IsOrganization() {
  375. teams := make([]string, 0, 5)
  376. _ = db.GetEngine(db.DefaultContext).Table("team_repo").
  377. Join("INNER", "team", "team.id = team_repo.team_id").
  378. Where("team_repo.repo_id = ?", repo.ID).
  379. Select("team.lower_name").
  380. OrderBy("team.lower_name").
  381. Find(&teams)
  382. metas["teams"] = "," + strings.Join(teams, ",") + ","
  383. metas["org"] = strings.ToLower(repo.OwnerName)
  384. }
  385. repo.RenderingMetas = metas
  386. }
  387. return repo.RenderingMetas
  388. }
  389. // ComposeDocumentMetas composes a map of metas for properly rendering documents
  390. func (repo *Repository) ComposeDocumentMetas() map[string]string {
  391. if len(repo.DocumentRenderingMetas) == 0 {
  392. metas := map[string]string{}
  393. for k, v := range repo.ComposeMetas() {
  394. metas[k] = v
  395. }
  396. metas["mode"] = "document"
  397. repo.DocumentRenderingMetas = metas
  398. }
  399. return repo.DocumentRenderingMetas
  400. }
  401. // GetBaseRepo populates repo.BaseRepo for a fork repository and
  402. // returns an error on failure (NOTE: no error is returned for
  403. // non-fork repositories, and BaseRepo will be left untouched)
  404. func (repo *Repository) GetBaseRepo(ctx context.Context) (err error) {
  405. if !repo.IsFork {
  406. return nil
  407. }
  408. repo.BaseRepo, err = GetRepositoryByID(ctx, repo.ForkID)
  409. return err
  410. }
  411. // IsGenerated returns whether _this_ repository was generated from a template
  412. func (repo *Repository) IsGenerated() bool {
  413. return repo.TemplateID != 0
  414. }
  415. // RepoPath returns repository path by given user and repository name.
  416. func RepoPath(userName, repoName string) string { //revive:disable-line:exported
  417. return filepath.Join(user_model.UserPath(userName), strings.ToLower(repoName)+".git")
  418. }
  419. // RepoPath returns the repository path
  420. func (repo *Repository) RepoPath() string {
  421. return RepoPath(repo.OwnerName, repo.Name)
  422. }
  423. // Link returns the repository link
  424. func (repo *Repository) Link() string {
  425. return setting.AppSubURL + "/" + url.PathEscape(repo.OwnerName) + "/" + url.PathEscape(repo.Name)
  426. }
  427. // ComposeCompareURL returns the repository comparison URL
  428. func (repo *Repository) ComposeCompareURL(oldCommitID, newCommitID string) string {
  429. return fmt.Sprintf("%s/%s/compare/%s...%s", url.PathEscape(repo.OwnerName), url.PathEscape(repo.Name), util.PathEscapeSegments(oldCommitID), util.PathEscapeSegments(newCommitID))
  430. }
  431. // IsOwnedBy returns true when user owns this repository
  432. func (repo *Repository) IsOwnedBy(userID int64) bool {
  433. return repo.OwnerID == userID
  434. }
  435. // CanCreateBranch returns true if repository meets the requirements for creating new branches.
  436. func (repo *Repository) CanCreateBranch() bool {
  437. return !repo.IsMirror
  438. }
  439. // CanEnablePulls returns true if repository meets the requirements of accepting pulls.
  440. func (repo *Repository) CanEnablePulls() bool {
  441. return !repo.IsMirror && !repo.IsEmpty
  442. }
  443. // AllowsPulls returns true if repository meets the requirements of accepting pulls and has them enabled.
  444. func (repo *Repository) AllowsPulls() bool {
  445. return repo.CanEnablePulls() && repo.UnitEnabled(db.DefaultContext, unit.TypePullRequests)
  446. }
  447. // CanEnableEditor returns true if repository meets the requirements of web editor.
  448. func (repo *Repository) CanEnableEditor() bool {
  449. return !repo.IsMirror
  450. }
  451. // DescriptionHTML does special handles to description and return HTML string.
  452. func (repo *Repository) DescriptionHTML(ctx context.Context) template.HTML {
  453. desc, err := markup.RenderDescriptionHTML(&markup.RenderContext{
  454. Ctx: ctx,
  455. URLPrefix: repo.HTMLURL(),
  456. // Don't use Metas to speedup requests
  457. }, repo.Description)
  458. if err != nil {
  459. log.Error("Failed to render description for %s (ID: %d): %v", repo.Name, repo.ID, err)
  460. return template.HTML(markup.Sanitize(repo.Description))
  461. }
  462. return template.HTML(markup.Sanitize(desc))
  463. }
  464. // CloneLink represents different types of clone URLs of repository.
  465. type CloneLink struct {
  466. SSH string
  467. HTTPS string
  468. }
  469. // ComposeHTTPSCloneURL returns HTTPS clone URL based on given owner and repository name.
  470. func ComposeHTTPSCloneURL(owner, repo string) string {
  471. return fmt.Sprintf("%s%s/%s.git", setting.AppURL, url.PathEscape(owner), url.PathEscape(repo))
  472. }
  473. func (repo *Repository) cloneLink(isWiki bool) *CloneLink {
  474. repoName := repo.Name
  475. if isWiki {
  476. repoName += ".wiki"
  477. }
  478. sshUser := setting.SSH.User
  479. cl := new(CloneLink)
  480. // if we have a ipv6 literal we need to put brackets around it
  481. // for the git cloning to work.
  482. sshDomain := setting.SSH.Domain
  483. ip := net.ParseIP(setting.SSH.Domain)
  484. if ip != nil && ip.To4() == nil {
  485. sshDomain = "[" + setting.SSH.Domain + "]"
  486. }
  487. if setting.SSH.Port != 22 {
  488. cl.SSH = fmt.Sprintf("ssh://%s@%s/%s/%s.git", sshUser, net.JoinHostPort(setting.SSH.Domain, strconv.Itoa(setting.SSH.Port)), url.PathEscape(repo.OwnerName), url.PathEscape(repoName))
  489. } else if setting.Repository.UseCompatSSHURI {
  490. cl.SSH = fmt.Sprintf("ssh://%s@%s/%s/%s.git", sshUser, sshDomain, url.PathEscape(repo.OwnerName), url.PathEscape(repoName))
  491. } else {
  492. cl.SSH = fmt.Sprintf("%s@%s:%s/%s.git", sshUser, sshDomain, url.PathEscape(repo.OwnerName), url.PathEscape(repoName))
  493. }
  494. cl.HTTPS = ComposeHTTPSCloneURL(repo.OwnerName, repoName)
  495. return cl
  496. }
  497. // CloneLink returns clone URLs of repository.
  498. func (repo *Repository) CloneLink() (cl *CloneLink) {
  499. return repo.cloneLink(false)
  500. }
  501. // GetOriginalURLHostname returns the hostname of a URL or the URL
  502. func (repo *Repository) GetOriginalURLHostname() string {
  503. u, err := url.Parse(repo.OriginalURL)
  504. if err != nil {
  505. return repo.OriginalURL
  506. }
  507. return u.Host
  508. }
  509. // GetTrustModel will get the TrustModel for the repo or the default trust model
  510. func (repo *Repository) GetTrustModel() TrustModelType {
  511. trustModel := repo.TrustModel
  512. if trustModel == DefaultTrustModel {
  513. trustModel = ToTrustModel(setting.Repository.Signing.DefaultTrustModel)
  514. if trustModel == DefaultTrustModel {
  515. return CollaboratorTrustModel
  516. }
  517. }
  518. return trustModel
  519. }
  520. // __________ .__ __
  521. // \______ \ ____ ______ ____ _____|__|/ |_ ___________ ___.__.
  522. // | _// __ \\____ \ / _ \/ ___/ \ __\/ _ \_ __ < | |
  523. // | | \ ___/| |_> > <_> )___ \| || | ( <_> ) | \/\___ |
  524. // |____|_ /\___ > __/ \____/____ >__||__| \____/|__| / ____|
  525. // \/ \/|__| \/ \/
  526. // ErrRepoNotExist represents a "RepoNotExist" kind of error.
  527. type ErrRepoNotExist struct {
  528. ID int64
  529. UID int64
  530. OwnerName string
  531. Name string
  532. }
  533. // IsErrRepoNotExist checks if an error is a ErrRepoNotExist.
  534. func IsErrRepoNotExist(err error) bool {
  535. _, ok := err.(ErrRepoNotExist)
  536. return ok
  537. }
  538. func (err ErrRepoNotExist) Error() string {
  539. return fmt.Sprintf("repository does not exist [id: %d, uid: %d, owner_name: %s, name: %s]",
  540. err.ID, err.UID, err.OwnerName, err.Name)
  541. }
  542. // Unwrap unwraps this error as a ErrNotExist error
  543. func (err ErrRepoNotExist) Unwrap() error {
  544. return util.ErrNotExist
  545. }
  546. // GetRepositoryByOwnerAndName returns the repository by given owner name and repo name
  547. func GetRepositoryByOwnerAndName(ctx context.Context, ownerName, repoName string) (*Repository, error) {
  548. var repo Repository
  549. has, err := db.GetEngine(ctx).Table("repository").Select("repository.*").
  550. Join("INNER", "`user`", "`user`.id = repository.owner_id").
  551. Where("repository.lower_name = ?", strings.ToLower(repoName)).
  552. And("`user`.lower_name = ?", strings.ToLower(ownerName)).
  553. Get(&repo)
  554. if err != nil {
  555. return nil, err
  556. } else if !has {
  557. return nil, ErrRepoNotExist{0, 0, ownerName, repoName}
  558. }
  559. return &repo, nil
  560. }
  561. // GetRepositoryByName returns the repository by given name under user if exists.
  562. func GetRepositoryByName(ownerID int64, name string) (*Repository, error) {
  563. repo := &Repository{
  564. OwnerID: ownerID,
  565. LowerName: strings.ToLower(name),
  566. }
  567. has, err := db.GetEngine(db.DefaultContext).Get(repo)
  568. if err != nil {
  569. return nil, err
  570. } else if !has {
  571. return nil, ErrRepoNotExist{0, ownerID, "", name}
  572. }
  573. return repo, err
  574. }
  575. // GetRepositoryByID returns the repository by given id if exists.
  576. func GetRepositoryByID(ctx context.Context, id int64) (*Repository, error) {
  577. repo := new(Repository)
  578. has, err := db.GetEngine(ctx).ID(id).Get(repo)
  579. if err != nil {
  580. return nil, err
  581. } else if !has {
  582. return nil, ErrRepoNotExist{id, 0, "", ""}
  583. }
  584. return repo, nil
  585. }
  586. // GetRepositoriesMapByIDs returns the repositories by given id slice.
  587. func GetRepositoriesMapByIDs(ids []int64) (map[int64]*Repository, error) {
  588. repos := make(map[int64]*Repository, len(ids))
  589. return repos, db.GetEngine(db.DefaultContext).In("id", ids).Find(&repos)
  590. }
  591. // IsRepositoryExist returns true if the repository with given name under user has already existed.
  592. func IsRepositoryExist(ctx context.Context, u *user_model.User, repoName string) (bool, error) {
  593. has, err := db.GetEngine(ctx).Get(&Repository{
  594. OwnerID: u.ID,
  595. LowerName: strings.ToLower(repoName),
  596. })
  597. if err != nil {
  598. return false, err
  599. }
  600. isDir, err := util.IsDir(RepoPath(u.Name, repoName))
  601. return has && isDir, err
  602. }
  603. // GetTemplateRepo populates repo.TemplateRepo for a generated repository and
  604. // returns an error on failure (NOTE: no error is returned for
  605. // non-generated repositories, and TemplateRepo will be left untouched)
  606. func GetTemplateRepo(ctx context.Context, repo *Repository) (*Repository, error) {
  607. if !repo.IsGenerated() {
  608. return nil, nil
  609. }
  610. return GetRepositoryByID(ctx, repo.TemplateID)
  611. }
  612. // TemplateRepo returns the repository, which is template of this repository
  613. func (repo *Repository) TemplateRepo() *Repository {
  614. repo, err := GetTemplateRepo(db.DefaultContext, repo)
  615. if err != nil {
  616. log.Error("TemplateRepo: %v", err)
  617. return nil
  618. }
  619. return repo
  620. }
  621. type CountRepositoryOptions struct {
  622. OwnerID int64
  623. Private util.OptionalBool
  624. }
  625. // CountRepositories returns number of repositories.
  626. // Argument private only takes effect when it is false,
  627. // set it true to count all repositories.
  628. func CountRepositories(ctx context.Context, opts CountRepositoryOptions) (int64, error) {
  629. sess := db.GetEngine(ctx).Where("id > 0")
  630. if opts.OwnerID > 0 {
  631. sess.And("owner_id = ?", opts.OwnerID)
  632. }
  633. if !opts.Private.IsNone() {
  634. sess.And("is_private=?", opts.Private.IsTrue())
  635. }
  636. count, err := sess.Count(new(Repository))
  637. if err != nil {
  638. return 0, fmt.Errorf("countRepositories: %w", err)
  639. }
  640. return count, nil
  641. }
  642. // UpdateRepoIssueNumbers updates one of a repositories amount of (open|closed) (issues|PRs) with the current count
  643. func UpdateRepoIssueNumbers(ctx context.Context, repoID int64, isPull, isClosed bool) error {
  644. field := "num_"
  645. if isClosed {
  646. field += "closed_"
  647. }
  648. if isPull {
  649. field += "pulls"
  650. } else {
  651. field += "issues"
  652. }
  653. subQuery := builder.Select("count(*)").
  654. From("issue").Where(builder.Eq{
  655. "repo_id": repoID,
  656. "is_pull": isPull,
  657. }.And(builder.If(isClosed, builder.Eq{"is_closed": isClosed})))
  658. // builder.Update(cond) will generate SQL like UPDATE ... SET cond
  659. query := builder.Update(builder.Eq{field: subQuery}).
  660. From("repository").
  661. Where(builder.Eq{"id": repoID})
  662. _, err := db.Exec(ctx, query)
  663. return err
  664. }
  665. // CountNullArchivedRepository counts the number of repositories with is_archived is null
  666. func CountNullArchivedRepository(ctx context.Context) (int64, error) {
  667. return db.GetEngine(ctx).Where(builder.IsNull{"is_archived"}).Count(new(Repository))
  668. }
  669. // FixNullArchivedRepository sets is_archived to false where it is null
  670. func FixNullArchivedRepository(ctx context.Context) (int64, error) {
  671. return db.GetEngine(ctx).Where(builder.IsNull{"is_archived"}).Cols("is_archived").NoAutoTime().Update(&Repository{
  672. IsArchived: false,
  673. })
  674. }