From ed2b9374599cc00467b1c27121318748cc0d3f49 Mon Sep 17 00:00:00 2001 From: Stefan Majewsky Date: Sat, 28 Mar 2020 13:53:10 +0100 Subject: rename pkg/ to internal/ --- internal/rtree/repo.go | 225 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 225 insertions(+) create mode 100644 internal/rtree/repo.go (limited to 'internal/rtree/repo.go') diff --git a/internal/rtree/repo.go b/internal/rtree/repo.go new file mode 100644 index 0000000..4af51c1 --- /dev/null +++ b/internal/rtree/repo.go @@ -0,0 +1,225 @@ +/******************************************************************************* +* +* Copyright 2017 Stefan Majewsky +* +* 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 . +* +*******************************************************************************/ + +package rtree + +import ( + "fmt" + "os" + "path/filepath" + "regexp" + "strings" + + "github.com/majewsky/gofu/internal/cli" +) + +//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 + out, err := cli.Interface.CaptureStdout(cli.Command{ + Program: []string{"git", "config", "-l"}, + WorkDir: path, + }) + if err != nil { + return + } + + for _, line := range strings.Split(out, "\n") { + match := remoteConfigRx.FindStringSubmatch(line) + if match == nil { + continue + } + repo.Remotes = append(repo.Remotes, Remote{ + Name: match[1], + URL: match[2], + }) + } + return +} + +//NewRepoFromRemoteURL initializes a Repo instance for checking out a remote +//for the first time. The checkout does not happen until Checkout() is called. +func NewRepoFromRemoteURL(remoteURL string) (Repo, error) { + checkoutPath, err := CheckoutPathForRemoteURL(ExpandRemoteURL(remoteURL)) + return Repo{ + CheckoutPath: checkoutPath, + Remotes: []Remote{ + { + Name: "origin", + URL: remoteURL, + }, + }, + }, err +} + +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 == "" { + err := cli.Interface.Run(cli.Command{ + Program: []string{"git", "init", r.AbsolutePath()}, + }) + if err != nil { + return err + } + cli.Interface.ShowWarning(`will not checkout anything since there is no remote named "origin"`) + } else { + err := cli.Interface.Run(cli.Command{ + Program: []string{"git", "clone", originURL, r.AbsolutePath()}, + }) + if err != nil { + return err + } + } + + remotesAdded := false + for _, remote := range r.Remotes { + if remote.Name != "origin" { + err := cli.Interface.Run(cli.Command{ + Program: []string{"git", "remote", "add", remote.Name, remote.URL}, + WorkDir: r.AbsolutePath(), + }) + if err != nil { + return err + } + remotesAdded = true + } + } + if remotesAdded { + return cli.Interface.Run(cli.Command{ + Program: []string{"git", "remote", "update"}, + WorkDir: r.AbsolutePath(), + }) + } + + return nil +} + +//Exec implements the meat of the `rtree exec` command. It returns +//true iff the command exited successfully. +func (r Repo) Exec(cmdline ...string) error { + cli.Interface.ShowProgress(r.AbsolutePath()) + return cli.Interface.Run(cli.Command{ + Program: cmdline, + WorkDir: r.AbsolutePath(), + }) +} + +//Move sets the CheckoutPath to the given value and moves the existing repo +//from the old to the new checkoutPath. If makeSymlink is given, a symlink will +//be created from the old to the new location. +func (r *Repo) Move(checkoutPath string, makeSymlink bool) error { + sourcePath := filepath.Join(RootPath, r.CheckoutPath) + targetPath := filepath.Join(RootPath, checkoutPath) + + //ensure that target does not exist + _, err := os.Lstat(targetPath) + if err == nil { + return fmt.Errorf("cannot move %s to %s: target exists in filesystem", sourcePath, targetPath) + } + if !os.IsNotExist(err) { + return err + } + + //prepare directory to move repo into + err = os.MkdirAll(filepath.Dir(targetPath), 0755) + if err != nil { + return err + } + + //move directory + err = os.Rename(sourcePath, targetPath) + if err != nil { + return err + } + r.CheckoutPath = checkoutPath + + //if requested, make compatibility symlink + if makeSymlink { + return os.Symlink(targetPath, sourcePath) + } + return nil +} -- cgit v1.3.1