github.com/x-motemen/ghq@v1.6.1/getter.go (about)

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