aboutsummaryrefslogtreecommitdiff
path: root/repo.go
diff options
context:
space:
mode:
authorStefan Majewsky <majewsky@gmx.net>2017-04-04 22:00:22 +0200
committerStefan Majewsky <majewsky@gmx.net>2017-04-04 22:00:22 +0200
commit5169b2d145b0086516491a35887bfc5eb07be2ae (patch)
tree1d667534b898103b682cf23479ff7494527a7656 /repo.go
parentb422f498bf3053bdc69c62a9946f047dfb09e125 (diff)
downloadgofu-5169b2d145b0086516491a35887bfc5eb07be2ae.tar.gz
implement "index" subcommand
Diffstat (limited to 'repo.go')
-rw-r--r--repo.go180
1 files changed, 180 insertions, 0 deletions
diff --git a/repo.go b/repo.go
new file mode 100644
index 0000000..f0c2491
--- /dev/null
+++ b/repo.go
@@ -0,0 +1,180 @@
+/*******************************************************************************
+*
+* 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 main
+
+import (
+ "bytes"
+ "errors"
+ "fmt"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "regexp"
+ "strings"
+)
+
+//RootPath is the directory below which all repositories are located. Its value
+//is $GOPATH/src to match the repository layout created by `go get`.
+var RootPath string
+
+func init() {
+ gopath := os.Getenv("GOPATH")
+ if gopath == "" {
+ FatalIfError(errors.New("$GOPATH is not set (rtree needs the GOPATH variable to know where to look for and place repos)"))
+ }
+ RootPath = filepath.Join(gopath, "src")
+}
+
+//Repo describes the entry for a repository in the index file.
+type Repo struct {
+ //CheckoutPath shall be relative to the RootPath.
+ CheckoutPath string `yaml:"path"`
+ //Remotes maps remote names (as noted in the .git/config of the repo) to
+ //remote URLs (as they appear in the .git/config of the repo, i.e. possibly
+ //abbreviated).
+ Remotes []Remote `yaml:"remotes"`
+}
+
+//Remote describes a remote that is configured in a Repo.
+type Remote struct {
+ Name string `yaml:"name"`
+ URL string `yaml:"url"`
+}
+
+//AbsolutePath returns the absolute CheckoutPath of this repo.
+func (r Repo) AbsolutePath() string {
+ return filepath.Join(RootPath, r.CheckoutPath)
+}
+
+//NewRepoFromAbsolutePath initializes a Repo instance by scanning the existing
+//checkout at the given path.
+func NewRepoFromAbsolutePath(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()
+ if err != nil {
+ return repo, fmt.Errorf("exec `git config -l` in %s: %s", path, err.Error())
+ }
+
+ for _, line := range strings.Split(string(buf.Bytes()), "\n") {
+ match := remoteConfigRx.FindStringSubmatch(line)
+ if match == nil {
+ continue
+ }
+ repo.Remotes = append(repo.Remotes, Remote{
+ Name: match[1],
+ URL: match[2],
+ })
+ }
+ return
+}
+
+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 {
+ return filepath.Walk(RootPath, func(path string, info os.FileInfo, err error) error {
+ if err != nil {
+ return err
+ }
+ //look for repos, i.e. directories containing a .git directory
+ if !info.IsDir() {
+ return nil
+ }
+ _, err = os.Stat(filepath.Join(path, ".git"))
+ if err != nil {
+ return nil
+ }
+
+ //appears to be a repo
+ repo, err := NewRepoFromAbsolutePath(path)
+ if err == nil {
+ err = action(repo)
+ }
+ if err != nil {
+ return err
+ }
+ //do not traverse further down into submodules etc.
+ return filepath.SkipDir
+ })
+}
+
+//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 {
+ //check if we have an "origin" remote to clone from
+ var originURL string
+ for _, remote := range r.Remotes {
+ if remote.Name == "origin" {
+ originURL = remote.URL
+ break
+ }
+ }
+
+ if originURL == "" {
+ cmd := exec.Command("git", "init", r.AbsolutePath())
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+ err := cmd.Run()
+ if err != nil {
+ return err
+ }
+ fmt.Fprintln(os.Stderr, "warning: 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()
+ if err != nil {
+ return err
+ }
+ }
+
+ 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()
+ if err != nil {
+ return err
+ }
+ remotesAdded = true
+ }
+ }
+ if remotesAdded {
+ cmd := exec.Command("git", "-C", r.AbsolutePath(), "remote", "update")
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+ return cmd.Run()
+ }
+
+ return nil
+}