github.com/kazu/ghq@v0.8.1-0.20180818162325-dedd532b4440/local_repository.go (about)

     1  package main
     2  
     3  import (
     4  	"fmt"
     5  	"net/url"
     6  	"os"
     7  	"path"
     8  	"path/filepath"
     9  	"strings"
    10  
    11  	"github.com/mitchellh/go-homedir"
    12  	"github.com/kazu/ghq/utils"
    13  )
    14  
    15  type LocalRepository struct {
    16  	FullPath  string
    17  	RelPath   string
    18  	PathParts []string
    19  }
    20  
    21  func LocalRepositoryFromFullPath(fullPath string) (*LocalRepository, error) {
    22  	var relPath string
    23  
    24  	for _, root := range localRepositoryRoots() {
    25  		if strings.HasPrefix(fullPath, root) == false {
    26  			continue
    27  		}
    28  
    29  		var err error
    30  		relPath, err = filepath.Rel(root, fullPath)
    31  		if err == nil {
    32  			break
    33  		}
    34  	}
    35  
    36  	if relPath == "" {
    37  		return nil, fmt.Errorf("no local repository found for: %s", fullPath)
    38  	}
    39  
    40  	pathParts := strings.Split(relPath, string(filepath.Separator))
    41  
    42  	return &LocalRepository{fullPath, filepath.ToSlash(relPath), pathParts}, nil
    43  }
    44  
    45  func LocalRepositoryFromURL(remoteURL *url.URL) *LocalRepository {
    46  	pathParts := append(
    47  		[]string{remoteURL.Host}, strings.Split(remoteURL.Path, "/")...,
    48  	)
    49  	relPath := strings.TrimSuffix(path.Join(pathParts...), ".git")
    50  
    51  	var localRepository *LocalRepository
    52  
    53  	// Find existing local repository first
    54  	walkLocalRepositories(func(repo *LocalRepository) {
    55  		if repo.RelPath == relPath {
    56  			localRepository = repo
    57  		}
    58  	})
    59  
    60  	if localRepository != nil {
    61  		return localRepository
    62  	}
    63  
    64  	// No local repository found, returning new one
    65  	return &LocalRepository{
    66  		path.Join(primaryLocalRepositoryRoot(), relPath),
    67  		relPath,
    68  		pathParts,
    69  	}
    70  }
    71  
    72  // Subpaths returns lists of tail parts of relative path from the root directory (shortest first)
    73  // for example, {"ghq", "motemen/ghq", "github.com/motemen/ghq"} for $root/github.com/motemen/ghq.
    74  func (repo *LocalRepository) Subpaths() []string {
    75  	tails := make([]string, len(repo.PathParts))
    76  
    77  	for i := range repo.PathParts {
    78  		tails[i] = strings.Join(repo.PathParts[len(repo.PathParts)-(i+1):], "/")
    79  	}
    80  
    81  	return tails
    82  }
    83  
    84  func (repo *LocalRepository) NonHostPath() string {
    85  	return strings.Join(repo.PathParts[1:], "/")
    86  }
    87  
    88  func (repo *LocalRepository) IsUnderPrimaryRoot() bool {
    89  	return strings.HasPrefix(repo.FullPath, primaryLocalRepositoryRoot())
    90  }
    91  
    92  // Matches checks if any subpath of the local repository equals the query.
    93  func (repo *LocalRepository) Matches(pathQuery string) bool {
    94  	for _, p := range repo.Subpaths() {
    95  		if p == pathQuery {
    96  			return true
    97  		}
    98  	}
    99  
   100  	return false
   101  }
   102  
   103  // TODO return err
   104  func (repo *LocalRepository) VCS() *VCSBackend {
   105  	var (
   106  		fi  os.FileInfo
   107  		err error
   108  	)
   109  
   110  	fi, err = os.Stat(filepath.Join(repo.FullPath, ".git/svn"))
   111  	if err == nil && fi.IsDir() {
   112  		return GitsvnBackend
   113  	}
   114  
   115  	fi, err = os.Stat(filepath.Join(repo.FullPath, ".git"))
   116  	if err == nil && fi.IsDir() {
   117  		return GitBackend
   118  	}
   119  
   120  	fi, err = os.Stat(filepath.Join(repo.FullPath, ".svn"))
   121  	if err == nil && fi.IsDir() {
   122  		return SubversionBackend
   123  	}
   124  
   125  	fi, err = os.Stat(filepath.Join(repo.FullPath, ".hg"))
   126  	if err == nil && fi.IsDir() {
   127  		return MercurialBackend
   128  	}
   129  
   130  	fi, err = os.Stat(filepath.Join(repo.FullPath, "_darcs"))
   131  	if err == nil && fi.IsDir() {
   132  		return DarcsBackend
   133  	}
   134  
   135  	return nil
   136  }
   137  
   138  var vcsDirs = []string{".git", ".svn", ".hg", "_darcs"}
   139  
   140  func walkLocalRepositories(callback func(*LocalRepository)) {
   141  	for _, root := range localRepositoryRoots() {
   142  		filepath.Walk(root, func(path string, fileInfo os.FileInfo, err error) error {
   143  			if err != nil || fileInfo == nil || fileInfo.IsDir() == false {
   144  				return nil
   145  			}
   146  
   147  			vcsDirFound := false
   148  			for _, d := range vcsDirs {
   149  				_, err := os.Stat(filepath.Join(path, d))
   150  				if err == nil {
   151  					vcsDirFound = true
   152  					break
   153  				}
   154  			}
   155  
   156  			if !vcsDirFound {
   157  				return nil
   158  			}
   159  
   160  			repo, err := LocalRepositoryFromFullPath(path)
   161  			if err != nil {
   162  				return nil
   163  			}
   164  
   165  			if repo == nil {
   166  				return nil
   167  			}
   168  			callback(repo)
   169  			return filepath.SkipDir
   170  		})
   171  	}
   172  }
   173  
   174  var _localRepositoryRoots []string
   175  
   176  // localRepositoryRoots returns locally cloned repositories' root directories.
   177  // The root dirs are determined as following:
   178  //
   179  //   - If GHQ_ROOT environment variable is nonempty, use it as the only root dir.
   180  //   - Otherwise, use the result of `git config --get-all ghq.root` as the dirs.
   181  //   - Otherwise, fallback to the default root, `~/.ghq`.
   182  //
   183  // TODO: More fancy default directory path?
   184  func localRepositoryRoots() []string {
   185  	if len(_localRepositoryRoots) != 0 {
   186  		return _localRepositoryRoots
   187  	}
   188  
   189  	envRoot := os.Getenv("GHQ_ROOT")
   190  	if envRoot != "" {
   191  		_localRepositoryRoots = filepath.SplitList(envRoot)
   192  	} else {
   193  		var err error
   194  		_localRepositoryRoots, err = GitConfigAll("ghq.root")
   195  		utils.PanicIf(err)
   196  	}
   197  
   198  	if len(_localRepositoryRoots) == 0 {
   199  		homeDir, err := homedir.Dir()
   200  		utils.PanicIf(err)
   201  
   202  		_localRepositoryRoots = []string{filepath.Join(homeDir, ".ghq")}
   203  	}
   204  
   205  	for i, v := range _localRepositoryRoots {
   206  		path := filepath.Clean(v)
   207  		if _, err := os.Stat(path); err == nil {
   208  			_localRepositoryRoots[i], err = filepath.EvalSymlinks(path)
   209  			utils.PanicIf(err)
   210  		} else {
   211  			_localRepositoryRoots[i] = path
   212  		}
   213  	}
   214  
   215  	return _localRepositoryRoots
   216  }
   217  
   218  func primaryLocalRepositoryRoot() string {
   219  	return localRepositoryRoots()[0]
   220  }