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

repo.go 14 kB

11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571
  1. // Copyright 2014 The Gogs Authors. All rights reserved.
  2. // Use of this source code is governed by a MIT-style
  3. // license that can be found in the LICENSE file.
  4. package models
  5. import (
  6. "container/list"
  7. "errors"
  8. "fmt"
  9. "io/ioutil"
  10. "os"
  11. "path"
  12. "path/filepath"
  13. "strings"
  14. "sync"
  15. "time"
  16. "unicode/utf8"
  17. "github.com/Unknwon/cae/zip"
  18. "github.com/Unknwon/com"
  19. "github.com/gogits/git"
  20. "github.com/gogits/gogs/modules/base"
  21. "github.com/gogits/gogs/modules/log"
  22. )
  23. // Repository represents a git repository.
  24. type Repository struct {
  25. Id int64
  26. OwnerId int64 `xorm:"unique(s)"`
  27. ForkId int64
  28. LowerName string `xorm:"unique(s) index not null"`
  29. Name string `xorm:"index not null"`
  30. Description string
  31. Private bool
  32. NumWatchs int
  33. NumStars int
  34. NumForks int
  35. Created time.Time `xorm:"created"`
  36. Updated time.Time `xorm:"updated"`
  37. }
  38. type Star struct {
  39. Id int64
  40. RepoId int64
  41. UserId int64
  42. Created time.Time `xorm:"created"`
  43. }
  44. var (
  45. gitInitLocker = sync.Mutex{}
  46. LanguageIgns, Licenses []string
  47. )
  48. var (
  49. ErrRepoAlreadyExist = errors.New("Repository already exist")
  50. ErrRepoNotExist = errors.New("Repository does not exist")
  51. )
  52. func init() {
  53. LanguageIgns = strings.Split(base.Cfg.MustValue("repository", "LANG_IGNS"), "|")
  54. Licenses = strings.Split(base.Cfg.MustValue("repository", "LICENSES"), "|")
  55. zip.Verbose = false
  56. // Check if server has basic git setting.
  57. stdout, _, err := com.ExecCmd("git", "config", "--get", "user.name")
  58. if err != nil {
  59. fmt.Printf("repo.init(fail to get git user.name): %v", err)
  60. os.Exit(2)
  61. } else if len(stdout) == 0 {
  62. if _, _, err = com.ExecCmd("git", "config", "--global", "user.email", "gogitservice@gmail.com"); err != nil {
  63. fmt.Printf("repo.init(fail to set git user.email): %v", err)
  64. os.Exit(2)
  65. } else if _, _, err = com.ExecCmd("git", "config", "--global", "user.name", "Gogs"); err != nil {
  66. fmt.Printf("repo.init(fail to set git user.name): %v", err)
  67. os.Exit(2)
  68. }
  69. }
  70. }
  71. // IsRepositoryExist returns true if the repository with given name under user has already existed.
  72. func IsRepositoryExist(user *User, repoName string) (bool, error) {
  73. repo := Repository{OwnerId: user.Id}
  74. has, err := orm.Where("lower_name = ?", strings.ToLower(repoName)).Get(&repo)
  75. if err != nil {
  76. return has, err
  77. }
  78. s, err := os.Stat(RepoPath(user.Name, repoName))
  79. if err != nil {
  80. return false, nil // Error simply means does not exist, but we don't want to show up.
  81. }
  82. return s.IsDir(), nil
  83. }
  84. // CreateRepository creates a repository for given user or orgnaziation.
  85. func CreateRepository(user *User, repoName, desc, repoLang, license string, private bool, initReadme bool) (*Repository, error) {
  86. isExist, err := IsRepositoryExist(user, repoName)
  87. if err != nil {
  88. return nil, err
  89. } else if isExist {
  90. return nil, ErrRepoAlreadyExist
  91. }
  92. repo := &Repository{
  93. OwnerId: user.Id,
  94. Name: repoName,
  95. LowerName: strings.ToLower(repoName),
  96. Description: desc,
  97. Private: private,
  98. }
  99. repoPath := RepoPath(user.Name, repoName)
  100. if err = initRepository(repoPath, user, repo, initReadme, repoLang, license); err != nil {
  101. return nil, err
  102. }
  103. session := orm.NewSession()
  104. defer session.Close()
  105. session.Begin()
  106. if _, err = session.Insert(repo); err != nil {
  107. if err2 := os.RemoveAll(repoPath); err2 != nil {
  108. log.Error("repo.CreateRepository(repo): %v", err)
  109. return nil, errors.New(fmt.Sprintf(
  110. "delete repo directory %s/%s failed(1): %v", user.Name, repoName, err2))
  111. }
  112. session.Rollback()
  113. return nil, err
  114. }
  115. access := Access{
  116. UserName: user.Name,
  117. RepoName: repo.Name,
  118. Mode: AU_WRITABLE,
  119. }
  120. if _, err = session.Insert(&access); err != nil {
  121. session.Rollback()
  122. if err2 := os.RemoveAll(repoPath); err2 != nil {
  123. log.Error("repo.CreateRepository(access): %v", err)
  124. return nil, errors.New(fmt.Sprintf(
  125. "delete repo directory %s/%s failed(2): %v", user.Name, repoName, err2))
  126. }
  127. return nil, err
  128. }
  129. rawSql := "UPDATE `user` SET num_repos = num_repos + 1 WHERE id = ?"
  130. if _, err = session.Exec(rawSql, user.Id); err != nil {
  131. session.Rollback()
  132. if err2 := os.RemoveAll(repoPath); err2 != nil {
  133. log.Error("repo.CreateRepository(repo count): %v", err)
  134. return nil, errors.New(fmt.Sprintf(
  135. "delete repo directory %s/%s failed(3): %v", user.Name, repoName, err2))
  136. }
  137. return nil, err
  138. }
  139. if err = session.Commit(); err != nil {
  140. session.Rollback()
  141. if err2 := os.RemoveAll(repoPath); err2 != nil {
  142. log.Error("repo.CreateRepository(commit): %v", err)
  143. return nil, errors.New(fmt.Sprintf(
  144. "delete repo directory %s/%s failed(3): %v", user.Name, repoName, err2))
  145. }
  146. return nil, err
  147. }
  148. return repo, NewRepoAction(user, repo)
  149. }
  150. // extractGitBareZip extracts git-bare.zip to repository path.
  151. func extractGitBareZip(repoPath string) error {
  152. z, err := zip.Open("conf/content/git-bare.zip")
  153. if err != nil {
  154. fmt.Println("shi?")
  155. return err
  156. }
  157. defer z.Close()
  158. return z.ExtractTo(repoPath)
  159. }
  160. // initRepoCommit temporarily changes with work directory.
  161. func initRepoCommit(tmpPath string, sig *git.Signature) error {
  162. gitInitLocker.Lock()
  163. defer gitInitLocker.Unlock()
  164. // Change work directory.
  165. curPath, err := os.Getwd()
  166. if err != nil {
  167. return err
  168. } else if err = os.Chdir(tmpPath); err != nil {
  169. return err
  170. }
  171. defer os.Chdir(curPath)
  172. var stderr string
  173. if _, stderr, err = com.ExecCmd("git", "add", "--all"); err != nil {
  174. return err
  175. }
  176. log.Info("stderr(1): %s", stderr)
  177. if _, stderr, err = com.ExecCmd("git", "commit", fmt.Sprintf("--author='%s <%s>'", sig.Name, sig.Email),
  178. "-m", "Init commit"); err != nil {
  179. return err
  180. }
  181. log.Info("stderr(2): %s", stderr)
  182. if _, stderr, err = com.ExecCmd("git", "push", "origin", "master"); err != nil {
  183. return err
  184. }
  185. log.Info("stderr(3): %s", stderr)
  186. return nil
  187. }
  188. // InitRepository initializes README and .gitignore if needed.
  189. func initRepository(f string, user *User, repo *Repository, initReadme bool, repoLang, license string) error {
  190. repoPath := RepoPath(user.Name, repo.Name)
  191. // Create bare new repository.
  192. if err := extractGitBareZip(repoPath); err != nil {
  193. return err
  194. }
  195. // hook/post-update
  196. pu, err := os.OpenFile(filepath.Join(repoPath, "hooks", "post-update"), os.O_CREATE|os.O_WRONLY, 0777)
  197. if err != nil {
  198. return err
  199. }
  200. defer pu.Close()
  201. // TODO: Windows .bat
  202. if _, err = pu.WriteString(fmt.Sprintf("#!/usr/bin/env bash\n%s update\n", appPath)); err != nil {
  203. return err
  204. }
  205. // Initialize repository according to user's choice.
  206. fileName := map[string]string{}
  207. if initReadme {
  208. fileName["readme"] = "README.md"
  209. }
  210. if repoLang != "" {
  211. fileName["gitign"] = ".gitignore"
  212. }
  213. if license != "" {
  214. fileName["license"] = "LICENSE"
  215. }
  216. // Clone to temprory path and do the init commit.
  217. tmpDir := filepath.Join(os.TempDir(), fmt.Sprintf("%d", time.Now().Nanosecond()))
  218. os.MkdirAll(tmpDir, os.ModePerm)
  219. if _, _, err := com.ExecCmd("git", "clone", repoPath, tmpDir); err != nil {
  220. return err
  221. }
  222. // README
  223. if initReadme {
  224. defaultReadme := repo.Name + "\n" + strings.Repeat("=",
  225. utf8.RuneCountInString(repo.Name)) + "\n\n" + repo.Description
  226. if err := ioutil.WriteFile(filepath.Join(tmpDir, fileName["readme"]),
  227. []byte(defaultReadme), 0644); err != nil {
  228. return err
  229. }
  230. }
  231. // .gitignore
  232. if repoLang != "" {
  233. filePath := "conf/gitignore/" + repoLang
  234. if com.IsFile(filePath) {
  235. if _, err := com.Copy(filePath,
  236. filepath.Join(tmpDir, fileName["gitign"])); err != nil {
  237. return err
  238. }
  239. }
  240. }
  241. // LICENSE
  242. if license != "" {
  243. filePath := "conf/license/" + license
  244. if com.IsFile(filePath) {
  245. if _, err := com.Copy(filePath,
  246. filepath.Join(tmpDir, fileName["license"])); err != nil {
  247. return err
  248. }
  249. }
  250. }
  251. if len(fileName) == 0 {
  252. return nil
  253. }
  254. // Apply changes and commit.
  255. if err := initRepoCommit(tmpDir, user.NewGitSig()); err != nil {
  256. return err
  257. }
  258. return nil
  259. }
  260. // GetRepositoryByName returns the repository by given name under user if exists.
  261. func GetRepositoryByName(user *User, repoName string) (*Repository, error) {
  262. repo := &Repository{
  263. OwnerId: user.Id,
  264. LowerName: strings.ToLower(repoName),
  265. }
  266. has, err := orm.Get(repo)
  267. if err != nil {
  268. return nil, err
  269. } else if !has {
  270. return nil, ErrRepoNotExist
  271. }
  272. return repo, err
  273. }
  274. // GetRepositoryById returns the repository by given id if exists.
  275. func GetRepositoryById(id int64) (repo *Repository, err error) {
  276. has, err := orm.Id(id).Get(repo)
  277. if err != nil {
  278. return nil, err
  279. } else if !has {
  280. return nil, ErrRepoNotExist
  281. }
  282. return repo, err
  283. }
  284. // GetRepositories returns the list of repositories of given user.
  285. func GetRepositories(user *User) ([]Repository, error) {
  286. repos := make([]Repository, 0, 10)
  287. err := orm.Desc("updated").Find(&repos, &Repository{OwnerId: user.Id})
  288. return repos, err
  289. }
  290. func GetRepositoryCount(user *User) (int64, error) {
  291. return orm.Count(&Repository{OwnerId: user.Id})
  292. }
  293. func StarReposiory(user *User, repoName string) error {
  294. return nil
  295. }
  296. func UnStarRepository() {
  297. }
  298. func WatchRepository() {
  299. }
  300. func UnWatchRepository() {
  301. }
  302. func ForkRepository(reposName string, userId int64) {
  303. }
  304. func RepoPath(userName, repoName string) string {
  305. return filepath.Join(UserPath(userName), repoName+".git")
  306. }
  307. // DeleteRepository deletes a repository for a user or orgnaztion.
  308. func DeleteRepository(userId, repoId int64, userName string) (err error) {
  309. repo := &Repository{Id: repoId, OwnerId: userId}
  310. has, err := orm.Get(repo)
  311. if err != nil {
  312. return err
  313. } else if !has {
  314. return ErrRepoNotExist
  315. }
  316. session := orm.NewSession()
  317. if err = session.Begin(); err != nil {
  318. return err
  319. }
  320. if _, err = session.Delete(&Repository{Id: repoId}); err != nil {
  321. session.Rollback()
  322. return err
  323. }
  324. if _, err := session.Delete(&Access{UserName: userName, RepoName: repo.Name}); err != nil {
  325. session.Rollback()
  326. return err
  327. }
  328. rawSql := "UPDATE user SET num_repos = num_repos - 1 WHERE id = ?"
  329. if base.Cfg.MustValue("database", "DB_TYPE") == "postgres" {
  330. rawSql = "UPDATE \"user\" SET num_repos = num_repos - 1 WHERE id = ?"
  331. }
  332. if _, err = session.Exec(rawSql, userId); err != nil {
  333. session.Rollback()
  334. return err
  335. }
  336. if err = session.Commit(); err != nil {
  337. session.Rollback()
  338. return err
  339. }
  340. if err = os.RemoveAll(RepoPath(userName, repo.Name)); err != nil {
  341. // TODO: log and delete manully
  342. log.Error("delete repo %s/%s failed: %v", userName, repo.Name, err)
  343. return err
  344. }
  345. return nil
  346. }
  347. var (
  348. ErrRepoFileNotLoaded = fmt.Errorf("repo file not loaded")
  349. )
  350. // RepoFile represents a file object in git repository.
  351. type RepoFile struct {
  352. *git.TreeEntry
  353. Path string
  354. Size int64
  355. Repo *git.Repository
  356. Commit *git.Commit
  357. }
  358. // LookupBlob returns the content of an object.
  359. func (file *RepoFile) LookupBlob() (*git.Blob, error) {
  360. if file.Repo == nil {
  361. return nil, ErrRepoFileNotLoaded
  362. }
  363. return file.Repo.LookupBlob(file.Id)
  364. }
  365. // GetBranches returns all branches of given repository.
  366. func GetBranches(userName, reposName string) ([]string, error) {
  367. repo, err := git.OpenRepository(RepoPath(userName, reposName))
  368. if err != nil {
  369. return nil, err
  370. }
  371. refs, err := repo.AllReferences()
  372. if err != nil {
  373. return nil, err
  374. }
  375. brs := make([]string, len(refs))
  376. for i, ref := range refs {
  377. brs[i] = ref.Name
  378. }
  379. return brs, nil
  380. }
  381. // GetReposFiles returns a list of file object in given directory of repository.
  382. func GetReposFiles(userName, reposName, branchName, commitId, rpath string) ([]*RepoFile, error) {
  383. repo, err := git.OpenRepository(RepoPath(userName, reposName))
  384. if err != nil {
  385. return nil, err
  386. }
  387. commit, err := GetCommit(userName, reposName, branchName, commitId)
  388. if err != nil {
  389. return nil, err
  390. }
  391. var repodirs []*RepoFile
  392. var repofiles []*RepoFile
  393. commit.Tree.Walk(func(dirname string, entry *git.TreeEntry) int {
  394. if dirname == rpath {
  395. // TODO: size get method shoule be improved
  396. size, err := repo.ObjectSize(entry.Id)
  397. if err != nil {
  398. return 0
  399. }
  400. var cm = commit
  401. for {
  402. if cm.ParentCount() == 0 {
  403. break
  404. } else if cm.ParentCount() == 1 {
  405. pt, _ := repo.SubTree(cm.Parent(0).Tree, dirname)
  406. if pt == nil {
  407. break
  408. }
  409. pEntry := pt.EntryByName(entry.Name)
  410. if pEntry == nil || !pEntry.Id.Equal(entry.Id) {
  411. break
  412. } else {
  413. cm = cm.Parent(0)
  414. }
  415. } else {
  416. var emptyCnt = 0
  417. var sameIdcnt = 0
  418. for i := 0; i < cm.ParentCount(); i++ {
  419. p := cm.Parent(i)
  420. pt, _ := repo.SubTree(p.Tree, dirname)
  421. var pEntry *git.TreeEntry
  422. if pt != nil {
  423. pEntry = pt.EntryByName(entry.Name)
  424. }
  425. if pEntry == nil {
  426. if emptyCnt == cm.ParentCount()-1 {
  427. goto loop
  428. } else {
  429. emptyCnt = emptyCnt + 1
  430. continue
  431. }
  432. } else {
  433. if !pEntry.Id.Equal(entry.Id) {
  434. goto loop
  435. } else {
  436. if sameIdcnt == cm.ParentCount()-1 {
  437. // TODO: now follow the first parent commit?
  438. cm = cm.Parent(0)
  439. break
  440. }
  441. sameIdcnt = sameIdcnt + 1
  442. }
  443. }
  444. }
  445. }
  446. }
  447. loop:
  448. rp := &RepoFile{
  449. entry,
  450. path.Join(dirname, entry.Name),
  451. size,
  452. repo,
  453. cm,
  454. }
  455. if entry.IsFile() {
  456. repofiles = append(repofiles, rp)
  457. } else if entry.IsDir() {
  458. repodirs = append(repodirs, rp)
  459. }
  460. }
  461. return 0
  462. })
  463. return append(repodirs, repofiles...), nil
  464. }
  465. func GetCommit(userName, repoName, branchname, commitid string) (*git.Commit, error) {
  466. repo, err := git.OpenRepository(RepoPath(userName, repoName))
  467. if err != nil {
  468. return nil, err
  469. }
  470. if commitid != "" {
  471. oid, err := git.NewOidFromString(commitid)
  472. if err != nil {
  473. return nil, err
  474. }
  475. return repo.LookupCommit(oid)
  476. }
  477. if branchname == "" {
  478. return nil, errors.New("no branch name and no commit id")
  479. }
  480. r, err := repo.LookupReference(fmt.Sprintf("refs/heads/%s", branchname))
  481. if err != nil {
  482. return nil, err
  483. }
  484. return r.LastCommit()
  485. }
  486. // GetCommits returns all commits of given branch of repository.
  487. func GetCommits(userName, reposName, branchname string) (*list.List, error) {
  488. repo, err := git.OpenRepository(RepoPath(userName, reposName))
  489. if err != nil {
  490. return nil, err
  491. }
  492. r, err := repo.LookupReference(fmt.Sprintf("refs/heads/%s", branchname))
  493. if err != nil {
  494. return nil, err
  495. }
  496. return r.AllCommits()
  497. }