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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779
  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. // FIXME: use models migration to solve all at once.
  200. if len(repo.DefaultBranch) == 0 {
  201. repo.DefaultBranch = setting.Repository.DefaultBranch
  202. }
  203. repo.NumOpenIssues = repo.NumIssues - repo.NumClosedIssues
  204. repo.NumOpenPulls = repo.NumPulls - repo.NumClosedPulls
  205. repo.NumOpenMilestones = repo.NumMilestones - repo.NumClosedMilestones
  206. repo.NumOpenProjects = repo.NumProjects - repo.NumClosedProjects
  207. repo.NumOpenActionRuns = repo.NumActionRuns - repo.NumClosedActionRuns
  208. }
  209. // LoadAttributes loads attributes of the repository.
  210. func (repo *Repository) LoadAttributes(ctx context.Context) error {
  211. // Load owner
  212. if err := repo.GetOwner(ctx); err != nil {
  213. return fmt.Errorf("load owner: %w", err)
  214. }
  215. // Load primary language
  216. stats := make(LanguageStatList, 0, 1)
  217. if err := db.GetEngine(ctx).
  218. Where("`repo_id` = ? AND `is_primary` = ? AND `language` != ?", repo.ID, true, "other").
  219. Find(&stats); err != nil {
  220. return fmt.Errorf("find primary languages: %w", err)
  221. }
  222. stats.LoadAttributes()
  223. for _, st := range stats {
  224. if st.RepoID == repo.ID {
  225. repo.PrimaryLanguage = st
  226. break
  227. }
  228. }
  229. return nil
  230. }
  231. // FullName returns the repository full name
  232. func (repo *Repository) FullName() string {
  233. return repo.OwnerName + "/" + repo.Name
  234. }
  235. // HTMLURL returns the repository HTML URL
  236. func (repo *Repository) HTMLURL() string {
  237. return setting.AppURL + url.PathEscape(repo.OwnerName) + "/" + url.PathEscape(repo.Name)
  238. }
  239. // CommitLink make link to by commit full ID
  240. // note: won't check whether it's an right id
  241. func (repo *Repository) CommitLink(commitID string) (result string) {
  242. if commitID == "" || commitID == "0000000000000000000000000000000000000000" {
  243. result = ""
  244. } else {
  245. result = repo.HTMLURL() + "/commit/" + url.PathEscape(commitID)
  246. }
  247. return result
  248. }
  249. // APIURL returns the repository API URL
  250. func (repo *Repository) APIURL() string {
  251. return setting.AppURL + "api/v1/repos/" + url.PathEscape(repo.OwnerName) + "/" + url.PathEscape(repo.Name)
  252. }
  253. // GetCommitsCountCacheKey returns cache key used for commits count caching.
  254. func (repo *Repository) GetCommitsCountCacheKey(contextName string, isRef bool) string {
  255. var prefix string
  256. if isRef {
  257. prefix = "ref"
  258. } else {
  259. prefix = "commit"
  260. }
  261. return fmt.Sprintf("commits-count-%d-%s-%s", repo.ID, prefix, contextName)
  262. }
  263. // LoadUnits loads repo units into repo.Units
  264. func (repo *Repository) LoadUnits(ctx context.Context) (err error) {
  265. if repo.Units != nil {
  266. return nil
  267. }
  268. repo.Units, err = getUnitsByRepoID(ctx, repo.ID)
  269. if log.IsTrace() {
  270. unitTypeStrings := make([]string, len(repo.Units))
  271. for i, unit := range repo.Units {
  272. unitTypeStrings[i] = unit.Type.String()
  273. }
  274. log.Trace("repo.Units, ID=%d, Types: [%s]", repo.ID, strings.Join(unitTypeStrings, ", "))
  275. }
  276. return err
  277. }
  278. // UnitEnabled if this repository has the given unit enabled
  279. func (repo *Repository) UnitEnabled(ctx context.Context, tp unit.Type) bool {
  280. if err := repo.LoadUnits(ctx); err != nil {
  281. log.Warn("Error loading repository (ID: %d) units: %s", repo.ID, err.Error())
  282. }
  283. for _, unit := range repo.Units {
  284. if unit.Type == tp {
  285. return true
  286. }
  287. }
  288. return false
  289. }
  290. // MustGetUnit always returns a RepoUnit object
  291. func (repo *Repository) MustGetUnit(ctx context.Context, tp unit.Type) *RepoUnit {
  292. ru, err := repo.GetUnit(ctx, tp)
  293. if err == nil {
  294. return ru
  295. }
  296. if tp == unit.TypeExternalWiki {
  297. return &RepoUnit{
  298. Type: tp,
  299. Config: new(ExternalWikiConfig),
  300. }
  301. } else if tp == unit.TypeExternalTracker {
  302. return &RepoUnit{
  303. Type: tp,
  304. Config: new(ExternalTrackerConfig),
  305. }
  306. } else if tp == unit.TypePullRequests {
  307. return &RepoUnit{
  308. Type: tp,
  309. Config: new(PullRequestsConfig),
  310. }
  311. } else if tp == unit.TypeIssues {
  312. return &RepoUnit{
  313. Type: tp,
  314. Config: new(IssuesConfig),
  315. }
  316. }
  317. return &RepoUnit{
  318. Type: tp,
  319. Config: new(UnitConfig),
  320. }
  321. }
  322. // GetUnit returns a RepoUnit object
  323. func (repo *Repository) GetUnit(ctx context.Context, tp unit.Type) (*RepoUnit, error) {
  324. if err := repo.LoadUnits(ctx); err != nil {
  325. return nil, err
  326. }
  327. for _, unit := range repo.Units {
  328. if unit.Type == tp {
  329. return unit, nil
  330. }
  331. }
  332. return nil, ErrUnitTypeNotExist{tp}
  333. }
  334. // GetOwner returns the repository owner
  335. func (repo *Repository) GetOwner(ctx context.Context) (err error) {
  336. if repo.Owner != nil {
  337. return nil
  338. }
  339. repo.Owner, err = user_model.GetUserByID(ctx, repo.OwnerID)
  340. return err
  341. }
  342. // MustOwner always returns a valid *user_model.User object to avoid
  343. // conceptually impossible error handling.
  344. // It creates a fake object that contains error details
  345. // when error occurs.
  346. func (repo *Repository) MustOwner(ctx context.Context) *user_model.User {
  347. if err := repo.GetOwner(ctx); err != nil {
  348. return &user_model.User{
  349. Name: "error",
  350. FullName: err.Error(),
  351. }
  352. }
  353. return repo.Owner
  354. }
  355. // ComposeMetas composes a map of metas for properly rendering issue links and external issue trackers.
  356. func (repo *Repository) ComposeMetas() map[string]string {
  357. if len(repo.RenderingMetas) == 0 {
  358. metas := map[string]string{
  359. "user": repo.OwnerName,
  360. "repo": repo.Name,
  361. "repoPath": repo.RepoPath(),
  362. "mode": "comment",
  363. }
  364. unit, err := repo.GetUnit(db.DefaultContext, unit.TypeExternalTracker)
  365. if err == nil {
  366. metas["format"] = unit.ExternalTrackerConfig().ExternalTrackerFormat
  367. switch unit.ExternalTrackerConfig().ExternalTrackerStyle {
  368. case markup.IssueNameStyleAlphanumeric:
  369. metas["style"] = markup.IssueNameStyleAlphanumeric
  370. case markup.IssueNameStyleRegexp:
  371. metas["style"] = markup.IssueNameStyleRegexp
  372. metas["regexp"] = unit.ExternalTrackerConfig().ExternalTrackerRegexpPattern
  373. default:
  374. metas["style"] = markup.IssueNameStyleNumeric
  375. }
  376. }
  377. repo.MustOwner(db.DefaultContext)
  378. if repo.Owner.IsOrganization() {
  379. teams := make([]string, 0, 5)
  380. _ = db.GetEngine(db.DefaultContext).Table("team_repo").
  381. Join("INNER", "team", "team.id = team_repo.team_id").
  382. Where("team_repo.repo_id = ?", repo.ID).
  383. Select("team.lower_name").
  384. OrderBy("team.lower_name").
  385. Find(&teams)
  386. metas["teams"] = "," + strings.Join(teams, ",") + ","
  387. metas["org"] = strings.ToLower(repo.OwnerName)
  388. }
  389. repo.RenderingMetas = metas
  390. }
  391. return repo.RenderingMetas
  392. }
  393. // ComposeDocumentMetas composes a map of metas for properly rendering documents
  394. func (repo *Repository) ComposeDocumentMetas() map[string]string {
  395. if len(repo.DocumentRenderingMetas) == 0 {
  396. metas := map[string]string{}
  397. for k, v := range repo.ComposeMetas() {
  398. metas[k] = v
  399. }
  400. metas["mode"] = "document"
  401. repo.DocumentRenderingMetas = metas
  402. }
  403. return repo.DocumentRenderingMetas
  404. }
  405. // GetBaseRepo populates repo.BaseRepo for a fork repository and
  406. // returns an error on failure (NOTE: no error is returned for
  407. // non-fork repositories, and BaseRepo will be left untouched)
  408. func (repo *Repository) GetBaseRepo(ctx context.Context) (err error) {
  409. if !repo.IsFork {
  410. return nil
  411. }
  412. repo.BaseRepo, err = GetRepositoryByID(ctx, repo.ForkID)
  413. return err
  414. }
  415. // IsGenerated returns whether _this_ repository was generated from a template
  416. func (repo *Repository) IsGenerated() bool {
  417. return repo.TemplateID != 0
  418. }
  419. // RepoPath returns repository path by given user and repository name.
  420. func RepoPath(userName, repoName string) string { //revive:disable-line:exported
  421. return filepath.Join(user_model.UserPath(userName), strings.ToLower(repoName)+".git")
  422. }
  423. // RepoPath returns the repository path
  424. func (repo *Repository) RepoPath() string {
  425. return RepoPath(repo.OwnerName, repo.Name)
  426. }
  427. // Link returns the repository link
  428. func (repo *Repository) Link() string {
  429. return setting.AppSubURL + "/" + url.PathEscape(repo.OwnerName) + "/" + url.PathEscape(repo.Name)
  430. }
  431. // ComposeCompareURL returns the repository comparison URL
  432. func (repo *Repository) ComposeCompareURL(oldCommitID, newCommitID string) string {
  433. return fmt.Sprintf("%s/%s/compare/%s...%s", url.PathEscape(repo.OwnerName), url.PathEscape(repo.Name), util.PathEscapeSegments(oldCommitID), util.PathEscapeSegments(newCommitID))
  434. }
  435. // IsOwnedBy returns true when user owns this repository
  436. func (repo *Repository) IsOwnedBy(userID int64) bool {
  437. return repo.OwnerID == userID
  438. }
  439. // CanCreateBranch returns true if repository meets the requirements for creating new branches.
  440. func (repo *Repository) CanCreateBranch() bool {
  441. return !repo.IsMirror
  442. }
  443. // CanEnablePulls returns true if repository meets the requirements of accepting pulls.
  444. func (repo *Repository) CanEnablePulls() bool {
  445. return !repo.IsMirror && !repo.IsEmpty
  446. }
  447. // AllowsPulls returns true if repository meets the requirements of accepting pulls and has them enabled.
  448. func (repo *Repository) AllowsPulls() bool {
  449. return repo.CanEnablePulls() && repo.UnitEnabled(db.DefaultContext, unit.TypePullRequests)
  450. }
  451. // CanEnableEditor returns true if repository meets the requirements of web editor.
  452. func (repo *Repository) CanEnableEditor() bool {
  453. return !repo.IsMirror
  454. }
  455. // DescriptionHTML does special handles to description and return HTML string.
  456. func (repo *Repository) DescriptionHTML(ctx context.Context) template.HTML {
  457. desc, err := markup.RenderDescriptionHTML(&markup.RenderContext{
  458. Ctx: ctx,
  459. URLPrefix: repo.HTMLURL(),
  460. // Don't use Metas to speedup requests
  461. }, repo.Description)
  462. if err != nil {
  463. log.Error("Failed to render description for %s (ID: %d): %v", repo.Name, repo.ID, err)
  464. return template.HTML(markup.Sanitize(repo.Description))
  465. }
  466. return template.HTML(markup.Sanitize(desc))
  467. }
  468. // CloneLink represents different types of clone URLs of repository.
  469. type CloneLink struct {
  470. SSH string
  471. HTTPS string
  472. }
  473. // ComposeHTTPSCloneURL returns HTTPS clone URL based on given owner and repository name.
  474. func ComposeHTTPSCloneURL(owner, repo string) string {
  475. return fmt.Sprintf("%s%s/%s.git", setting.AppURL, url.PathEscape(owner), url.PathEscape(repo))
  476. }
  477. func (repo *Repository) cloneLink(isWiki bool) *CloneLink {
  478. repoName := repo.Name
  479. if isWiki {
  480. repoName += ".wiki"
  481. }
  482. sshUser := setting.SSH.User
  483. cl := new(CloneLink)
  484. // if we have a ipv6 literal we need to put brackets around it
  485. // for the git cloning to work.
  486. sshDomain := setting.SSH.Domain
  487. ip := net.ParseIP(setting.SSH.Domain)
  488. if ip != nil && ip.To4() == nil {
  489. sshDomain = "[" + setting.SSH.Domain + "]"
  490. }
  491. if setting.SSH.Port != 22 {
  492. 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))
  493. } else if setting.Repository.UseCompatSSHURI {
  494. cl.SSH = fmt.Sprintf("ssh://%s@%s/%s/%s.git", sshUser, sshDomain, url.PathEscape(repo.OwnerName), url.PathEscape(repoName))
  495. } else {
  496. cl.SSH = fmt.Sprintf("%s@%s:%s/%s.git", sshUser, sshDomain, url.PathEscape(repo.OwnerName), url.PathEscape(repoName))
  497. }
  498. cl.HTTPS = ComposeHTTPSCloneURL(repo.OwnerName, repoName)
  499. return cl
  500. }
  501. // CloneLink returns clone URLs of repository.
  502. func (repo *Repository) CloneLink() (cl *CloneLink) {
  503. return repo.cloneLink(false)
  504. }
  505. // GetOriginalURLHostname returns the hostname of a URL or the URL
  506. func (repo *Repository) GetOriginalURLHostname() string {
  507. u, err := url.Parse(repo.OriginalURL)
  508. if err != nil {
  509. return repo.OriginalURL
  510. }
  511. return u.Host
  512. }
  513. // GetTrustModel will get the TrustModel for the repo or the default trust model
  514. func (repo *Repository) GetTrustModel() TrustModelType {
  515. trustModel := repo.TrustModel
  516. if trustModel == DefaultTrustModel {
  517. trustModel = ToTrustModel(setting.Repository.Signing.DefaultTrustModel)
  518. if trustModel == DefaultTrustModel {
  519. return CollaboratorTrustModel
  520. }
  521. }
  522. return trustModel
  523. }
  524. // __________ .__ __
  525. // \______ \ ____ ______ ____ _____|__|/ |_ ___________ ___.__.
  526. // | _// __ \\____ \ / _ \/ ___/ \ __\/ _ \_ __ < | |
  527. // | | \ ___/| |_> > <_> )___ \| || | ( <_> ) | \/\___ |
  528. // |____|_ /\___ > __/ \____/____ >__||__| \____/|__| / ____|
  529. // \/ \/|__| \/ \/
  530. // ErrRepoNotExist represents a "RepoNotExist" kind of error.
  531. type ErrRepoNotExist struct {
  532. ID int64
  533. UID int64
  534. OwnerName string
  535. Name string
  536. }
  537. // IsErrRepoNotExist checks if an error is a ErrRepoNotExist.
  538. func IsErrRepoNotExist(err error) bool {
  539. _, ok := err.(ErrRepoNotExist)
  540. return ok
  541. }
  542. func (err ErrRepoNotExist) Error() string {
  543. return fmt.Sprintf("repository does not exist [id: %d, uid: %d, owner_name: %s, name: %s]",
  544. err.ID, err.UID, err.OwnerName, err.Name)
  545. }
  546. // Unwrap unwraps this error as a ErrNotExist error
  547. func (err ErrRepoNotExist) Unwrap() error {
  548. return util.ErrNotExist
  549. }
  550. // GetRepositoryByOwnerAndName returns the repository by given owner name and repo name
  551. func GetRepositoryByOwnerAndName(ctx context.Context, ownerName, repoName string) (*Repository, error) {
  552. var repo Repository
  553. has, err := db.GetEngine(ctx).Table("repository").Select("repository.*").
  554. Join("INNER", "`user`", "`user`.id = repository.owner_id").
  555. Where("repository.lower_name = ?", strings.ToLower(repoName)).
  556. And("`user`.lower_name = ?", strings.ToLower(ownerName)).
  557. Get(&repo)
  558. if err != nil {
  559. return nil, err
  560. } else if !has {
  561. return nil, ErrRepoNotExist{0, 0, ownerName, repoName}
  562. }
  563. return &repo, nil
  564. }
  565. // GetRepositoryByName returns the repository by given name under user if exists.
  566. func GetRepositoryByName(ownerID int64, name string) (*Repository, error) {
  567. repo := &Repository{
  568. OwnerID: ownerID,
  569. LowerName: strings.ToLower(name),
  570. }
  571. has, err := db.GetEngine(db.DefaultContext).Get(repo)
  572. if err != nil {
  573. return nil, err
  574. } else if !has {
  575. return nil, ErrRepoNotExist{0, ownerID, "", name}
  576. }
  577. return repo, err
  578. }
  579. // GetRepositoryByID returns the repository by given id if exists.
  580. func GetRepositoryByID(ctx context.Context, id int64) (*Repository, error) {
  581. repo := new(Repository)
  582. has, err := db.GetEngine(ctx).ID(id).Get(repo)
  583. if err != nil {
  584. return nil, err
  585. } else if !has {
  586. return nil, ErrRepoNotExist{id, 0, "", ""}
  587. }
  588. return repo, nil
  589. }
  590. // GetRepositoriesMapByIDs returns the repositories by given id slice.
  591. func GetRepositoriesMapByIDs(ids []int64) (map[int64]*Repository, error) {
  592. repos := make(map[int64]*Repository, len(ids))
  593. return repos, db.GetEngine(db.DefaultContext).In("id", ids).Find(&repos)
  594. }
  595. // IsRepositoryExist returns true if the repository with given name under user has already existed.
  596. func IsRepositoryExist(ctx context.Context, u *user_model.User, repoName string) (bool, error) {
  597. has, err := db.GetEngine(ctx).Get(&Repository{
  598. OwnerID: u.ID,
  599. LowerName: strings.ToLower(repoName),
  600. })
  601. if err != nil {
  602. return false, err
  603. }
  604. isDir, err := util.IsDir(RepoPath(u.Name, repoName))
  605. return has && isDir, err
  606. }
  607. // GetTemplateRepo populates repo.TemplateRepo for a generated repository and
  608. // returns an error on failure (NOTE: no error is returned for
  609. // non-generated repositories, and TemplateRepo will be left untouched)
  610. func GetTemplateRepo(ctx context.Context, repo *Repository) (*Repository, error) {
  611. if !repo.IsGenerated() {
  612. return nil, nil
  613. }
  614. return GetRepositoryByID(ctx, repo.TemplateID)
  615. }
  616. // TemplateRepo returns the repository, which is template of this repository
  617. func (repo *Repository) TemplateRepo() *Repository {
  618. repo, err := GetTemplateRepo(db.DefaultContext, repo)
  619. if err != nil {
  620. log.Error("TemplateRepo: %v", err)
  621. return nil
  622. }
  623. return repo
  624. }
  625. type CountRepositoryOptions struct {
  626. OwnerID int64
  627. Private util.OptionalBool
  628. }
  629. // CountRepositories returns number of repositories.
  630. // Argument private only takes effect when it is false,
  631. // set it true to count all repositories.
  632. func CountRepositories(ctx context.Context, opts CountRepositoryOptions) (int64, error) {
  633. sess := db.GetEngine(ctx).Where("id > 0")
  634. if opts.OwnerID > 0 {
  635. sess.And("owner_id = ?", opts.OwnerID)
  636. }
  637. if !opts.Private.IsNone() {
  638. sess.And("is_private=?", opts.Private.IsTrue())
  639. }
  640. count, err := sess.Count(new(Repository))
  641. if err != nil {
  642. return 0, fmt.Errorf("countRepositories: %w", err)
  643. }
  644. return count, nil
  645. }
  646. // UpdateRepoIssueNumbers updates one of a repositories amount of (open|closed) (issues|PRs) with the current count
  647. func UpdateRepoIssueNumbers(ctx context.Context, repoID int64, isPull, isClosed bool) error {
  648. field := "num_"
  649. if isClosed {
  650. field += "closed_"
  651. }
  652. if isPull {
  653. field += "pulls"
  654. } else {
  655. field += "issues"
  656. }
  657. subQuery := builder.Select("count(*)").
  658. From("issue").Where(builder.Eq{
  659. "repo_id": repoID,
  660. "is_pull": isPull,
  661. }.And(builder.If(isClosed, builder.Eq{"is_closed": isClosed})))
  662. // builder.Update(cond) will generate SQL like UPDATE ... SET cond
  663. query := builder.Update(builder.Eq{field: subQuery}).
  664. From("repository").
  665. Where(builder.Eq{"id": repoID})
  666. _, err := db.Exec(ctx, query)
  667. return err
  668. }
  669. // CountNullArchivedRepository counts the number of repositories with is_archived is null
  670. func CountNullArchivedRepository(ctx context.Context) (int64, error) {
  671. return db.GetEngine(ctx).Where(builder.IsNull{"is_archived"}).Count(new(Repository))
  672. }
  673. // FixNullArchivedRepository sets is_archived to false where it is null
  674. func FixNullArchivedRepository(ctx context.Context) (int64, error) {
  675. return db.GetEngine(ctx).Where(builder.IsNull{"is_archived"}).Cols("is_archived").NoAutoTime().Update(&Repository{
  676. IsArchived: false,
  677. })
  678. }