fetch.go 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  1. package main
  2. import (
  3. "bytes"
  4. "io"
  5. "os"
  6. "os/exec"
  7. "path/filepath"
  8. "strings"
  9. "github.com/cryptix/exp/git"
  10. "github.com/pkg/errors"
  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 errors.Wrapf(err, "fetchAndWriteObj(%s) commit object failed", sha1)
  23. }
  24. commit, ok := obj.Commit()
  25. if !ok {
  26. return errors.Errorf("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 errors.Wrapf(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 errors.Wrapf(err, "fetchAndWriteObj(%s) commit tree failed", sha1)
  39. }
  40. entries, ok := obj.Tree()
  41. if !ok {
  42. return errors.Errorf("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 errors.Wrapf(err, "fetchAndWriteObj(%s) commit tree failed", sha1)
  48. }
  49. if obj.Type != git.BlobT {
  50. return errors.Errorf("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, errors.Wrapf(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, errors.Wrapf(err, "mkDirAll() failed")
  66. }
  67. targetObj, err := os.Create(targetP)
  68. if err != nil {
  69. return nil, errors.Wrapf(err, "os.Create(%s) commit failed", targetP)
  70. }
  71. obj, err := git.DecodeObject(io.TeeReader(ipfsCat, targetObj))
  72. if err != nil {
  73. return nil, errors.Wrapf(err, "git.DecodeObject(commit) failed")
  74. }
  75. if err := ipfsCat.Close(); err != nil {
  76. err = errors.Wrap(err, "ipfs/cat Close failed")
  77. if errRm := os.Remove(targetObj.Name()); errRm != nil {
  78. err = errors.Wrapf(err, "failed removing targetObj: %s", errRm)
  79. return nil, err
  80. }
  81. return nil, errors.Wrapf(err, "closing ipfs cat failed")
  82. }
  83. if err := targetObj.Close(); err != nil {
  84. return nil, errors.Wrapf(err, "target file close() failed")
  85. }
  86. return obj, nil
  87. }
  88. // "fetch $sha1 $ref" method 2 - unpacking packed objects
  89. // - look for it in packfiles by fetching ".git/objects/pack/*.idx"
  90. // and looking at each idx with cat <idx> | git show-index (alternatively can learn to read the format in go)
  91. // - if found in an <idx>, download the relevant .pack file,
  92. // and feed it into `git index-pack --stdin --fix-thin` which will put it into place.
  93. // - done \o/
  94. func fetchPackedObject(sha1 string) error {
  95. // search for all index files
  96. packPath := filepath.Join(ipfsRepoPath, "objects", "pack")
  97. links, err := ipfsShell.List(packPath)
  98. if err != nil {
  99. return errors.Wrapf(err, "shell FileList(%q) failed", packPath)
  100. }
  101. var indexes []string
  102. for _, lnk := range links {
  103. if lnk.Type == 2 && strings.HasSuffix(lnk.Name, ".idx") {
  104. indexes = append(indexes, filepath.Join(packPath, lnk.Name))
  105. }
  106. }
  107. if len(indexes) == 0 {
  108. return errors.New("fetchPackedObject: no idx files found")
  109. }
  110. for _, idx := range indexes {
  111. idxF, err := ipfsShell.Cat(idx)
  112. if err != nil {
  113. return errors.Wrapf(err, "fetchPackedObject: idx<%s> cat(%s) failed", sha1, idx)
  114. }
  115. // using external git show-index < idxF for now
  116. // TODO: parse index file in go to make this portable
  117. var b bytes.Buffer
  118. showIdx := exec.Command("git", "show-index")
  119. showIdx.Stdin = idxF
  120. showIdx.Stdout = &b
  121. showIdx.Stderr = &b
  122. if err := showIdx.Run(); err != nil {
  123. return errors.Wrapf(err, "fetchPackedObject: idx<%s> show-index start failed", sha1)
  124. }
  125. cmdOut := b.String()
  126. if !strings.Contains(cmdOut, sha1) {
  127. log.Log("idx", filepath.Base(idx), "event", "debug", "msg", "git show-index: sha1 not in index, next idx file")
  128. continue
  129. }
  130. // we found an index with our hash inside
  131. pack := strings.Replace(idx, ".idx", ".pack", 1)
  132. packF, err := ipfsShell.Cat(pack)
  133. if err != nil {
  134. return errors.Wrapf(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 errors.Wrapf(err, "fetchPackedObject: pack<%s> 'git unpack-objects' failed\nOutput: %s", sha1, b.String())
  144. }
  145. return nil
  146. }
  147. return errors.Errorf("did not find sha1<%s> in %d index files", sha1, len(indexes))
  148. }