func rootOf(pkg string) string {
	pkgParts := strings.Split(pkg, "/")
	return path.Join(pkgParts[:3]...)
}
rootOf extracts the path up to the github repo
package main
import (
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"path"
	"path/filepath"
	"strings"
)
const (
	GitDir    = ".git"
	GitIgnore = ".gitignore"
	GostFile  = ".gost"
	SetEnv    = "setenv.bash"
)
var (
	GOPATH string
	dir = ""
)
func main() {
	log.SetFlags(0)
	if len(os.Args) < 2 {
		failAndUsage("Please specify a command")
	}
	cmd := strings.ToLower(os.Args[1])
	switch cmd {
	case "init":
		doinit()
	case "get":
		get()
	case "push":
		push()
	default:
		failAndUsage("Unknown command: %s", cmd)
	}
}
func doinit() {
	var err error
	dir, err = os.Getwd()
	if err != nil {
		log.Fatalf("Unable to determine current directory: %s", err)
	}
	if exists(GitDir) {
		log.Fatalf("%s already contains a .git folder, can't initialize gost", dir)
	}
	run("git", "init")
	writeAndCommit(GitIgnore, DefaultGitIgnore)
	writeAndCommit(GostFile, DefaultGostFile)
	writeAndCommit(SetEnv, SetEnvFile)
	log.Print("Initialized git repo, please update your GOPATH and PATH. setenv.bash does this for you.")
	log.Print("  source ./setenv.bash")
}
func get() {
	requireGostGOPATH()
	requireCleanWorkingDirectory()
	flags := flag.NewFlagSet("get", flag.ExitOnError)
	update := flags.Bool("u", false, "update existing from remote, including recusively updating dependencies")
	updatePackage := flags.Bool("up", false, "update only the named package, fetch missing dependencies but leave existing dependencies alone")
	flags.Parse(os.Args[2:])
	pkg, branch := pkgAndBranch(flags.Args())
	shouldUpdate := *update || *updatePackage
	shouldUpdateDeps := *update
	fetchSubtree(pkg, branch, shouldUpdate, shouldUpdateDeps, map[string]bool{})
	removeGitFolders()
	run("git", "add", "src")
	run("git", "commit", "-m", fmt.Sprintf("[gost] Added %s and its dependencies", pkg))
	run("go", "install", pkg)
}
func push() {
	requireGostGOPATH()
	flags := flag.NewFlagSet("push", flag.ExitOnError)
	updateFirst := flags.Bool("u", false, "update existing from remote before pushing")
	flags.Parse(os.Args[2:])
	pkg, branch := pkgAndBranch(flags.Args())
	parts := strings.Split(strings.Trim(pkg, "/"), "/")
	if len(parts) > 2 {
		log.Printf("Pushing single package %s", pkg)
		err := doPush(pkg, branch, *updateFirst)
		if err != nil {
			log.Fatalf("Unable to push package %s: %s", pkg, err)
		}
	} else {
		log.Printf("Pushing all subpackages of %s", pkg)
		entries, err := ioutil.ReadDir(path.Join(GOPATH, "src", pkg))
		if err != nil {
			log.Fatalf("Unable to list subpackages of %s", pkg)
		}
		for _, entry := range entries {
			if entry.IsDir() {
				_, dir := path.Split(entry.Name())
				fullPkg := path.Join(pkg, dir)
				err := doPush(fullPkg, branch, *updateFirst)
				if err != nil {
					log.Printf("Unable to push package %s: %s", fullPkg, err)
				}
			}
		}
	}
}
func doPush(pkg string, branch string, updateFirst bool) error {
	pkgRoot := rootOf(pkg)
	srcPath := path.Join("src", pkgRoot)
	rpath := repopath(pkgRoot)
	if updateFirst {
		prefix := path.Join("src", pkgRoot)
		log.Printf("Updating %s before pushing", pkgRoot)
		_, err := doRun("git", "subtree", "pull", "--squash",
			"--prefix", prefix, rpath, branch)
		if err != nil {
			return err
		}
	}
	_, err := doRun("git", "subtree", "push", "--prefix", srcPath, rpath, branch)
	return err
}
func pkgAndBranch(args []string) (string, string) {
	if len(args) < 2 {
		log.Fatal("Please specify a package and a branch")
	}
	pkg := args[0]
	if !supportsSubtrees(pkg) {
		log.Fatal("gost only supports pushing packages to github.com or bitbucket.org")
	}
	branch := args[1]
	log.Printf("Using branch %s", branch)
	return pkg, branch
}
func fetchSubtree(pkg string, branch string, update bool, updateDeps bool, alreadyFetched map[string]bool) {
	pkgRoot := rootOf(pkg)
	if alreadyFetched[pkgRoot] {
		return
	}
	prefix := path.Join("src", pkgRoot)
	if exists(prefix) {
		if update {
			run("git", "subtree", "pull", "--squash",
				"--prefix", prefix,
				repopath(pkgRoot),
				branch)
		} else {
			log.Printf("%s already exists, declining to add as subtree", prefix)
		}
	} else {
		run("git", "subtree", "add", "--squash",
			"--prefix", prefix,
			repopath(pkgRoot),
			branch)
	}
	alreadyFetched[pkgRoot] = true
	fetchDeps(pkg, "master", updateDeps, alreadyFetched)
}
func fetchDeps(pkg string, branch string, updateDeps bool, alreadyFetched map[string]bool) {
	depsString := run("go", "list", "-f", "{{range .Deps}}{{.}} {{end}} {{range .TestImports}}{{.}} {{end}}", pkg)
	deps := parseDeps(depsString)
	nonGithubDeps := []string{}
	for _, dep := range deps {
		dep = strings.TrimSpace(dep)
		if dep == "" || dep == "." {
			continue
		}
		if supportsSubtrees(dep) {
			fetchSubtree(dep, branch, updateDeps, updateDeps, alreadyFetched)
		} else {
			nonGithubDeps = append(nonGithubDeps, dep)
		}
	}
	for _, dep := range nonGithubDeps {
		goGet(dep, updateDeps, alreadyFetched)
	}
}
func goGet(pkg string, update bool, alreadyFetched map[string]bool) {
	isRelative := strings.HasPrefix(pkg, ".")
	if alreadyFetched[pkg] || isRelative {
		return
	}
	run("go", "get", pkg)
	alreadyFetched[pkg] = true
}
func writeAndCommit(file string, content string) {
	if exists(file) {
		log.Fatalf("%s already contains %s, can't initialize gost", dir, file)
	}
	err := ioutil.WriteFile(file, []byte(content), 0644)
	if err != nil {
		log.Fatalf("Unable to write %s: %s", file, err)
	}
	run("git", "add", file)
	run("git", "commit", file, "-m", "[gost] Initialized "+file)
	log.Printf("Initialized and commited %s", file)
}
func requireGostGOPATH() {
	GOPATH = os.Getenv("GOPATH")
	if GOPATH == "" {
		log.Fatal("Please set your GOPATH")
	}
	requireFileInGOPATH(GostFile)
	requireFileInGOPATH(GitDir)
}
func requireCleanWorkingDirectory() {
	status := run("git", "status", "--porcelain")
	if status != "" {
		log.Fatal("Working tree dirty or untracked files present. Please clean up before running gost get.")
	}
}
func requireFileInGOPATH(file string) {
	if !exists(path.Join(GOPATH, file)) {
		log.Fatalf("Unable to find '%s' in the GOPATH '%s', please make sure you've run gost init within your GOPATH.", file, GOPATH)
	}
}
func exists(file string) bool {
	_, err := os.Stat(file)
	return err == nil
}
func supportsSubtrees(pkg string) bool {
	return isGitHub(pkg) || isBitBucket(pkg)
}