func (gd *GenericDownloader) UpdateSubmodules(path string) error {
	cmd := []string{
		"submodule",
		"update",
		"--init",
		"--recursive",
	}
	_, err := executeGitCommand(path, cmd, true)
	if err != nil {
		return err
	}
	return nil
}
Update all submodules in a repo (under path)
package downloader
import (
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"regexp"
	"sort"
	"strconv"
	"strings"
	log "github.com/sirupsen/logrus"
	"mynewt.apache.org/newt/newt/settings"
	"mynewt.apache.org/newt/util"
)
type DownloaderCommitType int
const (
	COMMIT_TYPE_BRANCH DownloaderCommitType = iota
	COMMIT_TYPE_TAG
	COMMIT_TYPE_HASH
)
type Downloader interface {
	FetchFile(commit string, path string, filename string, dstDir string) error
	Clone(commit string, dstPath string) error
	HashFor(path string, commit string) (string, error)
	CommitsFor(path string, commit string) ([]string, error)
	Fetch(path string) error
	Checkout(path string, commit string) error
	UpdateSubmodules(path string) error
	UpdateSubmodule(path string, submodule string) error
	DirtyState(path string) (string, error)
	CommitType(path string, commit string) (DownloaderCommitType, error)
	FixupOrigin(path string) error
	CurrentBranch(path string) (string, error)
	LatestRc(path string, base string) (string, error)
	MainBranch() string
}
type Commit struct {
	hash string
	name string
	typ  DownloaderCommitType
}
type GenericDownloader struct {
	commits map[string]Commit
	head string
	fetched bool
}
type GithubDownloader struct {
	GenericDownloader
	Server string
	User   string
	Repo   string
	Branch string
	Login string
	Password string
	PasswordEnv string
}
type GitDownloader struct {
	GenericDownloader
	Url    string
	Branch string
}
type LocalDownloader struct {
	GenericDownloader
	Path string
}
func gitPath() (string, error) {
	gitPath, err := exec.LookPath("git")
	if err != nil {
		return "", util.NewNewtError(fmt.Sprintf("Can't find git binary: %s\n",
			err.Error()))
	}
	return filepath.ToSlash(gitPath), nil
}
func executeGitCommand(dir string, cmd []string, logCmd bool) ([]byte, error) {
	wd, err := os.Getwd()
	if err != nil {
		return nil, util.NewNewtError(err.Error())
	}
	gp, err := gitPath()
	if err != nil {
		return nil, err
	}
	if err := os.Chdir(dir); err != nil {
		return nil, util.ChildNewtError(err)
	}
	defer os.Chdir(wd)
	gitCmd := []string{gp}
	gitCmd = append(gitCmd, cmd...)
	output, err := util.ShellCommandLimitDbgOutput(gitCmd, nil, logCmd, -1)
	if err != nil {
		return nil, err
	}
	return output, nil
}
func commitExists(repoDir string, commit string) bool {
	cmd := []string{
		"show-ref",
		"--verify",
		"--quiet",
		"refs/heads/" + commit,
	}
	_, err := executeGitCommand(repoDir, cmd, true)
	return err == nil
}
func fixupCommitString(s string) string {
	return strings.TrimPrefix(s, "origin/")
}
func mergeBase(repoDir string, commit string) (string, error) {
	cmd := []string{
		"merge-base",
		commit,
		commit,
	}
	o, err := executeGitCommand(repoDir, cmd, true)
	if err != nil {
		return "", err
	}
	return strings.TrimSpace(string(o)), nil
}
func upstreamFor(path string, commit string) (string, error) {
	cmd := []string{
		"rev-parse",
		"--abbrev-ref",
		"--symbolic-full-name",
		commit + "@{u}",
	}
	up, err := executeGitCommand(path, cmd, true)
	if err != nil {
		if !util.IsExit(err) {
			return "", err
		} else {
			return "", nil
		}
	}
	return strings.TrimSpace(string(up)), nil
}
func getRemoteUrl(path string, remote string) (string, error) {
	cmd := []string{
		"remote",
		"get-url",
		remote,
	}
	o, err := executeGitCommand(path, cmd, true)
	if err != nil {
		return "", err
	}
	return strings.TrimSpace(string(o)), nil
}
func setRemoteUrlCmd(remote string, url string) []string {
	return []string{
		"remote",
		"set-url",
		remote,
		url,
	}
}
func setRemoteUrl(path string, remote string, url string, logCmd bool) error {
	cmd := setRemoteUrlCmd(remote, url)
	_, err := executeGitCommand(path, cmd, logCmd)
	return err
}
func warnWrongOriginUrl(path string, curUrl string, goodUrl string) {
	util.OneTimeWarning(
		"Repo's \"origin\" remote points to unexpected URL: "+
			"%s; correcting it to %s.  Repo contents may be incorrect.",
		curUrl, goodUrl)
}
func getCommits(path string) (map[string]Commit, error) {
	cmd := []string{"show-ref", "--dereference"}
	o, err := executeGitCommand(path, cmd, true)
	if err != nil {
		return nil, err
	}
	m := map[string]Commit{}
	lines := strings.Split(strings.TrimSpace(string(o)), "\n")
	for _, line := range lines {
		f := strings.Fields(line)
		if len(f) != 2 {
			return nil, util.FmtNewtError(
				"git show-ref produced unexpected line: \"%s\"", line)
		}
		hash := f[0]
		ref := strings.TrimSuffix(f[1], "^{}")
		c := Commit{
			hash: hash,
		}
		if n := strings.TrimPrefix(ref, "refs/remotes/origin/"); n != ref {
			c.typ = COMMIT_TYPE_BRANCH
			c.name = n
		} else if n := strings.TrimPrefix(ref, "refs/tags/"); n != ref {
			c.typ = COMMIT_TYPE_TAG
			c.name = n
		}
		if c.name != "" {
			m[c.name] = c
		}
	}
	return m, nil
}
func urlsEquivalent(a string, b string) bool {
	a = strings.TrimSuffix(a, ".git")
	b = strings.TrimSuffix(b, ".git")
	if a == b {
		return true
	}
	gitRE := regexp.MustCompile(`git@([^:]+):(.*)`)
	parseGit := func(s string) string {
		groups := gitRE.FindStringSubmatch(s)
		if len(groups) != 3 {
			return ""
		}
		return groups[1] + "/" + groups[2]
	}
	parseHttps := func(s string) string {
		if !strings.HasPrefix(s, "https://") {
			return ""
		}
		return strings.TrimPrefix(s, "https://")
	}
	var git string
	var https string
	git = parseGit(a)
	if git == "" {
		git = parseGit(b)
		https = parseHttps(a)
	} else {
		https = parseHttps(b)
	}
	return git != "" && git == https
}
func (gd *GenericDownloader) init(path string) error {
	cmap, err := getCommits(path)
	if err != nil {
		return err
	}
	gd.commits = cmap
	cmd := []string{"rev-parse", "HEAD"}
	o, err := executeGitCommand(path, cmd, true)
	if err != nil {
		return err
	}
	gd.head = strings.TrimSpace(string(o))
	return nil
}
func (gd *GenericDownloader) ensureInited(path string) error {
	if gd.commits != nil {
		return nil
	}
	return gd.init(path)
}
func untrackedFilesFromCheckoutErr(err error) []string {
	var files []string
	text := err.Error()
	lines := strings.Split(text, "\n")
	collecting := false
	for _, line := range lines {
		if !collecting {
			if strings.Contains(line,
				"The following untracked working tree files would "+
					"be overwritten by checkout:") {
				collecting = true
			}
		} else {
			if strings.Contains(line, "Please move or remove them before") {
				collecting = false
			} else {
				files = append(files, strings.TrimSpace(line))
			}
		}
	}
	return files
}
func (gd *GenericDownloader) Checkout(repoDir string, commit string) error {
	hash, err := gd.HashFor(repoDir, commit)
	if err != nil {
		return err
	}
	util.StatusMessage(util.VERBOSITY_VERBOSE, "Will checkout %s\n", hash)
	cmd := []string{
		"checkout",
		hash,
	}
	_, err = executeGitCommand(repoDir, cmd, true)
	return err
}
func (gd *GenericDownloader) UpdateSubmodule(path string, submodule string) error {
	cmd := []string{
		"submodule",
		"update",
		"--init",
		"--recursive",
		submodule,
	}
	_, err := executeGitCommand(path, cmd, true)
	if err != nil {
		return err
	}
	return nil
}