git.go 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411
  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. "bufio"
  7. "container/list"
  8. "fmt"
  9. "io"
  10. "os"
  11. "os/exec"
  12. "path"
  13. "strings"
  14. "github.com/Unknwon/com"
  15. "github.com/gogits/git"
  16. "github.com/gogits/gogs/modules/base"
  17. )
  18. // RepoFile represents a file object in git repository.
  19. type RepoFile struct {
  20. *git.TreeEntry
  21. Path string
  22. Size int64
  23. Repo *git.Repository
  24. Commit *git.Commit
  25. }
  26. // LookupBlob returns the content of an object.
  27. func (file *RepoFile) LookupBlob() (*git.Blob, error) {
  28. if file.Repo == nil {
  29. return nil, ErrRepoFileNotLoaded
  30. }
  31. return file.Repo.LookupBlob(file.Id)
  32. }
  33. // GetBranches returns all branches of given repository.
  34. func GetBranches(userName, repoName string) ([]string, error) {
  35. repo, err := git.OpenRepository(RepoPath(userName, repoName))
  36. if err != nil {
  37. return nil, err
  38. }
  39. refs, err := repo.AllReferences()
  40. if err != nil {
  41. return nil, err
  42. }
  43. brs := make([]string, len(refs))
  44. for i, ref := range refs {
  45. brs[i] = ref.BranchName()
  46. }
  47. return brs, nil
  48. }
  49. // GetTags returns all tags of given repository.
  50. func GetTags(userName, repoName string) ([]string, error) {
  51. repo, err := git.OpenRepository(RepoPath(userName, repoName))
  52. if err != nil {
  53. return nil, err
  54. }
  55. refs, err := repo.AllTags()
  56. if err != nil {
  57. return nil, err
  58. }
  59. tags := make([]string, len(refs))
  60. for i, ref := range refs {
  61. tags[i] = ref.Name
  62. }
  63. return tags, nil
  64. }
  65. func IsBranchExist(userName, repoName, branchName string) bool {
  66. repo, err := git.OpenRepository(RepoPath(userName, repoName))
  67. if err != nil {
  68. return false
  69. }
  70. return repo.IsBranchExist(branchName)
  71. }
  72. func GetTargetFile(userName, repoName, branchName, commitId, rpath string) (*RepoFile, error) {
  73. repo, err := git.OpenRepository(RepoPath(userName, repoName))
  74. if err != nil {
  75. return nil, err
  76. }
  77. commit, err := repo.GetCommitOfBranch(branchName)
  78. if err != nil {
  79. commit, err = repo.GetCommit(commitId)
  80. if err != nil {
  81. return nil, err
  82. }
  83. }
  84. parts := strings.Split(path.Clean(rpath), "/")
  85. var entry *git.TreeEntry
  86. tree := commit.Tree
  87. for i, part := range parts {
  88. if i == len(parts)-1 {
  89. entry = tree.EntryByName(part)
  90. if entry == nil {
  91. return nil, ErrRepoFileNotExist
  92. }
  93. } else {
  94. tree, err = repo.SubTree(tree, part)
  95. if err != nil {
  96. return nil, err
  97. }
  98. }
  99. }
  100. size, err := repo.ObjectSize(entry.Id)
  101. if err != nil {
  102. return nil, err
  103. }
  104. repoFile := &RepoFile{
  105. entry,
  106. rpath,
  107. size,
  108. repo,
  109. commit,
  110. }
  111. return repoFile, nil
  112. }
  113. // GetReposFiles returns a list of file object in given directory of repository.
  114. // func GetReposFilesOfBranch(userName, repoName, branchName, rpath string) ([]*RepoFile, error) {
  115. // return getReposFiles(userName, repoName, commitId, rpath)
  116. // }
  117. // GetReposFiles returns a list of file object in given directory of repository.
  118. func GetReposFiles(userName, repoName, commitId, rpath string) ([]*RepoFile, error) {
  119. return getReposFiles(userName, repoName, commitId, rpath)
  120. }
  121. func getReposFiles(userName, repoName, commitId string, rpath string) ([]*RepoFile, error) {
  122. repopath := RepoPath(userName, repoName)
  123. repo, err := git.OpenRepository(repopath)
  124. if err != nil {
  125. return nil, err
  126. }
  127. commit, err := repo.GetCommit(commitId)
  128. if err != nil {
  129. return nil, err
  130. }
  131. var repodirs []*RepoFile
  132. var repofiles []*RepoFile
  133. commit.Tree.Walk(func(dirname string, entry *git.TreeEntry) int {
  134. if dirname == rpath {
  135. // TODO: size get method shoule be improved
  136. size, err := repo.ObjectSize(entry.Id)
  137. if err != nil {
  138. return 0
  139. }
  140. stdout, _, err := com.ExecCmdDir(repopath, "git", "log", "-1", "--pretty=format:%H", commitId, "--", path.Join(dirname, entry.Name))
  141. if err != nil {
  142. return 0
  143. }
  144. filecm, err := repo.GetCommit(string(stdout))
  145. if err != nil {
  146. return 0
  147. }
  148. rp := &RepoFile{
  149. entry,
  150. path.Join(dirname, entry.Name),
  151. size,
  152. repo,
  153. filecm,
  154. }
  155. if entry.IsFile() {
  156. repofiles = append(repofiles, rp)
  157. } else if entry.IsDir() {
  158. repodirs = append(repodirs, rp)
  159. }
  160. }
  161. return 0
  162. })
  163. return append(repodirs, repofiles...), nil
  164. }
  165. func GetCommit(userName, repoName, commitId string) (*git.Commit, error) {
  166. repo, err := git.OpenRepository(RepoPath(userName, repoName))
  167. if err != nil {
  168. return nil, err
  169. }
  170. return repo.GetCommit(commitId)
  171. }
  172. // GetCommitsByBranch returns all commits of given branch of repository.
  173. func GetCommitsByBranch(userName, repoName, branchName string) (*list.List, error) {
  174. repo, err := git.OpenRepository(RepoPath(userName, repoName))
  175. if err != nil {
  176. return nil, err
  177. }
  178. r, err := repo.LookupReference(fmt.Sprintf("refs/heads/%s", branchName))
  179. if err != nil {
  180. return nil, err
  181. }
  182. return r.AllCommits()
  183. }
  184. // GetCommitsByCommitId returns all commits of given commitId of repository.
  185. func GetCommitsByCommitId(userName, repoName, commitId string) (*list.List, error) {
  186. repo, err := git.OpenRepository(RepoPath(userName, repoName))
  187. if err != nil {
  188. return nil, err
  189. }
  190. oid, err := git.NewOidFromString(commitId)
  191. if err != nil {
  192. return nil, err
  193. }
  194. return repo.CommitsBefore(oid)
  195. }
  196. // Diff line types.
  197. const (
  198. DIFF_LINE_PLAIN = iota + 1
  199. DIFF_LINE_ADD
  200. DIFF_LINE_DEL
  201. DIFF_LINE_SECTION
  202. )
  203. const (
  204. DIFF_FILE_ADD = iota + 1
  205. DIFF_FILE_CHANGE
  206. DIFF_FILE_DEL
  207. )
  208. type DiffLine struct {
  209. LeftIdx int
  210. RightIdx int
  211. Type int
  212. Content string
  213. }
  214. func (d DiffLine) GetType() int {
  215. return d.Type
  216. }
  217. type DiffSection struct {
  218. Name string
  219. Lines []*DiffLine
  220. }
  221. type DiffFile struct {
  222. Name string
  223. Addition, Deletion int
  224. Type int
  225. Sections []*DiffSection
  226. }
  227. type Diff struct {
  228. TotalAddition, TotalDeletion int
  229. Files []*DiffFile
  230. }
  231. func (diff *Diff) NumFiles() int {
  232. return len(diff.Files)
  233. }
  234. const DIFF_HEAD = "diff --git "
  235. func ParsePatch(reader io.Reader) (*Diff, error) {
  236. scanner := bufio.NewScanner(reader)
  237. var (
  238. curFile *DiffFile
  239. curSection = &DiffSection{
  240. Lines: make([]*DiffLine, 0, 10),
  241. }
  242. leftLine, rightLine int
  243. )
  244. diff := &Diff{Files: make([]*DiffFile, 0)}
  245. var i int
  246. for scanner.Scan() {
  247. line := scanner.Text()
  248. // fmt.Println(i, line)
  249. if strings.HasPrefix(line, "+++ ") || strings.HasPrefix(line, "--- ") {
  250. continue
  251. }
  252. i = i + 1
  253. if line == "" {
  254. continue
  255. }
  256. if line[0] == ' ' {
  257. diffLine := &DiffLine{Type: DIFF_LINE_PLAIN, Content: line, LeftIdx: leftLine, RightIdx: rightLine}
  258. leftLine++
  259. rightLine++
  260. curSection.Lines = append(curSection.Lines, diffLine)
  261. continue
  262. } else if line[0] == '@' {
  263. curSection = &DiffSection{}
  264. curFile.Sections = append(curFile.Sections, curSection)
  265. ss := strings.Split(line, "@@")
  266. diffLine := &DiffLine{Type: DIFF_LINE_SECTION, Content: line}
  267. curSection.Lines = append(curSection.Lines, diffLine)
  268. // Parse line number.
  269. ranges := strings.Split(ss[len(ss)-2][1:], " ")
  270. leftLine, _ = base.StrTo(strings.Split(ranges[0], ",")[0][1:]).Int()
  271. rightLine, _ = base.StrTo(strings.Split(ranges[1], ",")[0]).Int()
  272. continue
  273. } else if line[0] == '+' {
  274. curFile.Addition++
  275. diff.TotalAddition++
  276. diffLine := &DiffLine{Type: DIFF_LINE_ADD, Content: line, RightIdx: rightLine}
  277. rightLine++
  278. curSection.Lines = append(curSection.Lines, diffLine)
  279. continue
  280. } else if line[0] == '-' {
  281. curFile.Deletion++
  282. diff.TotalDeletion++
  283. diffLine := &DiffLine{Type: DIFF_LINE_DEL, Content: line, LeftIdx: leftLine}
  284. if leftLine > 0 {
  285. leftLine++
  286. }
  287. curSection.Lines = append(curSection.Lines, diffLine)
  288. continue
  289. }
  290. // Get new file.
  291. if strings.HasPrefix(line, DIFF_HEAD) {
  292. fs := strings.Split(line[len(DIFF_HEAD):], " ")
  293. a := fs[0]
  294. curFile = &DiffFile{
  295. Name: a[strings.Index(a, "/")+1:],
  296. Type: DIFF_FILE_CHANGE,
  297. Sections: make([]*DiffSection, 0, 10),
  298. }
  299. diff.Files = append(diff.Files, curFile)
  300. // Check file diff type.
  301. for scanner.Scan() {
  302. switch {
  303. case strings.HasPrefix(scanner.Text(), "new file"):
  304. curFile.Type = DIFF_FILE_ADD
  305. case strings.HasPrefix(scanner.Text(), "deleted"):
  306. curFile.Type = DIFF_FILE_DEL
  307. case strings.HasPrefix(scanner.Text(), "index"):
  308. curFile.Type = DIFF_FILE_CHANGE
  309. }
  310. if curFile.Type > 0 {
  311. break
  312. }
  313. }
  314. }
  315. }
  316. return diff, nil
  317. }
  318. func GetDiff(repoPath, commitid string) (*Diff, error) {
  319. repo, err := git.OpenRepository(repoPath)
  320. if err != nil {
  321. return nil, err
  322. }
  323. commit, err := repo.GetCommit(commitid)
  324. if err != nil {
  325. return nil, err
  326. }
  327. // First commit of repository.
  328. if commit.ParentCount() == 0 {
  329. rd, wr := io.Pipe()
  330. go func() {
  331. cmd := exec.Command("git", "show", commitid)
  332. cmd.Dir = repoPath
  333. cmd.Stdout = wr
  334. cmd.Stdin = os.Stdin
  335. cmd.Stderr = os.Stderr
  336. cmd.Run()
  337. wr.Close()
  338. }()
  339. defer rd.Close()
  340. return ParsePatch(rd)
  341. }
  342. rd, wr := io.Pipe()
  343. go func() {
  344. cmd := exec.Command("git", "diff", commit.Parent(0).Oid.String(), commitid)
  345. cmd.Dir = repoPath
  346. cmd.Stdout = wr
  347. cmd.Stdin = os.Stdin
  348. cmd.Stderr = os.Stderr
  349. cmd.Run()
  350. wr.Close()
  351. }()
  352. defer rd.Close()
  353. return ParsePatch(rd)
  354. }