diff options
| author | Stefan Majewsky <majewsky@gmx.net> | 2017-05-07 22:46:38 +0200 |
|---|---|---|
| committer | Stefan Majewsky <majewsky@gmx.net> | 2017-05-07 23:02:08 +0200 |
| commit | 36b903423828b2e35c9c1fde3fdc898fe2ed23ca (patch) | |
| tree | 22770bbade8c8e20a3b6f4e9f5a91ed9d5580f89 /pkg/rtree | |
| parent | 385645d1a0bb9ae618e92d2e7a2ea6fe256b5ba2 (diff) | |
| download | gofu-36b903423828b2e35c9c1fde3fdc898fe2ed23ca.tar.gz | |
refactor towards new cli.Interface
Diffstat (limited to 'pkg/rtree')
| -rw-r--r-- | pkg/rtree/index.go | 146 | ||||
| -rw-r--r-- | pkg/rtree/main.go | 114 | ||||
| -rw-r--r-- | pkg/rtree/repo.go | 77 |
3 files changed, 169 insertions, 168 deletions
diff --git a/pkg/rtree/index.go b/pkg/rtree/index.go index dd1b979..c2c0202 100644 --- a/pkg/rtree/index.go +++ b/pkg/rtree/index.go @@ -23,14 +23,12 @@ import ( "fmt" "io/ioutil" "os" - "os/exec" "path" "path/filepath" "sort" "strings" "github.com/majewsky/gofu/pkg/cli" - "github.com/majewsky/gofu/pkg/util" yaml "gopkg.in/yaml.v2" ) @@ -102,7 +100,7 @@ func (r reposByAbsPath) Less(i, j int) bool { return r[i].AbsolutePath() < r[j]. func (r reposByAbsPath) Swap(i, j int) { r[i], r[j] = r[j], r[i] } //Write writes the index file to disk. -func (i *Index) Write() error { +func (i *Index) Write(ci *cli.Interface) error { buf, err := yaml.Marshal(i) if err != nil { return err @@ -123,7 +121,9 @@ func (i *Index) Write() error { warned := make(map[string]bool) for _, repo := range i.Repos { if seen[repo.CheckoutPath] && !warned[repo.CheckoutPath] { - fmt.Fprintf(os.Stderr, "warning: repo %s appears multiple times in the index file!\n", repo.AbsolutePath()) + ci.ShowWarning( + fmt.Sprintf("repo %s appears multiple times in the index file!", repo.AbsolutePath()), + ) warned[repo.CheckoutPath] = true } seen[repo.CheckoutPath] = true @@ -132,8 +132,8 @@ func (i *Index) Write() error { return nil } -//InteractiveRebuild implements the `rtree index` subcommand. -func (i *Index) InteractiveRebuild() error { +//Rebuild implements the `rtree index` subcommand. +func (i *Index) Rebuild(ci *cli.Interface) error { //check if existing index entries are still checked out var newRepos []*Repo for _, repo := range i.Repos { @@ -161,32 +161,35 @@ func (i *Index) InteractiveRebuild() error { remoteURLs = append(remoteURLs, remote.URL) } - var choice cli.Choice + var selection string if len(remoteURLs) == 0 { - choice, _ = cli.Query( + selection, err = ci.Query( fmt.Sprintf("repository %s has been deleted; no remote to restore from", filepath.Join(RootPath, repo.CheckoutPath)), - cli.Choice{Shortcut: 'd', Text: "delete from index"}, - cli.Choice{Shortcut: 's', Text: "skip"}, + cli.Choice{Return: "d", Shortcut: 'd', Text: "delete from index"}, + cli.Choice{Return: "s", Shortcut: 's', Text: "skip"}, ) } else { - choice, _ = cli.Query( + selection, err = ci.Query( fmt.Sprintf("repository %s has been deleted", filepath.Join(RootPath, repo.CheckoutPath)), - cli.Choice{Shortcut: 'r', Text: "(r)estore from " + strings.Join(remoteURLs, " and ")}, - cli.Choice{Shortcut: 'd', Text: "delete from index"}, - cli.Choice{Shortcut: 's', Text: "skip"}, + cli.Choice{Return: "r", Shortcut: 'r', Text: "(r)estore from " + strings.Join(remoteURLs, " and ")}, + cli.Choice{Return: "d", Shortcut: 'd', Text: "delete from index"}, + cli.Choice{Return: "s", Shortcut: 's', Text: "skip"}, ) } + if err != nil { + return err + } - switch choice.Shortcut { - case 'r': - err := repo.Checkout() + switch selection { + case "r": + err := repo.Checkout(ci) if err != nil { return err } newRepos = append(newRepos, repo) - case 'd': + case "d": continue - case 's': + case "s": newRepos = append(newRepos, repo) } } @@ -197,7 +200,7 @@ func (i *Index) InteractiveRebuild() error { } //index new repos - err := ForeachPhysicalRepo(func(newRepo Repo) error { + err := ForeachPhysicalRepo(ci, func(newRepo Repo) error { repo, exists := existingRepos[newRepo.CheckoutPath] if exists { //update the existing index entry with the new remotes @@ -215,12 +218,10 @@ func (i *Index) InteractiveRebuild() error { return nil } -var tenLetters = []string{"a", "b", "c", "d", "e", "f", "g", "h", "i", "j"} - -//InteractiveFindRepo locates the repo with the given remote if it exists on -//disk or (if allowClone is set) clones it and adds it to the index. This -//is the meat of `rtree get`, and is also used by `rtree drop`. -func (i *Index) InteractiveFindRepo(remoteURL string, allowClone bool) (*Repo, error) { +//FindRepo locates the repo with the given remote if it exists on disk or (if +//allowClone is set) clones it and adds it to the index. This is the meat of +//`rtree get`, and is also used by `rtree drop`. +func (i *Index) FindRepo(ci *cli.Interface, remoteURL string, allowClone bool) (*Repo, error) { //make sure that stdout is not used for prompts originalStdout := os.Stdout os.Stdout = os.Stderr @@ -255,7 +256,7 @@ func (i *Index) InteractiveFindRepo(remoteURL string, allowClone bool) (*Repo, e if err != nil { return nil, err } - fi, err := os.Stat(newRepo.AbsolutePath()) + _, err = os.Stat(newRepo.AbsolutePath()) switch { case err == nil: return nil, fmt.Errorf( @@ -272,12 +273,12 @@ func (i *Index) InteractiveFindRepo(remoteURL string, allowClone bool) (*Repo, e //if no fork candidates found, clone as new repo if len(candidates) == 0 { - err := newRepo.Checkout() + err := newRepo.Checkout(ci) if err != nil { return nil, err } i.Repos = append(i.Repos, &newRepo) - i.Write() + i.Write(ci) return &newRepo, nil } @@ -288,49 +289,60 @@ func (i *Index) InteractiveFindRepo(remoteURL string, allowClone bool) (*Repo, e } choices := make([]cli.Choice, len(candidates)+1) for idx, repo := range candidates { - choices[idx] = cli.Choice{Text: "add as remote to " + repo.AbsolutePath()} + choices[idx] = cli.Choice{Text: "add as remote to " + repo.AbsolutePath(), Return: repo.CheckoutPath} } choices[len(candidates)] = cli.Choice{ + Return: "clone", Shortcut: 'n', Text: "clone to " + newRepo.AbsolutePath(), } - choice, choiceIdx := cli.Query("Found possible fork candidates. What to do?", choices...) + selection, err := ci.Query("Found possible fork candidates. What to do?", choices...) + if err != nil { + return nil, err + } - if choice.Shortcut == 'n' { - err := newRepo.Checkout() + if selection == "clone" { + err := newRepo.Checkout(ci) if err != nil { return nil, err } i.Repos = append(i.Repos, &newRepo) - i.Write() + i.Write(ci) return &newRepo, nil } //find the repo selected by the user - target := candidates[choiceIdx] + var target *Repo + for _, repo := range candidates { + if target.CheckoutPath == selection { + target = repo + break + } + } //report the existing remotes, and ask for the name of the new remote - fmt.Println("Existing remotes:") + prompt := "Existing remotes:\n" for _, remote := range target.Remotes { - fmt.Printf("\t(%s) %s\n", remote.Name, remote.URL) + prompt += fmt.Sprintf("\t(%s) %s\n", remote.Name, remote.URL) + } + prompt += fmt.Sprintf("Enter remote name for %s:", remoteURL) + remoteName, err := ci.ReadLine(prompt) + if err != nil { + return nil, err } - fmt.Printf("Enter remote name for %s: ", remoteURL) - remoteName := util.ReadLine() - cmd := exec.Command("git", "remote", "add", remoteName, remoteURL) - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - cmd.Dir = target.AbsolutePath() - err = cmd.Run() + err = ci.Run(cli.Command{ + Program: []string{"git", "remote", "add", remoteName, remoteURL}, + WorkDir: target.AbsolutePath(), + }) if err != nil { return nil, err } - cmd = exec.Command("git", "remote", "update", remoteName) - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - cmd.Dir = target.AbsolutePath() - err = cmd.Run() + err = ci.Run(cli.Command{ + Program: []string{"git", "remote", "update", remoteName}, + WorkDir: target.AbsolutePath(), + }) if err != nil { return nil, err } @@ -339,18 +351,18 @@ func (i *Index) InteractiveFindRepo(remoteURL string, allowClone bool) (*Repo, e Name: remoteName, URL: remoteURL, }) - i.Write() + i.Write(ci) return target, nil } -//InteractiveImportRepo moves the given repo into the rtree and adds it to the index. -func (i *Index) InteractiveImportRepo(dirPath string) error { +//ImportRepo moves the given repo into the rtree and adds it to the index. +func (i *Index) ImportRepo(ci *cli.Interface, dirPath string) error { //need to make dirPath absolute first dirPath, err := filepath.Abs(dirPath) if err != nil { return err } - repo, err := NewRepoFromAbsolutePath(dirPath) + repo, err := NewRepoFromAbsolutePath(ci, dirPath) if err != nil { return err } @@ -373,7 +385,7 @@ func (i *Index) InteractiveImportRepo(dirPath string) error { checkoutPath = thisPath break } - choices[idx] = cli.Choice{Text: thisPath} + choices[idx] = cli.Choice{Return: thisPath, Text: thisPath} } //cannot decide myself -> let the user select @@ -383,8 +395,10 @@ func (i *Index) InteractiveImportRepo(dirPath string) error { } question := fmt.Sprintf("Repo has multiple remotes. Where to put below %s?", RootPath) - choice, _ := cli.Query(question, choices...) - checkoutPath = choice.Text + checkoutPath, err = ci.Query(question, choices...) + if err != nil { + return err + } } //double-check that there is no such repo in the rtree yet @@ -403,18 +417,18 @@ func (i *Index) InteractiveImportRepo(dirPath string) error { return nil } -//InteractiveDropRepo deletes the given repo from the rtree and removes it from -//the index. -func (i *Index) InteractiveDropRepo(repo *Repo) error { - ok := repo.InteractiveExec("git", "status") - if !ok { - return nil +//DropRepo deletes the given repo from the rtree and removes it from the index. +func (i *Index) DropRepo(ci *cli.Interface, repo *Repo) error { + err := repo.Exec(ci, "git", "status") + if err != nil { + return err } - if !cli.Confirm(">> Drop this repo?") { - return nil + ok, err := ci.Confirm(">> Drop this repo?") + if !ok || err != nil { + return err } - err := os.RemoveAll(repo.AbsolutePath()) + err = os.RemoveAll(repo.AbsolutePath()) if err != nil { return err } @@ -426,5 +440,5 @@ func (i *Index) InteractiveDropRepo(repo *Repo) error { } } i.Repos = reposNew - return nil + return i.Write(ci) } diff --git a/pkg/rtree/main.go b/pkg/rtree/main.go index d57b399..42e540e 100644 --- a/pkg/rtree/main.go +++ b/pkg/rtree/main.go @@ -19,150 +19,146 @@ package rtree import ( - "fmt" - "os" + "strings" - "github.com/majewsky/gofu/pkg/util" + "github.com/majewsky/gofu/pkg/cli" ) //Exec executes the rtree applet and does not return. The argument is os.Args //minus the leading "rtree" or "gofu rtree". -func Exec(args []string) int { +func Exec(ci *cli.Interface, args []string) int { if len(args) == 0 { - return usage() + return usage(ci) } index, errs := ReadIndex() if len(errs) > 0 { for _, err := range errs { - util.ShowError(err) + ci.ShowError(err.Error()) } - return 255 + return 1 } var err error switch args[0] { case "get": if len(args) != 2 { - return usage() + return usage(ci) } - err = commandGet(index, args[1]) + err = commandGet(ci, index, args[1]) case "drop": if len(args) != 2 { - return usage() + return usage(ci) } - err = commandDrop(index, args[1]) + err = commandDrop(ci, index, args[1]) case "index": if len(args) != 1 { - return usage() + return usage(ci) } - err = commandIndex(index) + err = commandIndex(ci, index) case "repos": if len(args) != 1 { - return usage() + return usage(ci) } - commandRepos(index) + commandRepos(ci, index) case "remotes": if len(args) != 1 { - return usage() + return usage(ci) } - commandRemotes(index) + commandRemotes(ci, index) case "import": if len(args) != 2 { - return usage() + return usage(ci) } - err = commandImport(index, args[1]) + err = commandImport(ci, index, args[1]) case "each": if len(args) < 2 { - return usage() + return usage(ci) } - return commandEach(index, args[1], args[2:]) + return commandEach(ci, index, args[1:]) default: - return usage() + return usage(ci) } if err == nil { return 0 } - util.ShowError(err) + ci.ShowError(err.Error()) return 1 } -func usage() int { - fmt.Fprintln(os.Stderr, "Usage:") - fmt.Fprintln(os.Stderr, " rtree [get|drop] <url>") - fmt.Fprintln(os.Stderr, " rtree [index|repos|remotes]") - fmt.Fprintln(os.Stderr, " rtree import <path>") - fmt.Fprintln(os.Stderr, " rtree each <command>") +var usageStr = strings.TrimSpace(` +Usage: + rtree [get|drop] <url> + rtree [index|repos|remotes] + rtree import <path> + rtree each <command> +`) + +func usage(ci *cli.Interface) int { + ci.ShowUsage(usageStr) return 1 } -func commandGet(index *Index, url string) error { - repo, err := index.InteractiveFindRepo(url, true) +func commandGet(ci *cli.Interface, index *Index, url string) error { + repo, err := index.FindRepo(ci, url, true) if err != nil { return err } - fmt.Println(repo.AbsolutePath()) + ci.ShowResult(repo.AbsolutePath()) return nil } -func commandDrop(index *Index, url string) error { - repo, err := index.InteractiveFindRepo(url, true) - if err != nil { - return err - } - err = index.InteractiveDropRepo(repo) +func commandDrop(ci *cli.Interface, index *Index, url string) error { + repo, err := index.FindRepo(ci, url, true) if err != nil { return err } - return index.Write() + return index.DropRepo(ci, repo) } -func commandIndex(index *Index) error { - err := index.InteractiveRebuild() +func commandIndex(ci *cli.Interface, index *Index) error { + err := index.Rebuild(ci) if err != nil { return err } - return index.Write() + return index.Write(ci) } -func commandRepos(index *Index) { +func commandRepos(ci *cli.Interface, index *Index) { var items []string for _, repo := range index.Repos { items = append(items, repo.CheckoutPath) } - util.ShowSorted(items) + ci.ShowResultsSorted(items) } -func commandRemotes(index *Index) { +func commandRemotes(ci *cli.Interface, index *Index) { var items []string for _, repo := range index.Repos { for _, remote := range repo.Remotes { items = append(items, remote.URL) } } - util.ShowSorted(items) + ci.ShowResultsSorted(items) } -func commandEach(index *Index, command string, args []string) int { - allOK := true +func commandEach(ci *cli.Interface, index *Index, cmdline []string) (exitCode int) { + exitCode = 0 for _, repo := range index.Repos { - ok := repo.InteractiveExec(command, args...) - if !ok { - allOK = false + err := repo.Exec(ci, cmdline...) + if err != nil { + ci.ShowError(err.Error()) + exitCode = 1 } } - - if allOK { - return 0 - } - return 1 + return } -func commandImport(index *Index, dirPath string) error { - err := index.InteractiveImportRepo(dirPath) +func commandImport(ci *cli.Interface, index *Index, dirPath string) error { + err := index.ImportRepo(ci, dirPath) if err != nil { return err } - return index.Write() + return index.Write(ci) } diff --git a/pkg/rtree/repo.go b/pkg/rtree/repo.go index 12afee0..fba658b 100644 --- a/pkg/rtree/repo.go +++ b/pkg/rtree/repo.go @@ -19,14 +19,14 @@ package rtree import ( - "bytes" "errors" "fmt" "os" - "os/exec" "path/filepath" "regexp" "strings" + + "github.com/majewsky/gofu/pkg/cli" ) //RootPath is the directory below which all repositories are located. Its value @@ -64,23 +64,22 @@ func (r Repo) AbsolutePath() string { //NewRepoFromAbsolutePath initializes a Repo instance by scanning the existing //checkout at the given path. -func NewRepoFromAbsolutePath(path string) (repo Repo, err error) { +func NewRepoFromAbsolutePath(ci *cli.Interface, path string) (repo Repo, err error) { repo.CheckoutPath, err = filepath.Rel(RootPath, path) if err != nil { return } //list remotes - cmd := exec.Command("git", "-C", path, "config", "-l") - var buf bytes.Buffer - cmd.Stdout = &buf - cmd.Stderr = os.Stderr - err = cmd.Run() + out, err := ci.CaptureStdout(cli.Command{ + Program: []string{"git", "config", "-l"}, + WorkDir: path, + }) if err != nil { - return repo, fmt.Errorf("exec `git config -l` in %s: %s", path, err.Error()) + return } - for _, line := range strings.Split(string(buf.Bytes()), "\n") { + for _, line := range strings.Split(out, "\n") { match := remoteConfigRx.FindStringSubmatch(line) if match == nil { continue @@ -113,7 +112,7 @@ var remoteConfigRx = regexp.MustCompile(`remote\.([^=]+)\.url=(.+)`) //ForeachPhysicalRepo walks over the repository tree, executing the action //function once for every repo encountered (but *not* for repos contained //within other repos, e.g. submodules). -func ForeachPhysicalRepo(action func(repo Repo) error) error { +func ForeachPhysicalRepo(ci *cli.Interface, action func(repo Repo) error) error { return filepath.Walk(RootPath, func(path string, info os.FileInfo, err error) error { if err != nil { return err @@ -128,7 +127,7 @@ func ForeachPhysicalRepo(action func(repo Repo) error) error { } //appears to be a repo - repo, err := NewRepoFromAbsolutePath(path) + repo, err := NewRepoFromAbsolutePath(ci, path) if err == nil { err = action(repo) } @@ -142,7 +141,7 @@ func ForeachPhysicalRepo(action func(repo Repo) error) error { //Checkout creates the repo in the given path with the given remotes. The //working copy will only be initialized if there is an "origin" remote. -func (r Repo) Checkout() error { +func (r Repo) Checkout(ci *cli.Interface) error { //check if we have an "origin" remote to clone from var originURL string for _, remote := range r.Remotes { @@ -153,19 +152,17 @@ func (r Repo) Checkout() error { } if originURL == "" { - cmd := exec.Command("git", "init", r.AbsolutePath()) - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - err := cmd.Run() + err := ci.Run(cli.Command{ + Program: []string{"git", "init", r.AbsolutePath()}, + }) if err != nil { return err } - fmt.Fprintln(os.Stderr, "warning: will not checkout anything since there is no remote named \"origin\"") + ci.ShowWarning(`will not checkout anything since there is no remote named "origin"`) } else { - cmd := exec.Command("git", "clone", originURL, r.AbsolutePath()) - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - err := cmd.Run() + err := ci.Run(cli.Command{ + Program: []string{"git", "clone", originURL, r.AbsolutePath()}, + }) if err != nil { return err } @@ -174,10 +171,10 @@ func (r Repo) Checkout() error { remotesAdded := false for _, remote := range r.Remotes { if remote.Name != "origin" { - cmd := exec.Command("git", "-C", r.AbsolutePath(), "remote", "add", remote.Name, remote.URL) - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - err := cmd.Run() + err := ci.Run(cli.Command{ + Program: []string{"git", "remote", "add", remote.Name, remote.URL}, + WorkDir: r.AbsolutePath(), + }) if err != nil { return err } @@ -185,29 +182,23 @@ func (r Repo) Checkout() error { } } if remotesAdded { - cmd := exec.Command("git", "-C", r.AbsolutePath(), "remote", "update") - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - return cmd.Run() + return ci.Run(cli.Command{ + Program: []string{"git", "remote", "update"}, + WorkDir: r.AbsolutePath(), + }) } return nil } -//InteractiveExec implements the meat of the `rtree exec` command. It returns +//Exec implements the meat of the `rtree exec` command. It returns //true iff the command exited successfully. -func (r Repo) InteractiveExec(command string, args ...string) (ok bool) { - fmt.Fprintf(os.Stdout, "\x1B[1;36m>> \x1B[0;36m%s\x1B[0m\n", r.AbsolutePath()) - cmd := exec.Command(command, args...) - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - cmd.Dir = r.AbsolutePath() - err := cmd.Run() - if err != nil { - fmt.Fprintf(os.Stderr, "\x1B[1;31m!! \x1B[0;31m%s\x1B[0m\n", err.Error()) - return false - } - return true +func (r Repo) Exec(ci *cli.Interface, cmdline ...string) error { + ci.ShowProgress(r.AbsolutePath()) + return ci.Run(cli.Command{ + Program: cmdline, + WorkDir: r.AbsolutePath(), + }) } //Move sets the CheckoutPath to the given value and moves the existing repo |
