github.com/motemen/ghq@v1.0.3/getter.go (about)

     1  package main
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"path"
     7  	"path/filepath"
     8  	"strings"
     9  	"sync"
    10  
    11  	"github.com/motemen/ghq/logger"
    12  )
    13  
    14  var (
    15  	seen = make(map[string]bool)
    16  	mu   = &sync.Mutex{}
    17  )
    18  
    19  func getRepoLock(localRepoRoot string) bool {
    20  	mu.Lock()
    21  	defer func() {
    22  		seen[localRepoRoot] = true
    23  		mu.Unlock()
    24  	}()
    25  	return !seen[localRepoRoot]
    26  }
    27  
    28  type getter struct {
    29  	update, shallow, silent, ssh, recursive bool
    30  	vcs, branch                             string
    31  }
    32  
    33  func (g *getter) get(argURL string) error {
    34  	u, err := newURL(argURL, g.ssh, false)
    35  	if err != nil {
    36  		return fmt.Errorf("Could not parse URL %q: %w", argURL, err)
    37  	}
    38  
    39  	remote, err := NewRemoteRepository(u)
    40  	if err != nil {
    41  		return err
    42  	}
    43  
    44  	return g.getRemoteRepository(remote)
    45  }
    46  
    47  // getRemoteRepository clones or updates a remote repository remote.
    48  // If doUpdate is true, updates the locally cloned repository. Otherwise does nothing.
    49  // If isShallow is true, does shallow cloning. (no effect if already cloned or the VCS is Mercurial and git-svn)
    50  func (g *getter) getRemoteRepository(remote RemoteRepository) error {
    51  	remoteURL := remote.URL()
    52  	local, err := LocalRepositoryFromURL(remoteURL)
    53  	if err != nil {
    54  		return err
    55  	}
    56  
    57  	var (
    58  		fpath   = local.FullPath
    59  		newPath = false
    60  	)
    61  
    62  	_, err = os.Stat(fpath)
    63  	if err != nil {
    64  		if os.IsNotExist(err) {
    65  			newPath = true
    66  			err = nil
    67  		}
    68  		if err != nil {
    69  			return err
    70  		}
    71  	}
    72  
    73  	switch {
    74  	case newPath:
    75  		logger.Log("clone", fmt.Sprintf("%s -> %s", remoteURL, fpath))
    76  		var (
    77  			localRepoRoot = fpath
    78  			repoURL       = remoteURL
    79  		)
    80  		vcs, ok := vcsRegistry[g.vcs]
    81  		if !ok {
    82  			vcs, repoURL = remote.VCS()
    83  			if vcs == nil {
    84  				return fmt.Errorf("Could not find version control system: %s", remoteURL)
    85  			}
    86  		}
    87  		l := detectLocalRepoRoot(
    88  			strings.TrimSuffix(remoteURL.Path, ".git"),
    89  			strings.TrimSuffix(repoURL.Path, ".git"))
    90  		if l != "" {
    91  			localRepoRoot = filepath.Join(local.RootPath, remoteURL.Hostname(), l)
    92  		}
    93  
    94  		if getRepoLock(localRepoRoot) {
    95  			return vcs.Clone(&vcsGetOption{
    96  				url:       repoURL,
    97  				dir:       localRepoRoot,
    98  				shallow:   g.shallow,
    99  				silent:    g.silent,
   100  				branch:    g.branch,
   101  				recursive: g.recursive,
   102  			})
   103  		}
   104  		return nil
   105  	case g.update:
   106  		logger.Log("update", fpath)
   107  		vcs, localRepoRoot := local.VCS()
   108  		if vcs == nil {
   109  			return fmt.Errorf("failed to detect VCS for %q", fpath)
   110  		}
   111  		if getRepoLock(localRepoRoot) {
   112  			return vcs.Update(&vcsGetOption{
   113  				dir:       localRepoRoot,
   114  				silent:    g.silent,
   115  				recursive: g.recursive,
   116  			})
   117  		}
   118  		return nil
   119  	}
   120  	logger.Log("exists", fpath)
   121  	return nil
   122  }
   123  
   124  func detectLocalRepoRoot(remotePath, repoPath string) string {
   125  	pathParts := strings.Split(repoPath, "/")
   126  	pathParts = pathParts[1:]
   127  	for i := 0; i < len(pathParts); i++ {
   128  		subPath := "/" + path.Join(pathParts[i:]...)
   129  		if subIdx := strings.Index(remotePath, subPath); subIdx >= 0 {
   130  			return remotePath[0:subIdx] + subPath
   131  		}
   132  	}
   133  	return ""
   134  }