1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226
|
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
package repo
import (
"fmt"
"os/exec"
"strings"
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/config"
"github.com/go-git/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/plumbing/storer"
)
type WorkTree interface {
Root() string
Add(path string) error
Commit(message string) error
Checkout(opt *CheckoutOptions) error
CheckoutTag(tag string) error
CreateBranch(branch *Branch) error
DeleteBranch(name string) error
CherryPick(commit string) error
Stash() error
StashPop() error
Head() (*plumbing.Reference, error)
Tags() (storer.ReferenceIter, error)
Remotes() ([]*git.Remote, error)
DeleteRemote(name string) error
CreateRemote(c *config.RemoteConfig) (*git.Remote, error)
Fetch(o *git.FetchOptions) error
}
type CheckoutOptions git.CheckoutOptions
type Branch config.Branch
type repository struct {
*git.Repository
wt *git.Worktree
root string
}
func NewWorkTree(path string) (WorkTree, error) {
r, err := git.PlainOpenWithOptions(path, &git.PlainOpenOptions{
DetectDotGit: true,
})
if err != nil {
return nil, fmt.Errorf("cannot open '%s': %+v", path, err)
}
wt, err := r.Worktree()
if err != nil {
return nil, fmt.Errorf("cannot get the work tree of '%s': %+v", path, err)
}
return &repository{
Repository: r,
wt: wt,
root: wt.Filesystem.Root(),
}, nil
}
func CloneWorkTree(repoURL, workingPath string) (WorkTree, error) {
r, err := git.PlainClone(workingPath, false, &git.CloneOptions{
URL: repoURL,
})
if err != nil {
return nil, fmt.Errorf("cannot clone '%s' to '%s': %+v", repoURL, workingPath, err)
}
wt, err := r.Worktree()
if err != nil {
return nil, fmt.Errorf("cannot get the work tree of '%s': %+v", workingPath, err)
}
return &repository{
Repository: r,
wt: wt,
root: wt.Filesystem.Root(),
}, nil
}
func (r *repository) Root() string {
return r.root
}
// TODO -- go-git has some performance issue during Add, therefore we use the git command as a workaround
func (r *repository) Add(path string) error {
cmd := exec.Command("git", "add", path)
cmd.Dir = r.root
output, err := cmd.CombinedOutput()
if err != nil {
return fmt.Errorf(string(output))
}
return nil
}
// TODO -- go-git has some performance and permission issue during Commit, therefore we use the git command as a workaround
func (r *repository) Commit(message string) error {
cmd := exec.Command("git", "commit", "-m", message)
cmd.Dir = r.root
output, err := cmd.CombinedOutput()
if err != nil {
m := string(output)
if strings.Contains(m, "nothing added to commit") {
return &NothingToCommit{
message: m,
}
}
return fmt.Errorf(m)
}
return nil
}
// TODO -- go-git has some issue on the Checkout command, it will keep the CRLF changes after switching branches in stage
func (r *repository) Checkout(opt *CheckoutOptions) error {
if len(opt.Branch) > 0 {
return r.checkoutBranch(opt.Branch.Short(), opt.Create)
}
if !opt.Hash.IsZero() {
return r.checkoutHash(opt.Hash.String())
}
return fmt.Errorf("must set one of hash or branch")
}
func (r *repository) checkoutBranch(branch string, create bool) error {
var cmd *exec.Cmd
if create {
cmd = exec.Command("git", "checkout", "-b", branch)
} else {
cmd = exec.Command("git", "checkout", branch)
}
cmd.Dir = r.root
output, err := cmd.CombinedOutput()
if err != nil {
return fmt.Errorf(string(output))
}
return nil
}
func (r *repository) checkoutHash(hash string) error {
cmd := exec.Command("git", "checkout", hash)
cmd.Dir = r.root
output, err := cmd.CombinedOutput()
if err != nil {
return fmt.Errorf(string(output))
}
return nil
}
func (r *repository) CheckoutTag(tag string) error {
cmd := exec.Command("git", "checkout", tag)
cmd.Dir = r.root
output, err := cmd.CombinedOutput()
if err != nil {
return fmt.Errorf(string(output))
}
return nil
}
func (r *repository) CreateBranch(branch *Branch) error {
cmd := exec.Command("git", "branch", branch.Name)
cmd.Dir = r.root
output, err := cmd.CombinedOutput()
if err != nil {
return fmt.Errorf(string(output))
}
return nil
}
// TODO -- we cannot delete the branch that is not created using go-git, therefore we use the git command as a workaround
func (r *repository) DeleteBranch(name string) error {
cmd := exec.Command("git", "branch", "-D", name)
cmd.Dir = r.root
output, err := cmd.CombinedOutput()
if err != nil {
return fmt.Errorf(string(output))
}
return nil
}
// TODO -- go-git now does not support cherry-pick (or I did not find this?), therefore we use the git command as a workaround
func (r *repository) CherryPick(commit string) error {
cmd := exec.Command("git", "cherry-pick", commit)
cmd.Dir = r.root
output, err := cmd.CombinedOutput()
if err != nil {
return fmt.Errorf(string(output))
}
return nil
}
// TODO -- go-git now does not support stash (or I did not find this?), therefore we use the git command as a workaround
func (r *repository) Stash() error {
cmd := exec.Command("git", "stash")
cmd.Dir = r.root
output, err := cmd.CombinedOutput()
if err != nil {
return fmt.Errorf(string(output))
}
return nil
}
// TODO -- go-git now does not support stash (or I did not find this?), therefore we use the git command as a workaround
func (r *repository) StashPop() error {
cmd := exec.Command("git", "stash", "pop")
cmd.Dir = r.root
output, err := cmd.CombinedOutput()
if err != nil {
return fmt.Errorf(string(output))
}
return nil
}
type NothingToCommit struct {
message string
}
func (n *NothingToCommit) Error() string {
return n.message
}
func IsNothingToCommit(err error) bool {
_, ok := err.(*NothingToCommit)
return ok
}
|