forked from cli/cli
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathlocal_repo.go
More file actions
139 lines (126 loc) · 3.2 KB
/
local_repo.go
File metadata and controls
139 lines (126 loc) · 3.2 KB
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
package git
import (
"bufio"
"bytes"
"context"
"io"
"os/exec"
"regexp"
"strings"
"github.com/cli/safeexec"
)
type LocalRepo struct {
Stdin io.Reader
Stdout io.Writer
Stderr io.Writer
gitCommandInit func(ctx context.Context, args ...string) (*exec.Cmd, error)
gitExePath string
gitExeErr error
}
func (c *LocalRepo) PathPrefix(ctx context.Context) (string, error) {
showCmd, err := c.gitCommand(ctx, "rev-parse", "--show-prefix")
if err != nil {
return "", err
}
output, err := showCmd.Output()
return strings.TrimRight(string(output), "\n"), err
}
func (c *LocalRepo) LastCommit(ctx context.Context) (*Commit, error) {
showCmd, err := c.gitCommand(ctx, "-c", "log.ShowSignature=false", "show", "-s", "--pretty=format:%H,%s", "HEAD")
if err != nil {
return nil, err
}
output, err := showCmd.Output()
if err != nil {
return nil, err
}
idx := bytes.IndexByte(output, ',')
return &Commit{
Sha: string(output[0:idx]),
Title: strings.TrimSpace(string(output[idx+1:])),
}, nil
}
func (c *LocalRepo) gitCommand(ctx context.Context, args ...string) (*exec.Cmd, error) {
if c.gitCommandInit != nil {
return c.gitCommandInit(ctx, args...)
}
gitExe, err := c.gitExe()
if err != nil {
return nil, err
}
cmd := exec.CommandContext(ctx, gitExe, args...)
return cmd, nil
}
func (c *LocalRepo) gitExe() (string, error) {
if c.gitExePath == "" && c.gitExeErr == nil {
c.gitExePath, c.gitExeErr = safeexec.LookPath("git")
}
return c.gitExePath, c.gitExeErr
}
type PushOptions struct {
RemoteName string
TargetBranch string
ShowProgress bool
SetUpstream bool
}
var gitPushRegexp = regexp.MustCompile(`^remote: (Create a pull request.*by visiting|[[:space:]]*https://.*/pull/new/)`)
// Push publishes a branch to a git remote and filters out "Create a pull request by visiting <URL>" lines
// before forwarding them to stderr.
func (g *LocalRepo) Push(ctx context.Context, opts PushOptions) error {
args := []string{"push"}
if opts.SetUpstream {
args = append(args, "--set-upstream")
}
if opts.ShowProgress {
args = append(args, "--progress")
}
args = append(args, opts.RemoteName, "HEAD:"+opts.TargetBranch)
pushCmd, err := g.gitCommand(ctx, args...)
if err != nil {
return err
}
pushCmd.Stdin = g.Stdin
pushCmd.Stdout = g.Stdout
r, err := pushCmd.StderrPipe()
if err != nil {
return err
}
if err = pushCmd.Start(); err != nil {
return err
}
if err = filterLines(g.Stderr, r, gitPushRegexp); err != nil {
return err
}
return pushCmd.Wait()
}
func filterLines(w io.Writer, r io.Reader, re *regexp.Regexp) error {
s := bufio.NewScanner(r)
s.Split(func(data []byte, atEOF bool) (advance int, token []byte, err error) {
if atEOF && len(data) == 0 {
return 0, nil, nil
}
i := bytes.IndexAny(data, "\r\n")
if i >= 0 {
// encompass both CR & LF characters if they appear together
if data[i] == '\r' && len(data) > i+1 && data[i+1] == '\n' {
return i + 2, data[0 : i+2], nil
}
return i + 1, data[0 : i+1], nil
}
if atEOF {
return len(data), data, nil
}
// Request more data.
return 0, nil, nil
})
for s.Scan() {
line := s.Bytes()
if !re.Match(line) {
_, err := w.Write(line)
if err != nil {
return err
}
}
}
return s.Err()
}