git.go 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136
  1. package main
  2. import (
  3. "bufio"
  4. "bytes"
  5. "compress/zlib"
  6. "fmt"
  7. "io"
  8. "io/ioutil"
  9. "os/exec"
  10. "path/filepath"
  11. "strconv"
  12. "strings"
  13. "github.com/pkg/errors"
  14. )
  15. // return the objects reachable from ref excluding the objects reachable from exclude
  16. func gitListObjects(ref string, exclude []string) ([]string, error) {
  17. args := []string{"rev-list", "--objects", ref}
  18. for _, e := range exclude {
  19. args = append(args, "^"+e)
  20. }
  21. revList := exec.Command("git", args...)
  22. // dunno why - sometime git doesnt want to work on the inner repo/.git
  23. if strings.HasSuffix(thisGitRepo, ".git") {
  24. thisGitRepo = filepath.Dir(thisGitRepo)
  25. }
  26. revList.Dir = thisGitRepo // GIT_DIR
  27. out, err := revList.CombinedOutput()
  28. if err != nil {
  29. return nil, errors.Wrapf(err, "rev-list failed: %s\n%q", err, string(out))
  30. }
  31. var objs []string
  32. s := bufio.NewScanner(bytes.NewReader(out))
  33. for s.Scan() {
  34. objs = append(objs, strings.Split(s.Text(), " ")[0])
  35. }
  36. if err := s.Err(); err != nil {
  37. return nil, errors.Wrapf(err, "scanning rev-list output failed: %s", err)
  38. }
  39. return objs, nil
  40. }
  41. func gitFlattenObject(sha1 string) (io.Reader, error) {
  42. kind, err := gitCatKind(sha1)
  43. if err != nil {
  44. return nil, errors.Wrapf(err, "flatten: kind(%s) failed", sha1)
  45. }
  46. size, err := gitCatSize(sha1)
  47. if err != nil {
  48. return nil, errors.Wrapf(err, "flatten: size(%s) failed", sha1)
  49. }
  50. r, err := gitCatData(sha1, kind)
  51. if err != nil {
  52. return nil, errors.Wrapf(err, "flatten: data(%s) failed", sha1)
  53. }
  54. // move to exp/git
  55. pr, pw := io.Pipe()
  56. go func() {
  57. zw := zlib.NewWriter(pw)
  58. if _, err := fmt.Fprintf(zw, "%s %d\x00", kind, size); err != nil {
  59. pw.CloseWithError(errors.Wrapf(err, "writing git format header failed"))
  60. return
  61. }
  62. if _, err := io.Copy(zw, r); err != nil {
  63. pw.CloseWithError(errors.Wrapf(err, "copying git data failed"))
  64. return
  65. }
  66. if err := zw.Close(); err != nil {
  67. pw.CloseWithError(errors.Wrapf(err, "zlib close failed"))
  68. return
  69. }
  70. pw.Close()
  71. }()
  72. return pr, nil
  73. }
  74. func gitCatKind(sha1 string) (string, error) {
  75. catFile := exec.Command("git", "cat-file", "-t", sha1)
  76. catFile.Dir = thisGitRepo // GIT_DIR
  77. out, err := catFile.CombinedOutput()
  78. return strings.TrimSpace(string(out)), err
  79. }
  80. func gitCatSize(sha1 string) (int64, error) {
  81. catFile := exec.Command("git", "cat-file", "-s", sha1)
  82. catFile.Dir = thisGitRepo // GIT_DIR
  83. out, err := catFile.CombinedOutput()
  84. if err != nil {
  85. return -1, errors.Wrapf(err, "catSize(%s): run failed", sha1)
  86. }
  87. return strconv.ParseInt(strings.TrimSpace(string(out)), 10, 64)
  88. }
  89. func gitCatData(sha1, kind string) (io.Reader, error) {
  90. catFile := exec.Command("git", "cat-file", kind, sha1)
  91. catFile.Dir = thisGitRepo // GIT_DIR
  92. stdout, err := catFile.StdoutPipe()
  93. if err != nil {
  94. return nil, errors.Wrapf(err, "catData(%s): stdoutPipe failed", sha1)
  95. }
  96. stderr, err := catFile.StderrPipe()
  97. if err != nil {
  98. return nil, errors.Wrapf(err, "catData(%s): stderrPipe failed", sha1)
  99. }
  100. r := io.MultiReader(stdout, stderr)
  101. if err := catFile.Start(); err != nil {
  102. err = errors.Wrap(err, "catFile.Start failed")
  103. out, readErr := ioutil.ReadAll(r)
  104. if readErr != nil {
  105. readErr = errors.Wrap(readErr, "readAll failed")
  106. return nil, errors.Wrapf(err, "catData(%s) failed during: %s", sha1, readErr)
  107. }
  108. return nil, errors.Wrapf(err, "catData(%s) failed: %q", sha1, out)
  109. }
  110. // todo wait for cmd?!
  111. return r, nil
  112. }
  113. func gitRefHash(ref string) (string, error) {
  114. refParse := exec.Command("git", "rev-parse", ref)
  115. refParse.Dir = thisGitRepo // GIT_DIR
  116. out, err := refParse.CombinedOutput()
  117. return strings.TrimSpace(string(out)), err
  118. }
  119. func gitIsAncestor(a, ref string) error {
  120. mergeBase := exec.Command("git", "merge-base", "--is-ancestor", a, ref)
  121. mergeBase.Dir = thisGitRepo // GIT_DIR
  122. if out, err := mergeBase.CombinedOutput(); err != nil {
  123. return errors.Wrapf(err, "merge-base failed: %q", string(out))
  124. }
  125. return nil
  126. }