summaryrefslogtreecommitdiff
path: root/pkg/rtree/index.go
diff options
context:
space:
mode:
authorStefan Majewsky <majewsky@gmx.net>2020-03-28 13:53:10 +0100
committerStefan Majewsky <majewsky@gmx.net>2020-03-28 13:53:10 +0100
commited2b9374599cc00467b1c27121318748cc0d3f49 (patch)
tree65e3200a0b69817b24ae772223d1d852a52458af /pkg/rtree/index.go
parent1ff86053ddef8de3247909434e0bd7000c060f2a (diff)
downloadgofu-ed2b9374599cc00467b1c27121318748cc0d3f49.tar.gz
rename pkg/ to internal/
Diffstat (limited to 'pkg/rtree/index.go')
-rw-r--r--pkg/rtree/index.go431
1 files changed, 0 insertions, 431 deletions
diff --git a/pkg/rtree/index.go b/pkg/rtree/index.go
deleted file mode 100644
index 4e10ffd..0000000
--- a/pkg/rtree/index.go
+++ /dev/null
@@ -1,431 +0,0 @@
-/*******************************************************************************
-*
-* Copyright 2017 Stefan Majewsky <majewsky@gmx.net>
-*
-* This program is free software: you can redistribute it and/or modify it under
-* the terms of the GNU General Public License as published by the Free Software
-* Foundation, either version 3 of the License, or (at your option) any later
-* version.
-*
-* This program is distributed in the hope that it will be useful, but WITHOUT ANY
-* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
-* A PARTICULAR PURPOSE. See the GNU General Public License for more details.
-*
-* You should have received a copy of the GNU General Public License along with
-* this program. If not, see <http://www.gnu.org/licenses/>.
-*
-*******************************************************************************/
-
-package rtree
-
-import (
- "errors"
- "fmt"
- "io/ioutil"
- "os"
- "path"
- "path/filepath"
- "sort"
- "strings"
-
- "github.com/majewsky/gofu/pkg/cli"
-
- yaml "gopkg.in/yaml.v2"
-)
-
-//Index represents the contents of the index file.
-type Index struct {
- Repos []*Repo `yaml:"repos"`
-}
-
-//ReadIndex reads the index file.
-func ReadIndex() (*Index, []error) {
- //read contents of index file
- buf, err := ioutil.ReadFile(IndexPath)
- if err != nil {
- if os.IsNotExist(err) {
- return &Index{Repos: nil}, nil
- }
- return nil, []error{err}
- }
-
- //deserialize YAML
- var index Index
- err = yaml.Unmarshal(buf, &index)
- if err != nil {
- return nil, []error{err}
- }
-
- //validate YAML
- var errs []error
- missing := func(key string, args ...interface{}) {
- errs = append(errs, fmt.Errorf("read %s: missing \"%s\"",
- IndexPath, fmt.Sprintf(key, args...),
- ))
- }
- for idx, repo := range index.Repos {
- if repo.CheckoutPath == "" {
- missing("repos[%d].path", idx)
- }
- if len(repo.Remotes) == 0 {
- missing("repos[%d].remotes", idx)
- }
- for idx2, remote := range repo.Remotes {
- switch {
- case remote.Name == "":
- missing("repos[%d].remotes[%d].name", idx, idx2)
- case remote.URL == "":
- missing("repos[%d].remotes[%d].url", idx, idx2)
- }
- }
- }
-
- sort.Sort(reposByAbsPath(index.Repos))
- return &index, errs
-}
-
-type reposByAbsPath []*Repo
-
-func (r reposByAbsPath) Len() int { return len(r) }
-func (r reposByAbsPath) Less(i, j int) bool { return r[i].AbsolutePath() < r[j].AbsolutePath() }
-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 {
- sort.Sort(reposByAbsPath(i.Repos))
- buf, err := yaml.Marshal(i)
- if err != nil {
- return err
- }
-
- err = os.MkdirAll(filepath.Dir(IndexPath), 0755)
- if err != nil {
- return err
- }
- err = ioutil.WriteFile(IndexPath, buf, 0644)
- if err != nil {
- return err
- }
-
- //perform sanity check (TODO: do this instead when rebuilding the index)
- seen := make(map[string]bool)
- warned := make(map[string]bool)
- for _, repo := range i.Repos {
- if seen[repo.CheckoutPath] && !warned[repo.CheckoutPath] {
- cli.Interface.ShowWarning(
- fmt.Sprintf("repo %s appears multiple times in the index file!", repo.AbsolutePath()),
- )
- warned[repo.CheckoutPath] = true
- }
- seen[repo.CheckoutPath] = true
- }
-
- return nil
-}
-
-//Rebuild implements the `rtree index` subcommand.
-func (i *Index) Rebuild() error {
- //check if existing index entries are still checked out
- var newRepos []*Repo
- for _, repo := range i.Repos {
- gitDirPath := filepath.Join(repo.AbsolutePath(), ".git")
- fi, err := os.Stat(gitDirPath)
- switch {
- case err == nil:
- if fi.IsDir() {
- //everything okay with this repo
- newRepos = append(newRepos, repo)
- continue
- }
- return fmt.Errorf("expected repository at %s, but is not a directory", gitDirPath)
- case !os.IsNotExist(err):
- return err
- }
-
- //repo has been deleted - ask what to do
- var remoteURLs []string
- for _, remote := range repo.Remotes {
- if remote.Name == "origin" {
- remoteURLs = []string{remote.URL}
- break
- }
- remoteURLs = append(remoteURLs, remote.URL)
- }
-
- var selection string
- if len(remoteURLs) == 0 {
- selection, err = cli.Interface.Query(
- fmt.Sprintf("repository %s has been deleted; no remote to restore from", filepath.Join(RootPath, repo.CheckoutPath)),
- cli.Choice{Return: "d", Shortcut: 'd', Text: "delete from index"},
- cli.Choice{Return: "s", Shortcut: 's', Text: "skip"},
- )
- } else {
- selection, err = cli.Interface.Query(
- fmt.Sprintf("repository %s has been deleted", filepath.Join(RootPath, repo.CheckoutPath)),
- cli.Choice{Return: "r", Shortcut: 'r', Text: "restore 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 selection {
- case "r":
- err := repo.Checkout()
- if err != nil {
- return err
- }
- newRepos = append(newRepos, repo)
- case "d":
- continue
- case "s":
- newRepos = append(newRepos, repo)
- }
- }
-
- existingRepos := make(map[string]*Repo)
- for _, repo := range newRepos {
- existingRepos[repo.CheckoutPath] = repo
- }
-
- //index new repos
- err := ForeachPhysicalRepo(func(newRepo Repo) error {
- repo, exists := existingRepos[newRepo.CheckoutPath]
- if exists {
- //update the existing index entry with the new remotes
- repo.Remotes = newRepo.Remotes
- } else {
- newRepos = append(newRepos, &newRepo)
- }
- return nil
- })
- if err != nil {
- return err
- }
-
- i.Repos = newRepos
- return nil
-}
-
-//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(remoteURL string, allowClone bool) (*Repo, error) {
- //make sure that stdout is not used for prompts
- cli.Interface.StdoutProtected = true
-
- expandedRemoteURL := ExpandRemoteURL(remoteURL)
- basename := path.Base(expandedRemoteURL)
-
- //is this remote already checked out directly? also look for repos with the
- //same basename that could be forks
- var candidates []*Repo
- for _, repo := range i.Repos {
- isCandidate := false
- for _, remote := range repo.Remotes {
- otherExpandedRemoteURL := ExpandRemoteURL(remote.URL)
- if expandedRemoteURL == otherExpandedRemoteURL {
- return repo, nil
- }
- if basename == path.Base(otherExpandedRemoteURL) {
- isCandidate = true
- }
- }
- if isCandidate {
- candidates = append(candidates, repo)
- }
- }
-
- //double-check if the repo is already checked out, but we didn't notice it yet
- newRepo, err := NewRepoFromRemoteURL(remoteURL)
- if err != nil {
- return nil, err
- }
- _, err = os.Stat(newRepo.AbsolutePath())
- switch {
- case err == nil:
- return nil, fmt.Errorf(
- "%s already exists (if there is a repo there, try `rtree index`)",
- newRepo.AbsolutePath(),
- )
- case !os.IsNotExist(err):
- return nil, err
- }
-
- if !allowClone {
- return nil, errors.New("no such remote in index (you can validate the index with `rtree index`)")
- }
-
- //if no fork candidates found, clone as new repo
- if len(candidates) == 0 {
- err := newRepo.Checkout()
- if err != nil {
- return nil, err
- }
- i.Repos = append(i.Repos, &newRepo)
- i.Write()
- return &newRepo, nil
- }
-
- //if we found fork candidates, ask the user to match the repo with a fork
- //candidate (or confirm that the repo shall be cloned fresh)
- if len(candidates) > 10 {
- candidates = candidates[:10]
- }
- choices := make([]cli.Choice, len(candidates)+1)
- for idx, repo := range candidates {
- 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(),
- }
- selection, err := cli.Interface.Query("Found possible fork candidates. What to do?", choices...)
- if err != nil {
- return nil, err
- }
-
- if selection == "clone" {
- err := newRepo.Checkout()
- if err != nil {
- return nil, err
- }
- i.Repos = append(i.Repos, &newRepo)
- i.Write()
- return &newRepo, nil
- }
-
- //find the repo selected by the user
- var target *Repo
- for _, repo := range candidates {
- if repo.CheckoutPath == selection {
- target = repo
- break
- }
- }
-
- //report the existing remotes, and ask for the name of the new remote
- prompt := "Existing remotes:\n"
- for _, remote := range target.Remotes {
- prompt += fmt.Sprintf("\t(%s) %s\n", remote.Name, remote.URL)
- }
- prompt += fmt.Sprintf("Enter remote name for %s:", remoteURL)
- remoteName, err := cli.Interface.ReadLine(prompt)
- if err != nil {
- return nil, err
- }
-
- err = cli.Interface.Run(cli.Command{
- Program: []string{"git", "remote", "add", remoteName, remoteURL},
- WorkDir: target.AbsolutePath(),
- })
- if err != nil {
- return nil, err
- }
-
- err = cli.Interface.Run(cli.Command{
- Program: []string{"git", "remote", "update", remoteName},
- WorkDir: target.AbsolutePath(),
- })
- if err != nil {
- return nil, err
- }
-
- target.Remotes = append(target.Remotes, Remote{
- Name: remoteName,
- URL: remoteURL,
- })
- i.Write()
- return target, nil
-}
-
-//ImportRepo moves the given repo into the rtree and adds it to the index.
-func (i *Index) ImportRepo(dirPath string) error {
- //need to make dirPath absolute first
- dirPath, err := filepath.Abs(dirPath)
- if err != nil {
- return err
- }
- repo, err := NewRepoFromAbsolutePath(dirPath)
- if err != nil {
- return err
- }
-
- //repo must be outside $GOPATH/src
- if !strings.HasPrefix(repo.CheckoutPath, "../") {
- return fmt.Errorf("%s is already inside GOPATH", dirPath)
- }
-
- //select the remote which determines the checkout path
- choices := make([]cli.Choice, len(repo.Remotes))
- var checkoutPath string
- for idx, remote := range repo.Remotes {
- thisPath, err := CheckoutPathForRemoteURL(ExpandRemoteURL(remote.URL))
- if err != nil {
- return err
- }
- if remote.Name == "origin" {
- //prefer "origin" over everything else
- checkoutPath = thisPath
- break
- }
- choices[idx] = cli.Choice{Return: thisPath, Text: thisPath}
- }
-
- //cannot decide myself -> let the user select
- if checkoutPath == "" {
- if len(choices) == 0 {
- return errors.New("repo has no remotes")
- }
-
- question := fmt.Sprintf("Repo has multiple remotes. Where to put below %s?", RootPath)
- checkoutPath, err = cli.Interface.Query(question, choices...)
- if err != nil {
- return err
- }
- }
-
- //double-check that there is no such repo in the rtree yet
- for _, other := range i.Repos {
- if other.CheckoutPath == checkoutPath {
- return errors.New("will not overwrite existing checkout at " + other.AbsolutePath())
- }
- }
-
- //do the move
- err = repo.Move(checkoutPath, true)
- if err != nil {
- return err
- }
- i.Repos = append(i.Repos, &repo)
- return nil
-}
-
-//DropRepo deletes the given repo from the rtree and removes it from the index.
-func (i *Index) DropRepo(repo *Repo) error {
- err := repo.Exec("git", "status")
- if err != nil {
- return err
- }
- ok, err := cli.Interface.Confirm(">> Drop this repo?")
- if !ok || err != nil {
- return err
- }
-
- err = os.RemoveAll(repo.AbsolutePath())
- if err != nil {
- return err
- }
-
- reposNew := make([]*Repo, 0, len(i.Repos)-1)
- for _, r := range i.Repos {
- if r.CheckoutPath != repo.CheckoutPath {
- reposNew = append(reposNew, r)
- }
- }
- i.Repos = reposNew
- return i.Write()
-}