github.com/sdboyer/gps@v0.16.3/vcs_repo.go (about) 1 package gps 2 3 import ( 4 "context" 5 "encoding/xml" 6 "os" 7 "path/filepath" 8 "runtime" 9 "strings" 10 "time" 11 12 "github.com/Masterminds/vcs" 13 ) 14 15 type ctxRepo interface { 16 vcs.Repo 17 get(context.Context) error 18 fetch(context.Context) error 19 updateVersion(context.Context, string) error 20 //ping(context.Context) (bool, error) 21 } 22 23 // original implementation of these methods come from 24 // https://github.com/Masterminds/vcs 25 26 type gitRepo struct { 27 *vcs.GitRepo 28 } 29 30 func newVcsRemoteErrorOr(msg string, err error, out string) error { 31 if err == context.Canceled || err == context.DeadlineExceeded { 32 return err 33 } 34 return vcs.NewRemoteError(msg, err, out) 35 } 36 37 func newVcsLocalErrorOr(msg string, err error, out string) error { 38 if err == context.Canceled || err == context.DeadlineExceeded { 39 return err 40 } 41 return vcs.NewLocalError(msg, err, out) 42 } 43 44 func (r *gitRepo) get(ctx context.Context) error { 45 out, err := runFromCwd(ctx, "git", "clone", "--recursive", r.Remote(), r.LocalPath()) 46 if err != nil { 47 return newVcsRemoteErrorOr("unable to get repository", err, string(out)) 48 } 49 50 return nil 51 } 52 53 func (r *gitRepo) fetch(ctx context.Context) error { 54 // Perform a fetch to make sure everything is up to date. 55 out, err := runFromRepoDir(ctx, r, "git", "fetch", "--tags", "--prune", r.RemoteLocation) 56 if err != nil { 57 return newVcsRemoteErrorOr("unable to update repository", err, string(out)) 58 } 59 return nil 60 } 61 62 func (r *gitRepo) updateVersion(ctx context.Context, v string) error { 63 out, err := runFromRepoDir(ctx, r, "git", "checkout", v) 64 if err != nil { 65 return newVcsLocalErrorOr("Unable to update checked out version", err, string(out)) 66 } 67 68 return r.defendAgainstSubmodules(ctx) 69 } 70 71 // defendAgainstSubmodules tries to keep repo state sane in the event of 72 // submodules. Or nested submodules. What a great idea, submodules. 73 func (r *gitRepo) defendAgainstSubmodules(ctx context.Context) error { 74 // First, update them to whatever they should be, if there should happen to be any. 75 out, err := runFromRepoDir(ctx, r, "git", "submodule", "update", "--init", "--recursive") 76 if err != nil { 77 return newVcsLocalErrorOr("unexpected error while defensively updating submodules", err, string(out)) 78 } 79 80 // Now, do a special extra-aggressive clean in case changing versions caused 81 // one or more submodules to go away. 82 out, err = runFromRepoDir(ctx, r, "git", "clean", "-x", "-d", "-f", "-f") 83 if err != nil { 84 return newVcsLocalErrorOr("unexpected error while defensively cleaning up after possible derelict submodule directories", err, string(out)) 85 } 86 87 // Then, repeat just in case there are any nested submodules that went away. 88 out, err = runFromRepoDir(ctx, r, "git", "submodule", "foreach", "--recursive", "git", "clean", "-x", "-d", "-f", "-f") 89 if err != nil { 90 return newVcsLocalErrorOr("unexpected error while defensively cleaning up after possible derelict nested submodule directories", err, string(out)) 91 } 92 93 return nil 94 } 95 96 type bzrRepo struct { 97 *vcs.BzrRepo 98 } 99 100 func (r *bzrRepo) get(ctx context.Context) error { 101 basePath := filepath.Dir(filepath.FromSlash(r.LocalPath())) 102 if _, err := os.Stat(basePath); os.IsNotExist(err) { 103 err = os.MkdirAll(basePath, 0755) 104 if err != nil { 105 return newVcsLocalErrorOr("unable to create directory", err, "") 106 } 107 } 108 109 out, err := runFromCwd(ctx, "bzr", "branch", r.Remote(), r.LocalPath()) 110 if err != nil { 111 return newVcsRemoteErrorOr("unable to get repository", err, string(out)) 112 } 113 114 return nil 115 } 116 117 func (r *bzrRepo) fetch(ctx context.Context) error { 118 out, err := runFromRepoDir(ctx, r, "bzr", "pull") 119 if err != nil { 120 return newVcsRemoteErrorOr("unable to update repository", err, string(out)) 121 } 122 return nil 123 } 124 125 func (r *bzrRepo) updateVersion(ctx context.Context, version string) error { 126 out, err := runFromRepoDir(ctx, r, "bzr", "update", "-r", version) 127 if err != nil { 128 return newVcsLocalErrorOr("unable to update checked out version", err, string(out)) 129 } 130 return nil 131 } 132 133 type hgRepo struct { 134 *vcs.HgRepo 135 } 136 137 func (r *hgRepo) get(ctx context.Context) error { 138 out, err := runFromCwd(ctx, "hg", "clone", r.Remote(), r.LocalPath()) 139 if err != nil { 140 return newVcsRemoteErrorOr("unable to get repository", err, string(out)) 141 } 142 143 return nil 144 } 145 146 func (r *hgRepo) fetch(ctx context.Context) error { 147 out, err := runFromRepoDir(ctx, r, "hg", "pull") 148 if err != nil { 149 return newVcsRemoteErrorOr("unable to fetch latest changes", err, string(out)) 150 } 151 return nil 152 } 153 154 func (r *hgRepo) updateVersion(ctx context.Context, version string) error { 155 out, err := runFromRepoDir(ctx, r, "hg", "update", version) 156 if err != nil { 157 return newVcsRemoteErrorOr("unable to update checked out version", err, string(out)) 158 } 159 160 return nil 161 } 162 163 type svnRepo struct { 164 *vcs.SvnRepo 165 } 166 167 func (r *svnRepo) get(ctx context.Context) error { 168 remote := r.Remote() 169 if strings.HasPrefix(remote, "/") { 170 remote = "file://" + remote 171 } else if runtime.GOOS == "windows" && filepath.VolumeName(remote) != "" { 172 remote = "file:///" + remote 173 } 174 175 out, err := runFromCwd(ctx, "svn", "checkout", remote, r.LocalPath()) 176 if err != nil { 177 return newVcsRemoteErrorOr("unable to get repository", err, string(out)) 178 } 179 180 return nil 181 } 182 183 func (r *svnRepo) update(ctx context.Context) error { 184 out, err := runFromRepoDir(ctx, r, "svn", "update") 185 if err != nil { 186 return newVcsRemoteErrorOr("unable to update repository", err, string(out)) 187 } 188 189 return err 190 } 191 192 func (r *svnRepo) updateVersion(ctx context.Context, version string) error { 193 out, err := runFromRepoDir(ctx, r, "svn", "update", "-r", version) 194 if err != nil { 195 return newVcsRemoteErrorOr("unable to update checked out version", err, string(out)) 196 } 197 198 return nil 199 } 200 201 func (r *svnRepo) CommitInfo(id string) (*vcs.CommitInfo, error) { 202 ctx := context.TODO() 203 // There are cases where Svn log doesn't return anything for HEAD or BASE. 204 // svn info does provide details for these but does not have elements like 205 // the commit message. 206 if id == "HEAD" || id == "BASE" { 207 type commit struct { 208 Revision string `xml:"revision,attr"` 209 } 210 211 type info struct { 212 Commit commit `xml:"entry>commit"` 213 } 214 215 out, err := runFromRepoDir(ctx, r, "svn", "info", "-r", id, "--xml") 216 if err != nil { 217 return nil, newVcsLocalErrorOr("unable to retrieve commit information", err, string(out)) 218 } 219 220 infos := new(info) 221 err = xml.Unmarshal(out, &infos) 222 if err != nil { 223 return nil, newVcsLocalErrorOr("unable to retrieve commit information", err, string(out)) 224 } 225 226 id = infos.Commit.Revision 227 if id == "" { 228 return nil, vcs.ErrRevisionUnavailable 229 } 230 } 231 232 out, err := runFromRepoDir(ctx, r, "svn", "log", "-r", id, "--xml") 233 if err != nil { 234 return nil, newVcsRemoteErrorOr("unable to retrieve commit information", err, string(out)) 235 } 236 237 type logentry struct { 238 Author string `xml:"author"` 239 Date string `xml:"date"` 240 Msg string `xml:"msg"` 241 } 242 243 type log struct { 244 XMLName xml.Name `xml:"log"` 245 Logs []logentry `xml:"logentry"` 246 } 247 248 logs := new(log) 249 err = xml.Unmarshal(out, &logs) 250 if err != nil { 251 return nil, newVcsLocalErrorOr("unable to retrieve commit information", err, string(out)) 252 } 253 254 if len(logs.Logs) == 0 { 255 return nil, vcs.ErrRevisionUnavailable 256 } 257 258 ci := &vcs.CommitInfo{ 259 Commit: id, 260 Author: logs.Logs[0].Author, 261 Message: logs.Logs[0].Msg, 262 } 263 264 if len(logs.Logs[0].Date) > 0 { 265 ci.Date, err = time.Parse(time.RFC3339Nano, logs.Logs[0].Date) 266 if err != nil { 267 return nil, newVcsLocalErrorOr("unable to retrieve commit information", err, string(out)) 268 } 269 } 270 271 return ci, nil 272 }