gitutils.go 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122
  1. package gitutils
  2. import (
  3. "fmt"
  4. "io/ioutil"
  5. "net/http"
  6. "net/url"
  7. "os"
  8. "os/exec"
  9. "path/filepath"
  10. "strings"
  11. "github.com/docker/docker/pkg/symlink"
  12. "github.com/docker/docker/pkg/urlutil"
  13. "github.com/pkg/errors"
  14. )
  15. // Clone clones a repository into a newly created directory which
  16. // will be under "docker-build-git"
  17. func Clone(remoteURL string) (string, error) {
  18. if !urlutil.IsGitTransport(remoteURL) {
  19. remoteURL = "https://" + remoteURL
  20. }
  21. root, err := ioutil.TempDir("", "docker-build-git")
  22. if err != nil {
  23. return "", err
  24. }
  25. u, err := url.Parse(remoteURL)
  26. if err != nil {
  27. return "", err
  28. }
  29. if out, err := gitWithinDir(root, "init"); err != nil {
  30. return "", errors.Wrapf(err, "failed to init repo at %s: %s", root, out)
  31. }
  32. ref, subdir := getRefAndSubdir(u.Fragment)
  33. fetch := fetchArgs(u, ref)
  34. u.Fragment = ""
  35. // Add origin remote for compatibility with previous implementation that
  36. // used "git clone" and also to make sure local refs are created for branches
  37. if out, err := gitWithinDir(root, "remote", "add", "origin", u.String()); err != nil {
  38. return "", errors.Wrapf(err, "failed add origin repo at %s: %s", u.String(), out)
  39. }
  40. if output, err := gitWithinDir(root, fetch...); err != nil {
  41. return "", errors.Wrapf(err, "error fetching: %s", output)
  42. }
  43. return checkoutGit(root, ref, subdir)
  44. }
  45. func getRefAndSubdir(fragment string) (ref string, subdir string) {
  46. refAndDir := strings.SplitN(fragment, ":", 2)
  47. ref = "master"
  48. if len(refAndDir[0]) != 0 {
  49. ref = refAndDir[0]
  50. }
  51. if len(refAndDir) > 1 && len(refAndDir[1]) != 0 {
  52. subdir = refAndDir[1]
  53. }
  54. return
  55. }
  56. func fetchArgs(remoteURL *url.URL, ref string) []string {
  57. args := []string{"fetch", "--recurse-submodules=yes"}
  58. shallow := true
  59. if strings.HasPrefix(remoteURL.Scheme, "http") {
  60. res, err := http.Head(fmt.Sprintf("%s/info/refs?service=git-upload-pack", remoteURL))
  61. if err != nil || res.Header.Get("Content-Type") != "application/x-git-upload-pack-advertisement" {
  62. shallow = false
  63. }
  64. }
  65. if shallow {
  66. args = append(args, "--depth", "1")
  67. }
  68. return append(args, "origin", ref)
  69. }
  70. func checkoutGit(root, ref, subdir string) (string, error) {
  71. // Try checking out by ref name first. This will work on branches and sets
  72. // .git/HEAD to the current branch name
  73. if output, err := gitWithinDir(root, "checkout", ref); err != nil {
  74. // If checking out by branch name fails check out the last fetched ref
  75. if _, err2 := gitWithinDir(root, "checkout", "FETCH_HEAD"); err2 != nil {
  76. return "", errors.Wrapf(err, "error checking out %s: %s", ref, output)
  77. }
  78. }
  79. if subdir != "" {
  80. newCtx, err := symlink.FollowSymlinkInScope(filepath.Join(root, subdir), root)
  81. if err != nil {
  82. return "", errors.Wrapf(err, "error setting git context, %q not within git root", subdir)
  83. }
  84. fi, err := os.Stat(newCtx)
  85. if err != nil {
  86. return "", err
  87. }
  88. if !fi.IsDir() {
  89. return "", errors.Errorf("error setting git context, not a directory: %s", newCtx)
  90. }
  91. root = newCtx
  92. }
  93. return root, nil
  94. }
  95. func gitWithinDir(dir string, args ...string) ([]byte, error) {
  96. a := []string{"--work-tree", dir, "--git-dir", filepath.Join(dir, ".git")}
  97. return git(append(a, args...)...)
  98. }
  99. func git(args ...string) ([]byte, error) {
  100. return exec.Command("git", args...).CombinedOutput()
  101. }