refs.go 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246
  1. package gig
  2. import (
  3. "bufio"
  4. "bytes"
  5. "fmt"
  6. "io"
  7. "io/ioutil"
  8. "os"
  9. "os/exec"
  10. "path"
  11. "path/filepath"
  12. "strings"
  13. )
  14. type Ref interface {
  15. Repo() *Repository
  16. Name() string
  17. Fullname() string
  18. Namespace() string
  19. Resolve() (SHA1, error)
  20. }
  21. type ref struct {
  22. repo *Repository
  23. name string
  24. ns string // #special, #branch, or the name like 'remote', 'tags'
  25. }
  26. func (r *ref) Name() string {
  27. return r.name
  28. }
  29. func (r *ref) Fullname() string {
  30. fullname := r.name
  31. if !strings.HasPrefix(r.ns, "#") {
  32. fullname = path.Join(r.ns, r.name)
  33. }
  34. return fullname
  35. }
  36. func (r *ref) Repo() *Repository {
  37. return r.repo
  38. }
  39. func (r *ref) Namespace() string {
  40. return r.ns
  41. }
  42. func IsBranchRef(r Ref) bool {
  43. return r.Namespace() == "#branch"
  44. }
  45. //IDRef is a reference that points via
  46. //a sha1 directly to a git object
  47. type IDRef struct {
  48. ref
  49. id SHA1
  50. }
  51. //Resolve for IDRef returns the stored object
  52. //id (SHA1)
  53. func (r *IDRef) Resolve() (SHA1, error) {
  54. return r.id, nil
  55. }
  56. //SymbolicRef is a reference that points
  57. //to another reference
  58. type SymbolicRef struct {
  59. ref
  60. Symbol string
  61. }
  62. //Resolve will resolve the symbolic reference into
  63. //an object id.
  64. func (r *SymbolicRef) Resolve() (SHA1, error) {
  65. gdir := fmt.Sprintf("--git-dir=%s", r.repo.Path)
  66. cmd := exec.Command("git", gdir, "rev-parse", r.Fullname())
  67. body, err := cmd.Output()
  68. if err != nil {
  69. var id SHA1
  70. return id, err
  71. }
  72. return ParseSHA1(string(body))
  73. }
  74. func parseRefName(filename string) (name, ns string, err error) {
  75. comps := strings.Split(filename, "/")
  76. n := len(comps)
  77. if n < 1 || n == 2 || (n > 2 && comps[0] != "refs") {
  78. err = fmt.Errorf("git: unexpected ref name: %v", filename)
  79. return
  80. }
  81. if n == 1 {
  82. name = comps[0]
  83. ns = "#special"
  84. }
  85. // 'man gitrepository-layout' is really helpfull
  86. // 'man git-check-ref-format' too
  87. // [HEAD|ORIG_HEAD] -> special head
  88. // [0|refs][1|<ns>][2+|name]
  89. // <ns> == "heads" -> local branch"
  90. switch {
  91. case n == 1:
  92. name = comps[0]
  93. ns = "#special"
  94. case comps[1] == "heads":
  95. name = path.Join(comps[2:]...)
  96. ns = "#branch"
  97. default:
  98. name = path.Join(comps[2:]...)
  99. ns = comps[1]
  100. }
  101. return
  102. }
  103. func (repo *Repository) parseRef(filename string) (Ref, error) {
  104. name, ns, err := parseRefName(filename)
  105. if err != nil {
  106. return nil, err
  107. }
  108. base := ref{repo, name, ns}
  109. //now to the actual contents of the ref
  110. data, err := ioutil.ReadFile(filepath.Join(repo.Path, filename))
  111. if err != nil {
  112. if os.IsNotExist(err) {
  113. return repo.findPackedRef(base.Fullname())
  114. }
  115. return nil, err
  116. }
  117. b := string(data)
  118. if strings.HasPrefix(b, "ref:") {
  119. trimmed := strings.Trim(b[4:], " \n")
  120. return &SymbolicRef{base, trimmed}, nil
  121. }
  122. id, err := ParseSHA1(b)
  123. if err == nil {
  124. return &IDRef{base, id}, nil
  125. }
  126. return nil, fmt.Errorf("git: unknown ref type: %q", b)
  127. }
  128. func (repo *Repository) listRefWithName(name string) (res []Ref) {
  129. gdir := fmt.Sprintf("--git-dir=%s", repo.Path)
  130. cmd := exec.Command("git", gdir, "show-ref", name)
  131. body, err := cmd.Output()
  132. if err != nil {
  133. return
  134. }
  135. r := bytes.NewBuffer(body)
  136. for {
  137. var l string
  138. l, err = r.ReadString('\n')
  139. if err != nil {
  140. break
  141. }
  142. _, name := split2(l[:len(l)-1], " ")
  143. r, err := repo.parseRef(name)
  144. if err != nil {
  145. fmt.Fprintf(os.Stderr, "git: could not parse ref with name %q: %v", name, err)
  146. continue
  147. }
  148. res = append(res, r)
  149. }
  150. return
  151. }
  152. func (repo *Repository) loadPackedRefs() ([]Ref, error) {
  153. fd, err := os.Open(filepath.Join(repo.Path, "packed-refs"))
  154. if err != nil {
  155. return nil, err
  156. }
  157. defer fd.Close()
  158. r := bufio.NewReader(fd)
  159. var refs []Ref
  160. for {
  161. var l string
  162. l, err = r.ReadString('\n')
  163. if err != nil {
  164. break
  165. }
  166. head, tail := split2(l, " ")
  167. if tail == "" {
  168. //probably a peeled id (i.e. "^SHA1")
  169. //TODO: do something with it
  170. continue
  171. }
  172. name, ns, err := parseRefName(tail[:len(tail)-1])
  173. if err != nil {
  174. //TODO: log error, panic?
  175. continue
  176. }
  177. id, err := ParseSHA1(head)
  178. if err != nil {
  179. //TODO: same as above
  180. continue
  181. }
  182. refs = append(refs, &IDRef{ref{repo, name, ns}, id})
  183. }
  184. if err != nil && err != io.EOF {
  185. return nil, err
  186. }
  187. return refs, nil
  188. }
  189. func (repo *Repository) findPackedRef(name string) (Ref, error) {
  190. refs, err := repo.loadPackedRefs()
  191. if err != nil {
  192. return nil, err
  193. }
  194. for _, ref := range refs {
  195. if ref.Fullname() == name {
  196. return ref, nil
  197. }
  198. }
  199. return nil, fmt.Errorf("ref with name %q not found", name)
  200. }