fetch.go 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  1. package main
  2. import (
  3. "bytes"
  4. "io"
  5. "os"
  6. "os/exec"
  7. "path/filepath"
  8. "strings"
  9. "github.com/cryptix/git-remote-ipfs/Godeps/_workspace/src/github.com/cryptix/exp/git"
  10. "github.com/cryptix/git-remote-ipfs/Godeps/_workspace/src/gopkg.in/errgo.v1"
  11. )
  12. // "fetch $sha1 $ref" method 1 - unpacking loose objects
  13. // - look for it in ".git/objects/substr($sha1, 0, 2)/substr($sha, 2)"
  14. // - if found, download it and put it in place. (there may be a command for this)
  15. // - done \o/
  16. func fetchObject(sha1 string) error {
  17. return recurseCommit(sha1)
  18. }
  19. func recurseCommit(sha1 string) error {
  20. obj, err := fetchAndWriteObj(sha1)
  21. if err != nil {
  22. return errgo.Notef(err, "fetchAndWriteObj(%s) commit object failed", sha1)
  23. }
  24. commit, ok := obj.Commit()
  25. if !ok {
  26. return errgo.Newf("sha1<%s> is not a git commit object:%s ", sha1, obj)
  27. }
  28. if commit.Parent != "" {
  29. if err := recurseCommit(commit.Parent); err != nil {
  30. return errgo.Notef(err, "recurseCommit(%s) commit Parent failed", commit.Parent)
  31. }
  32. }
  33. return fetchTree(commit.Tree)
  34. }
  35. func fetchTree(sha1 string) error {
  36. obj, err := fetchAndWriteObj(sha1)
  37. if err != nil {
  38. return errgo.Notef(err, "fetchAndWriteObj(%s) commit tree failed", sha1)
  39. }
  40. entries, ok := obj.Tree()
  41. if !ok {
  42. return errgo.Newf("sha1<%s> is not a git tree object:%s ", sha1, obj)
  43. }
  44. for _, t := range entries {
  45. obj, err := fetchAndWriteObj(t.SHA1Sum.String())
  46. if err != nil {
  47. return errgo.Notef(err, "fetchAndWriteObj(%s) commit tree failed", sha1)
  48. }
  49. if obj.Type != git.BlobT {
  50. return errgo.Newf("sha1<%s> is not a git tree object:%s ", t.SHA1Sum.String(), obj)
  51. }
  52. }
  53. return nil
  54. }
  55. // fetchAndWriteObj looks for the loose object under 'thisGitRepo' global git dir
  56. // and usses an io.TeeReader to write it to the local repo
  57. func fetchAndWriteObj(sha1 string) (*git.Object, error) {
  58. p := filepath.Join(ipfsRepoPath, "objects", sha1[:2], sha1[2:])
  59. ipfsCat, err := ipfsShell.Cat(p)
  60. if err != nil {
  61. return nil, errgo.Notef(err, "shell.Cat() commit failed")
  62. }
  63. targetP := filepath.Join(thisGitRepo, "objects", sha1[:2], sha1[2:])
  64. if err := os.MkdirAll(filepath.Join(thisGitRepo, "objects", sha1[:2]), 0700); err != nil {
  65. return nil, errgo.Notef(err, "mkDirAll() failed")
  66. }
  67. targetObj, err := os.Create(targetP)
  68. if err != nil {
  69. return nil, errgo.Notef(err, "os.Create(%s) commit failed", targetP)
  70. }
  71. obj, err := git.DecodeObject(io.TeeReader(ipfsCat, targetObj))
  72. if err != nil {
  73. return nil, errgo.Notef(err, "git.DecodeObject(commit) failed")
  74. }
  75. if err := ipfsCat.Close(); err != nil {
  76. if errRm := os.Remove(targetObj.Name()); errRm != nil {
  77. return nil, errgo.WithCausef(errRm, err, "os.Remove(targetObj) failed while closing ipfs cat")
  78. }
  79. return nil, errgo.Notef(err, "closing ipfs cat failed")
  80. }
  81. if err := targetObj.Close(); err != nil {
  82. return nil, errgo.Notef(err, "target file close() failed")
  83. }
  84. return obj, nil
  85. }
  86. // "fetch $sha1 $ref" method 2 - unpacking packed objects
  87. // - look for it in packfiles by fetching ".git/objects/pack/*.idx"
  88. // and looking at each idx with cat <idx> | git show-index (alternatively can learn to read the format in go)
  89. // - if found in an <idx>, download the relevant .pack file,
  90. // and feed it into `git index-pack --stdin --fix-thin` which will put it into place.
  91. // - done \o/
  92. func fetchPackedObject(sha1 string) error {
  93. // search for all index files
  94. packPath := filepath.Join(ipfsRepoPath, "objects", "pack")
  95. links, err := ipfsShell.List(packPath)
  96. if err != nil {
  97. return errgo.Notef(err, "shell FileList(%q) failed", packPath)
  98. }
  99. var indexes []string
  100. for _, lnk := range links {
  101. if lnk.Type == 2 && strings.HasSuffix(lnk.Name, ".idx") {
  102. indexes = append(indexes, filepath.Join(packPath, lnk.Name))
  103. }
  104. }
  105. if len(indexes) == 0 {
  106. return errgo.New("fetchPackedObject: no idx files found")
  107. }
  108. for _, idx := range indexes {
  109. idxF, err := ipfsShell.Cat(idx)
  110. if err != nil {
  111. return errgo.Notef(err, "fetchPackedObject: idx<%s> cat(%s) failed", sha1, idx)
  112. }
  113. // using external git show-index < idxF for now
  114. // TODO: parse index file in go to make this portable
  115. var b bytes.Buffer
  116. showIdx := exec.Command("git", "show-index")
  117. showIdx.Stdin = idxF
  118. showIdx.Stdout = &b
  119. showIdx.Stderr = &b
  120. if err := showIdx.Run(); err != nil {
  121. return errgo.Notef(err, "fetchPackedObject: idx<%s> show-index start failed", sha1)
  122. }
  123. cmdOut := b.String()
  124. if !strings.Contains(cmdOut, sha1) {
  125. log.WithField("idx", filepath.Base(idx)).Debug("git show-index: sha1 not in index, next idx file")
  126. continue
  127. }
  128. //log.Debug("git show-index:", cmdOut)
  129. // we found an index with our hash inside
  130. pack := strings.Replace(idx, ".idx", ".pack", 1)
  131. log.Debug("unpacking:", pack)
  132. packF, err := ipfsShell.Cat(pack)
  133. if err != nil {
  134. return errgo.Notef(err, "fetchPackedObject: pack<%s> open() failed", sha1)
  135. }
  136. b.Reset()
  137. unpackIdx := exec.Command("git", "unpack-objects")
  138. unpackIdx.Dir = thisGitRepo // GIT_DIR
  139. unpackIdx.Stdin = packF
  140. unpackIdx.Stdout = &b
  141. unpackIdx.Stderr = &b
  142. if err := unpackIdx.Run(); err != nil {
  143. return errgo.Notef(err, "fetchPackedObject: pack<%s> 'git unpack-objects' failed\nOutput: %s", sha1, b.String())
  144. }
  145. log.Debug("git unpack-objects ...:", b.String())
  146. return nil
  147. }
  148. return errgo.Newf("did not find sha1<%s> in %d index files", sha1, len(indexes))
  149. }