7b321bc barerepo 1mo
1
package main
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
19
func gitShow(args ...string) {
7b321bc barerepo 1mo
20
fmt.Fprintln(os.Stderr, "+ git "+strings.Join(args, " "))
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
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
42
return out.String(), errors.New(msg)
7b321bc barerepo 1mo
44
return out.String(), nil
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
51
return nil
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
58
return "origin"
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
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
69
return n, nil
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
76
return gitRun(ctx, "push", remoteOr(args), "HEAD:refs/proposals/new")
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
83
n, err := threadNumber(args, "proposal")
7b321bc barerepo 1mo
84
if err != nil {
7b321bc barerepo 1mo
85
return err
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
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
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
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
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
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
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
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