File view with blame information shown in the left gutter beside each line.

barerepo / cli / cmd/br/client.go
388 lines · 10kb · 7b321bcf7a437f0a27c143577d257e65664f8734
log files threads runs releases config jump to file t
7b321bc barerepo 1mo
1
package main
7b321bc barerepo 1mo
2
7b321bc barerepo 1mo
3
import (
7b321bc barerepo 1mo
4
"bytes"
7b321bc barerepo 1mo
5
"context"
7b321bc barerepo 1mo
6
"errors"
7b321bc barerepo 1mo
7
"flag"
7b321bc barerepo 1mo
8
"fmt"
7b321bc barerepo 1mo
9
"io"
7b321bc barerepo 1mo
10
"net/http"
7b321bc barerepo 1mo
11
"net/url"
7b321bc barerepo 1mo
12
"os"
7b321bc barerepo 1mo
13
"os/exec"
7b321bc barerepo 1mo
14
"path/filepath"
7b321bc barerepo 1mo
15
"strconv"
7b321bc barerepo 1mo
16
"strings"
7b321bc barerepo 1mo
17
)
7b321bc barerepo 1mo
18
7b321bc barerepo 1mo
19
func gitShow(args ...string) {
7b321bc barerepo 1mo
20
fmt.Fprintln(os.Stderr, "+ git "+strings.Join(args, " "))
7b321bc barerepo 1mo
21
}
7b321bc barerepo 1mo
22
7b321bc barerepo 1mo
23
func gitRun(ctx context.Context, args ...string) error {
7b321bc barerepo 1mo
24
gitShow(args...)
7b321bc barerepo 1mo
25
cmd := exec.CommandContext(ctx, "git", args...)
7b321bc barerepo 1mo
26
cmd.Stdin = os.Stdin
7b321bc barerepo 1mo
27
cmd.Stdout = os.Stdout
7b321bc barerepo 1mo
28
cmd.Stderr = os.Stderr
7b321bc barerepo 1mo
29
return cmd.Run()
7b321bc barerepo 1mo
30
}
7b321bc barerepo 1mo
31
7b321bc barerepo 1mo
32
func gitQuiet(ctx context.Context, args ...string) (string, error) {
7b321bc barerepo 1mo
33
var out, errb bytes.Buffer
7b321bc barerepo 1mo
34
cmd := exec.CommandContext(ctx, "git", args...)
7b321bc barerepo 1mo
35
cmd.Stdout = &out
7b321bc barerepo 1mo
36
cmd.Stderr = &errb
7b321bc barerepo 1mo
37
if err := cmd.Run(); err != nil {
7b321bc barerepo 1mo
38
msg := strings.TrimSpace(errb.String())
7b321bc barerepo 1mo
39
if msg == "" {
7b321bc barerepo 1mo
40
msg = err.Error()
7b321bc barerepo 1mo
41
}
7b321bc barerepo 1mo
42
return out.String(), errors.New(msg)
7b321bc barerepo 1mo
43
}
7b321bc barerepo 1mo
44
return out.String(), nil
7b321bc barerepo 1mo
45
}
7b321bc barerepo 1mo
46
7b321bc barerepo 1mo
47
func inWorkTree(ctx context.Context) error {
7b321bc barerepo 1mo
48
if _, err := gitQuiet(ctx, "rev-parse", "--git-dir"); err != nil {
7b321bc barerepo 1mo
49
return errors.New("this is not a git repository. cd into one first")
7b321bc barerepo 1mo
50
}
7b321bc barerepo 1mo
51
return nil
7b321bc barerepo 1mo
52
}
7b321bc barerepo 1mo
53
7b321bc barerepo 1mo
54
func remoteOr(args []string) string {
7b321bc barerepo 1mo
55
if len(args) > 0 && args[0] != "" {
7b321bc barerepo 1mo
56
return args[0]
7b321bc barerepo 1mo
57
}
7b321bc barerepo 1mo
58
return "origin"
7b321bc barerepo 1mo
59
}
7b321bc barerepo 1mo
60
7b321bc barerepo 1mo
61
func threadNumber(args []string, what string) (int, error) {
7b321bc barerepo 1mo
62
if len(args) == 0 {
7b321bc barerepo 1mo
63
return 0, fmt.Errorf("which %s? give its number, for example: forge %s 47", what, what)
7b321bc barerepo 1mo
64
}
7b321bc barerepo 1mo
65
n, err := strconv.Atoi(strings.TrimPrefix(args[0], "#"))
7b321bc barerepo 1mo
66
if err != nil || n < 1 {
7b321bc barerepo 1mo
67
return 0, fmt.Errorf("%q is not a %s number", args[0], what)
7b321bc barerepo 1mo
68
}
7b321bc barerepo 1mo
69
return n, nil
7b321bc barerepo 1mo
70
}
7b321bc barerepo 1mo
71
7b321bc barerepo 1mo
72
func cmdPropose(ctx context.Context, args []string) error {
7b321bc barerepo 1mo
73
if err := inWorkTree(ctx); err != nil {
7b321bc barerepo 1mo
74
return err
7b321bc barerepo 1mo
75
}
7b321bc barerepo 1mo
76
return gitRun(ctx, "push", remoteOr(args), "HEAD:refs/proposals/new")
7b321bc barerepo 1mo
77
}
7b321bc barerepo 1mo
78
7b321bc barerepo 1mo
79
func cmdFetchProposal(ctx context.Context, args []string) error {
7b321bc barerepo 1mo
80
if err := inWorkTree(ctx); err != nil {
7b321bc barerepo 1mo
81
return err
7b321bc barerepo 1mo
82
}
7b321bc barerepo 1mo
83
n, err := threadNumber(args, "proposal")
7b321bc barerepo 1mo
84
if err != nil {
7b321bc barerepo 1mo
85
return err
7b321bc barerepo 1mo
86
}
7b321bc barerepo 1mo
87
remote := remoteOr(args[1:])
7b321bc barerepo 1mo
88
spec := fmt.Sprintf("refs/proposals/%d:prop-%d", n, n)
7b321bc barerepo 1mo
89
if err := gitRun(ctx, "fetch", remote, spec); err != nil {
7b321bc barerepo 1mo
90
return err
7b321bc barerepo 1mo
91
}
7b321bc barerepo 1mo
92
fmt.Printf("proposal %d is now the local branch prop-%d\n", n, n)
7b321bc barerepo 1mo
93
fmt.Printf(" git log master..prop-%d\n", n)
7b321bc barerepo 1mo
94
fmt.Printf(" git diff master...prop-%d\n", n)
7b321bc barerepo 1mo
95
return nil
7b321bc barerepo 1mo
96
}
7b321bc barerepo 1mo
97
7b321bc barerepo 1mo
98
func cmdNotes(ctx context.Context, args []string) error {
7b321bc barerepo 1mo
99
if err := inWorkTree(ctx); err != nil {
7b321bc barerepo 1mo
100
return err
7b321bc barerepo 1mo
101
}
7b321bc barerepo 1mo
102
remote := remoteOr(args)
7b321bc barerepo 1mo
103
if err := gitRun(ctx, "fetch", remote, "refs/notes/*:refs/notes/*"); err == nil {
7b321bc barerepo 1mo
104
return nil
7b321bc barerepo 1mo
105
}
7b321bc barerepo 1mo
106
fmt.Fprintln(os.Stderr, "\nyou have replies of your own here. merging instead of overwriting.")
7b321bc barerepo 1mo
107
if err := gitRun(ctx, "fetch", remote, "+refs/notes/*:refs/notes/incoming/*"); err != nil {
7b321bc barerepo 1mo
108
return err
7b321bc barerepo 1mo
109
}
7b321bc barerepo 1mo
110
out, err := gitQuiet(ctx, "for-each-ref", "--format=%(refname:lstrip=3)", "refs/notes/incoming/")
7b321bc barerepo 1mo
111
if err != nil {
7b321bc barerepo 1mo
112
return err
7b321bc barerepo 1mo
113
}
7b321bc barerepo 1mo
114
for _, name := range strings.Fields(out) {
7b321bc barerepo 1mo
115
if err := gitRun(ctx, "notes", "--ref="+name, "merge", "-s", "union", "refs/notes/incoming/"+name); err != nil {
7b321bc barerepo 1mo
116
return err
7b321bc barerepo 1mo
117
}
7b321bc barerepo 1mo
118
}
7b321bc barerepo 1mo
119
for _, name := range strings.Fields(out) {
7b321bc barerepo 1mo
120
if err := gitRun(ctx, "update-ref", "-d", "refs/notes/incoming/"+name); err != nil {
7b321bc barerepo 1mo
121
return err
7b321bc barerepo 1mo
122
}
7b321bc barerepo 1mo
123
}
7b321bc barerepo 1mo
124
return nil
7b321bc barerepo 1mo
125
}
7b321bc barerepo 1mo
126
7b321bc barerepo 1mo
127
func cmdThreads(ctx context.Context, args []string) error {
7b321bc barerepo 1mo
128
if err := inWorkTree(ctx); err != nil {
7b321bc barerepo 1mo
129
return err
7b321bc barerepo 1mo
130
}
7b321bc barerepo 1mo
131
format := "%(refname:lstrip=3)"
7b321bc barerepo 1mo
132
gitShow("for-each-ref", "--format="+format, "refs/notes/threads/")
7b321bc barerepo 1mo
133
out, err := gitQuiet(ctx, "for-each-ref", "--format="+format, "refs/notes/threads/")
7b321bc barerepo 1mo
134
if err != nil {
7b321bc barerepo 1mo
135
return err
7b321bc barerepo 1mo
136
}
7b321bc barerepo 1mo
137
names := strings.Fields(out)
7b321bc barerepo 1mo
138
if len(names) == 0 {
7b321bc barerepo 1mo
139
fmt.Println("no threads here yet. run forge notes to fetch them.")
7b321bc barerepo 1mo
140
return nil
7b321bc barerepo 1mo
141
}
7b321bc barerepo 1mo
142
for _, name := range names {
7b321bc barerepo 1mo
143
fmt.Printf("%s\tgit log --show-notes=threads/%s\n", name, name)
7b321bc barerepo 1mo
144
}
7b321bc barerepo 1mo
145
fmt.Fprintln(os.Stderr, "\nclosed threads look the same here. the closed list lives on the server.")
7b321bc barerepo 1mo
146
return nil
7b321bc barerepo 1mo
147
}
7b321bc barerepo 1mo
148
7b321bc barerepo 1mo
149
func cmdThread(ctx context.Context, args []string) error {
7b321bc barerepo 1mo
150
if err := inWorkTree(ctx); err != nil {
7b321bc barerepo 1mo
151
return err
7b321bc barerepo 1mo
152
}
7b321bc barerepo 1mo
153
n, err := threadNumber(args, "thread")
7b321bc barerepo 1mo
154
if err != nil {
7b321bc barerepo 1mo
155
return err
7b321bc barerepo 1mo
156
}
7b321bc barerepo 1mo
157
ref := fmt.Sprintf("refs/notes/threads/%d", n)
7b321bc barerepo 1mo
158
if _, err := gitQuiet(ctx, "rev-parse", "--verify", "--quiet", ref); err != nil {
7b321bc barerepo 1mo
159
return fmt.Errorf("thread %d is not here. run forge notes to fetch it", n)
7b321bc barerepo 1mo
160
}
7b321bc barerepo 1mo
161
return gitRun(ctx, "log", fmt.Sprintf("--show-notes=threads/%d", n))
7b321bc barerepo 1mo
162
}
7b321bc barerepo 1mo
163
7b321bc barerepo 1mo
164
func cmdReply(ctx context.Context, args []string) error {
7b321bc barerepo 1mo
165
if err := inWorkTree(ctx); err != nil {
7b321bc barerepo 1mo
166
return err
7b321bc barerepo 1mo
167
}
7b321bc barerepo 1mo
168
fs := flag.NewFlagSet("reply", flag.ContinueOnError)
7b321bc barerepo 1mo
169
fs.SetOutput(os.Stderr)
7b321bc barerepo 1mo
170
msg := fs.String("m", "", "the comment to append, or leave it out and type on stdin")
7b321bc barerepo 1mo
171
rest, err := splitNumberFirst(args, fs)
7b321bc barerepo 1mo
172
if err != nil {
7b321bc barerepo 1mo
173
return err
7b321bc barerepo 1mo
174
}
7b321bc barerepo 1mo
175
n, err := threadNumber(rest, "thread")
7b321bc barerepo 1mo
176
if err != nil {
7b321bc barerepo 1mo
177
return err
7b321bc barerepo 1mo
178
}
7b321bc barerepo 1mo
179
body := strings.TrimSpace(*msg)
7b321bc barerepo 1mo
180
if body == "" {
7b321bc barerepo 1mo
181
typed, err := io.ReadAll(os.Stdin)
7b321bc barerepo 1mo
182
if err != nil {
7b321bc barerepo 1mo
183
return err
7b321bc barerepo 1mo
184
}
7b321bc barerepo 1mo
185
body = strings.TrimSpace(string(typed))
7b321bc barerepo 1mo
186
}
7b321bc barerepo 1mo
187
if body == "" {
7b321bc barerepo 1mo
188
return errors.New("an empty reply says nothing. use -m, or type the comment on stdin")
7b321bc barerepo 1mo
189
}
7b321bc barerepo 1mo
190
7b321bc barerepo 1mo
191
ref := fmt.Sprintf("threads/%d", n)
7b321bc barerepo 1mo
192
full := "refs/notes/" + ref
7b321bc barerepo 1mo
193
base, _ := gitQuiet(ctx, "rev-parse", "--verify", "--quiet", full)
7b321bc barerepo 1mo
194
base = strings.TrimSpace(base)
7b321bc barerepo 1mo
195
if err := gitRun(ctx, "notes", "--ref="+ref, "append", "-m", body); err != nil {
7b321bc barerepo 1mo
196
return err
7b321bc barerepo 1mo
197
}
7b321bc barerepo 1mo
198
remote := remoteOr(nil)
7b321bc barerepo 1mo
199
if err := gitRun(ctx, "push", remote, full); err == nil {
7b321bc barerepo 1mo
200
return nil
7b321bc barerepo 1mo
201
}
7b321bc barerepo 1mo
202
7b321bc barerepo 1mo
203
fmt.Fprintln(os.Stderr, "\nsomebody replied first. taking their reply, then writing yours after it.")
7b321bc barerepo 1mo
204
if err := gitRun(ctx, "fetch", remote, full); err != nil {
7b321bc barerepo 1mo
205
return err
7b321bc barerepo 1mo
206
}
7b321bc barerepo 1mo
207
head, err := gitQuiet(ctx, "rev-parse", "FETCH_HEAD")
7b321bc barerepo 1mo
208
if err != nil {
7b321bc barerepo 1mo
209
return err
7b321bc barerepo 1mo
210
}
7b321bc barerepo 1mo
211
head = strings.TrimSpace(head)
7b321bc barerepo 1mo
212
if base != "" {
7b321bc barerepo 1mo
213
if _, err := gitQuiet(ctx, "merge-base", "--is-ancestor", base, head); err != nil {
7b321bc barerepo 1mo
214
saved := "refs/notes/before-reply/" + ref
7b321bc barerepo 1mo
215
if err := gitRun(ctx, "update-ref", saved, base); err != nil {
7b321bc barerepo 1mo
216
return err
7b321bc barerepo 1mo
217
}
7b321bc barerepo 1mo
218
fmt.Fprintln(os.Stderr, "your copy held replies the server has not. it is kept at "+saved)
7b321bc barerepo 1mo
219
}
7b321bc barerepo 1mo
220
}
7b321bc barerepo 1mo
221
if err := gitRun(ctx, "update-ref", full, head); err != nil {
7b321bc barerepo 1mo
222
return err
222 of 388 lines · the whole file
history · rawbarerepo 0.1.0