github.com/jiasir/docker@v1.3.3-0.20170609024000-252e610103e7/builder/remotecontext/git/gitutils.go (about)

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