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 }