aboutsummaryrefslogtreecommitdiff
path: root/pkg/rtree/index.go
diff options
context:
space:
mode:
authorStefan Majewsky <majewsky@gmx.net>2017-05-02 21:22:20 +0200
committerStefan Majewsky <majewsky@gmx.net>2017-05-02 21:22:20 +0200
commit72733e8203654cd26464c8c5a6c955b025102cca (patch)
tree4a8c312fb25dcf79f2cd61b88b7f2ff6baf8d996 /pkg/rtree/index.go
parent6b382c55ed7885857a6232a0c8b086ab1f64985e (diff)
downloadgofu-72733e8203654cd26464c8c5a6c955b025102cca.tar.gz
restructure rtree into gofu (with rtree as the first applet)
Diffstat (limited to 'pkg/rtree/index.go')
-rw-r--r--pkg/rtree/index.go187
1 files changed, 187 insertions, 0 deletions
diff --git a/pkg/rtree/index.go b/pkg/rtree/index.go
new file mode 100644
index 0000000..a4a0b74
--- /dev/null
+++ b/pkg/rtree/index.go
@@ -0,0 +1,187 @@
+/*******************************************************************************
+*
+* 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/filepath"
+ "sort"
+ "strings"
+
+ "github.com/majewsky/gofu/pkg/util"
+
+ yaml "gopkg.in/yaml.v2"
+)
+
+//Index represents the contents of the index file.
+type Index struct {
+ Repos []*Repo `yaml:"repos"`
+}
+
+func indexPath() string {
+ homeDir := os.Getenv("HOME")
+ if homeDir == "" {
+ util.FatalIfError(errors.New("$HOME is not set (rtree needs the HOME variable to locate its index file)"))
+ }
+ return filepath.Join(homeDir, ".rtree/index.yaml")
+}
+
+//ReadIndex reads the index file.
+func ReadIndex() *Index {
+ //read contents of index file
+ path := indexPath()
+ buf, err := ioutil.ReadFile(path)
+ if err != nil {
+ if os.IsNotExist(err) {
+ return &Index{Repos: nil}
+ }
+ util.FatalIfError(err)
+ }
+
+ //deserialize YAML
+ var index Index
+ util.FatalIfError(yaml.Unmarshal(buf, &index))
+
+ //validate YAML
+ valid := true
+ for idx, repo := range index.Repos {
+ if repo.CheckoutPath == "" {
+ util.ShowError(fmt.Errorf("missing \"repos[%d].path\"", idx))
+ valid = false
+ }
+ if len(repo.Remotes) == 0 {
+ util.ShowError(fmt.Errorf("missing \"repos[%d].remotes\"", idx))
+ valid = false
+ }
+ for idx2, remote := range repo.Remotes {
+ switch {
+ case remote.Name == "":
+ util.ShowError(fmt.Errorf("missing \"repos[%d].remotes[%d].name\"", idx, idx2))
+ valid = false
+ case remote.URL == "":
+ util.ShowError(fmt.Errorf("missing \"repos[%d].remotes[%d].url\"", idx, idx2))
+ valid = false
+ }
+ }
+ }
+
+ if !valid {
+ util.FatalIfError(errors.New("index file is corrupted; see errors above"))
+ }
+
+ sort.Sort(reposByAbsPath(index.Repos))
+ return &index
+}
+
+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() {
+ buf, err := yaml.Marshal(i)
+ util.FatalIfError(err)
+ path := indexPath()
+ util.FatalIfError(os.MkdirAll(filepath.Dir(path), 0755))
+ util.FatalIfError(ioutil.WriteFile(path, buf, 0644))
+}
+
+//InteractiveRebuild implements the `rtree index` subcommand.
+func (i *Index) InteractiveRebuild() error {
+ //check if existing index entries are still checked out
+ existingRepos := make(map[string]*Repo)
+ var newRepos []*Repo
+ for _, repo := range i.Repos {
+ gitDirPath := filepath.Join(repo.AbsolutePath(), ".git")
+ fi, err := os.Stat(gitDirPath)
+ if err == nil {
+ if fi.IsDir() {
+ //everything okay with this repo
+ existingRepos[repo.CheckoutPath] = repo
+ newRepos = append(newRepos, repo)
+ continue
+ }
+ return fmt.Errorf("%s is not a directory: I'm seriously confused", gitDirPath)
+ }
+ if err != nil && !os.IsNotExist(err) {
+ return err
+ }
+
+ //repo has been deleted - ask what to do
+ fmt.Printf("repository %s has been deleted\n", filepath.Join(RootPath, repo.CheckoutPath))
+
+ var remoteURLs []string
+ for _, remote := range repo.Remotes {
+ if remote.Name == "origin" {
+ remoteURLs = []string{remote.URL}
+ break
+ }
+ remoteURLs = append(remoteURLs, remote.URL)
+ }
+
+ var choice string
+ if len(remoteURLs) == 0 {
+ choice = util.Prompt(
+ "no remote to restore from; (d)elete from index or (s)kip?",
+ []string{"d", "s"},
+ )
+ } else {
+ choice = util.Prompt(
+ fmt.Sprintf("(r)estore from %s, (d)elete from index, or (s)kip?", strings.Join(remoteURLs, " and ")),
+ []string{"r", "d", "s"},
+ )
+ }
+
+ switch choice {
+ case "r":
+ err := repo.Checkout()
+ if err != nil {
+ return err
+ }
+ newRepos = append(newRepos, repo)
+ case "d":
+ continue
+ case "s":
+ newRepos = append(newRepos, 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
+}