github.com/hattya/nazuna@v0.7.1-0.20240331055452-55e14c275c1c/vcs.go (about)

     1  //
     2  // nazuna :: vcs.go
     3  //
     4  //   Copyright (c) 2013-2020 Akinori Hattori <hattya@gmail.com>
     5  //
     6  //   SPDX-License-Identifier: MIT
     7  //
     8  
     9  package nazuna
    10  
    11  import (
    12  	"errors"
    13  	"fmt"
    14  	"os/exec"
    15  	"path/filepath"
    16  	"strings"
    17  	"sync"
    18  )
    19  
    20  type VCS interface {
    21  	String() string
    22  	Exec(...string) error
    23  
    24  	Init(string) error
    25  	Clone(string, string) error
    26  
    27  	Add(...string) error
    28  	List(...string) *exec.Cmd
    29  	Update() error
    30  }
    31  
    32  type BaseVCS struct {
    33  	Name string
    34  	Cmd  string
    35  	UI   UI
    36  	Dir  string
    37  }
    38  
    39  func (v *BaseVCS) String() string {
    40  	return v.Name
    41  }
    42  
    43  func (v *BaseVCS) Command(args ...string) *exec.Cmd {
    44  	cmd := exec.Command(v.Cmd, args...)
    45  	cmd.Dir = v.Dir
    46  	return cmd
    47  }
    48  
    49  func (v *BaseVCS) Exec(args ...string) error {
    50  	return v.UI.Exec(v.Command(args...))
    51  }
    52  
    53  func (v *BaseVCS) Init(string) error {
    54  	return errors.New("VCS.Init not implemented")
    55  }
    56  
    57  func (v *BaseVCS) Clone(string, string) error {
    58  	return errors.New("VCS.Clone not implemented")
    59  }
    60  
    61  func (v *BaseVCS) Add(...string) error {
    62  	return errors.New("VCS.Add not implemented")
    63  }
    64  
    65  func (v *BaseVCS) List(...string) *exec.Cmd {
    66  	return nil
    67  }
    68  
    69  func (v *BaseVCS) Update() error {
    70  	return errors.New("VCS.Update not implemented")
    71  }
    72  
    73  type Git struct {
    74  	BaseVCS
    75  }
    76  
    77  func newGit(ui UI, dir string) VCS {
    78  	return &Git{BaseVCS{
    79  		Name: "Git",
    80  		Cmd:  "git",
    81  		UI:   ui,
    82  		Dir:  dir,
    83  	}}
    84  }
    85  
    86  func (v *Git) Init(dir string) error {
    87  	return v.Exec("init", "-q", dir)
    88  }
    89  
    90  func (v *Git) Clone(src, dst string) error {
    91  	return v.Exec("clone", "--recursive", src, dst)
    92  }
    93  
    94  func (v *Git) Add(paths ...string) error {
    95  	return v.Exec(append([]string{"add"}, paths...)...)
    96  }
    97  
    98  func (v *Git) List(paths ...string) *exec.Cmd {
    99  	return v.Command(append([]string{"ls-files"}, paths...)...)
   100  }
   101  
   102  func (v *Git) Update() error {
   103  	if err := v.Exec("pull"); err != nil {
   104  		return err
   105  	}
   106  	return v.Exec("submodule", "update", "--init", "--recursive")
   107  }
   108  
   109  type Mercurial struct {
   110  	BaseVCS
   111  }
   112  
   113  func newMercurial(ui UI, dir string) VCS {
   114  	return &Mercurial{BaseVCS{
   115  		Name: "Mercurial",
   116  		Cmd:  "hg",
   117  		UI:   ui,
   118  		Dir:  dir,
   119  	}}
   120  }
   121  
   122  func (v *Mercurial) Init(dir string) error {
   123  	return v.Exec("init", dir)
   124  }
   125  
   126  func (v *Mercurial) Clone(src, dst string) error {
   127  	return v.Exec("clone", src, dst)
   128  }
   129  
   130  func (v *Mercurial) Add(paths ...string) error {
   131  	return v.Exec(append([]string{"add"}, paths...)...)
   132  }
   133  
   134  func (v *Mercurial) List(paths ...string) *exec.Cmd {
   135  	return v.Command(append([]string{"status", "-madcn", "--config", "ui.slash=True"}, paths...)...)
   136  }
   137  
   138  func (v *Mercurial) Update() error {
   139  	if err := v.Exec("pull"); err != nil {
   140  		return err
   141  	}
   142  	return v.Exec("update")
   143  }
   144  
   145  var (
   146  	mu    sync.RWMutex
   147  	vcses = map[string]*vcsType{
   148  		"git": {".git", newGit},
   149  		"hg":  {".hg", newMercurial},
   150  	}
   151  )
   152  
   153  type vcsType struct {
   154  	ctrlDir string
   155  	new     NewVCS
   156  }
   157  
   158  type NewVCS func(UI, string) VCS
   159  
   160  func RegisterVCS(cmd, ctrlDir string, new NewVCS) {
   161  	k := strings.ToLower(cmd)
   162  	if _, ok := vcses[k]; ok {
   163  		panic(fmt.Sprintf("vcs '%v' already registered", cmd))
   164  	}
   165  	vcses[k] = &vcsType{ctrlDir, new}
   166  }
   167  
   168  func FindVCS(ui UI, cmd, dir string) (VCS, error) {
   169  	mu.RLock()
   170  	defer mu.RUnlock()
   171  
   172  	k := strings.ToLower(cmd)
   173  	if v, ok := vcses[k]; ok {
   174  		return v.new(ui, dir), nil
   175  	}
   176  	return nil, fmt.Errorf("unknown vcs '%v'", cmd)
   177  }
   178  
   179  func VCSFor(ui UI, dir string) (VCS, error) {
   180  	mu.RLock()
   181  	defer mu.RUnlock()
   182  
   183  	for _, v := range vcses {
   184  		if IsDir(filepath.Join(dir, v.ctrlDir)) {
   185  			vcs := v.new(ui, dir)
   186  			return vcs, nil
   187  		}
   188  	}
   189  	return nil, fmt.Errorf("unknown vcs for directory '%v'", dir)
   190  }