+23
-2
appview/knotclient/signer.go
+23
-2
appview/knotclient/signer.go
···
106
106
return s.client.Do(req)
107
107
}
108
108
109
+
func (s *SignedClient) RepoForkAheadBehind(ownerDid, source, name, branch, hiddenRef string) (*http.Response, error) {
110
+
const (
111
+
Method = "GET"
112
+
)
113
+
endpoint := fmt.Sprintf("/repo/fork/sync/%s", url.PathEscape(branch))
114
+
115
+
body, _ := json.Marshal(map[string]any{
116
+
"did": ownerDid,
117
+
"source": source,
118
+
"name": name,
119
+
"hiddenref": hiddenRef,
120
+
})
121
+
122
+
req, err := s.newRequest(Method, endpoint, body)
123
+
if err != nil {
124
+
return nil, err
125
+
}
126
+
127
+
return s.client.Do(req)
128
+
}
129
+
109
130
func (s *SignedClient) SyncRepoFork(ownerDid, source, name, branch string) (*http.Response, error) {
110
131
const (
111
-
Method = "POST"
132
+
Method = "POST"
112
133
)
113
-
endpoint := fmt.Sprintf("/repo/fork/sync/%s", branch)
134
+
endpoint := fmt.Sprintf("/repo/fork/sync/%s", url.PathEscape(branch))
114
135
115
136
body, _ := json.Marshal(map[string]any{
116
137
"did": ownerDid,
+1
-15
appview/pages/pages.go
+1
-15
appview/pages/pages.go
···
402
402
return p.executePlain("repo/fragments/repoDescription", w, params)
403
403
}
404
404
405
-
type ForkStatus int
406
-
407
-
const (
408
-
UpToDate ForkStatus = 0
409
-
FastForwardable = 1
410
-
Conflict = 2
411
-
MissingBranch = 3
412
-
)
413
-
414
-
type ForkInfo struct {
415
-
IsFork bool
416
-
Status ForkStatus
417
-
}
418
-
419
405
type RepoIndexParams struct {
420
406
LoggedInUser *oauth.User
421
407
RepoInfo repoinfo.RepoInfo
···
424
410
CommitsTrunc []*object.Commit
425
411
TagsTrunc []*types.TagReference
426
412
BranchesTrunc []types.Branch
427
-
ForkInfo ForkInfo
413
+
ForkInfo types.ForkInfo
428
414
types.RepoIndexResponse
429
415
HTMLReadme template.HTML
430
416
Raw bool
+21
-46
appview/state/repo.go
+21
-46
appview/state/repo.go
···
25
25
"tangled.sh/tangled.sh/core/appview/pages/markup"
26
26
"tangled.sh/tangled.sh/core/appview/pages/repoinfo"
27
27
"tangled.sh/tangled.sh/core/appview/pagination"
28
-
"tangled.sh/tangled.sh/core/knotserver"
29
28
"tangled.sh/tangled.sh/core/types"
30
29
31
30
"github.com/bluesky-social/indigo/atproto/data"
···
124
123
user := s.oauth.GetUser(r)
125
124
repoInfo := f.RepoInfo(s, user)
126
125
127
-
forkInfo, err := GetForkInfo(repoInfo, s, f, us, w, user)
126
+
forkInfo, err := getForkInfo(repoInfo, s, f, us, w, user)
128
127
if err != nil {
129
128
log.Printf("Failed to fetch fork information: %v", err)
130
129
return
···
144
143
return
145
144
}
146
145
147
-
func GetForkInfo(
146
+
func getForkInfo(
148
147
repoInfo repoinfo.RepoInfo,
149
148
s *State,
150
149
f *FullyResolvedRepo,
151
150
us *knotclient.UnsignedClient,
152
151
w http.ResponseWriter,
153
152
user *oauth.User,
154
-
) (*pages.ForkInfo, error) {
155
-
forkInfo := pages.ForkInfo{
153
+
) (*types.ForkInfo, error) {
154
+
forkInfo := types.ForkInfo{
156
155
IsFork: repoInfo.Source != nil,
157
-
Status: pages.UpToDate,
156
+
Status: types.UpToDate,
158
157
}
159
158
160
159
secret, err := db.GetRegistrationKey(s.db, f.Knot)
···
187
186
return nil, err
188
187
}
189
188
190
-
var contains = false
191
-
for _, branch := range result.Branches {
192
-
if branch.Name == f.Ref {
193
-
contains = true
194
-
break
195
-
}
196
-
}
197
-
198
-
if contains == false {
199
-
forkInfo.Status = pages.MissingBranch
189
+
if !slices.ContainsFunc(result.Branches, func(branch types.Branch) bool {
190
+
return branch.Name == f.Ref
191
+
}) {
192
+
forkInfo.Status = types.MissingBranch
200
193
return &forkInfo, nil
201
194
}
202
195
···
213
206
}
214
207
215
208
hiddenRef := fmt.Sprintf("hidden/%s/%s", f.Ref, f.Ref)
216
-
comparison, err := us.Compare(user.Did, repoInfo.Name, f.Ref, hiddenRef)
217
-
if err != nil {
218
-
log.Printf("failed to compare branches '%s' and '%s': %s", f.Ref, hiddenRef, err)
219
-
return nil, err
220
-
}
221
209
222
-
if len(comparison.FormatPatch) == 0 {
223
-
return &forkInfo, nil
224
-
}
225
-
226
-
var isAncestor types.AncestorCheckResponse
227
-
forkSyncableResp, err := signedClient.RepoForkSyncable(user.Did, string(f.RepoAt), repoInfo.Name, f.Ref, hiddenRef)
210
+
var status types.AncestorCheckResponse
211
+
forkSyncableResp, err := signedClient.RepoForkAheadBehind(user.Did, string(f.RepoAt), repoInfo.Name, f.Ref, hiddenRef)
228
212
if err != nil {
229
-
log.Printf("failed to check if fork is syncable: %s", err)
213
+
log.Printf("failed to check if fork is ahead/behind: %s", err)
230
214
return nil, err
231
215
}
232
216
233
-
if err := json.NewDecoder(forkSyncableResp.Body).Decode(&isAncestor); err != nil {
234
-
log.Printf("failed to decode 'isAncestor': %s", err)
217
+
if err := json.NewDecoder(forkSyncableResp.Body).Decode(&status); err != nil {
218
+
log.Printf("failed to decode fork status: %s", err)
235
219
return nil, err
236
220
}
237
221
238
-
if isAncestor.IsAncestor {
239
-
forkInfo.Status = pages.FastForwardable
240
-
} else {
241
-
forkInfo.Status = pages.Conflict
242
-
}
243
-
222
+
forkInfo.Status = status.Status
244
223
return &forkInfo, nil
245
224
}
246
225
···
1916
1895
}
1917
1896
1918
1897
func (s *State) SyncRepoFork(w http.ResponseWriter, r *http.Request) {
1919
-
user := s.auth.GetUser(r)
1898
+
user := s.oauth.GetUser(r)
1920
1899
f, err := s.fullyResolvedRepo(r)
1921
1900
if err != nil {
1922
1901
log.Printf("failed to resolve source repo: %v", err)
1923
1902
return
1924
1903
}
1925
1904
1926
-
params := r.URL.Query()
1927
-
knot := params.Get("knot")
1928
-
branch := params.Get("branch")
1929
-
1930
1905
switch r.Method {
1931
1906
case http.MethodPost:
1932
-
secret, err := db.GetRegistrationKey(s.db, knot)
1907
+
secret, err := db.GetRegistrationKey(s.db, f.Knot)
1933
1908
if err != nil {
1934
-
s.pages.Notice(w, "repo", fmt.Sprintf("No registration key found for knot %s.", knot))
1909
+
s.pages.Notice(w, "repo", fmt.Sprintf("No registration key found for knot %s.", f.Knot))
1935
1910
return
1936
1911
}
1937
1912
1938
-
client, err := NewSignedClient(knot, secret, s.config.Dev)
1913
+
client, err := knotclient.NewSignedClient(f.Knot, secret, s.config.Core.Dev)
1939
1914
if err != nil {
1940
1915
s.pages.Notice(w, "repo", "Failed to reach knot server.")
1941
1916
return
1942
1917
}
1943
1918
1944
1919
var uri string
1945
-
if s.config.Dev {
1920
+
if s.config.Core.Dev {
1946
1921
uri = "http"
1947
1922
} else {
1948
1923
uri = "https"
···
1950
1925
forkName := fmt.Sprintf("%s", f.RepoName)
1951
1926
forkSourceUrl := fmt.Sprintf("%s://%s/%s/%s", uri, f.Knot, f.OwnerDid(), f.RepoName)
1952
1927
1953
-
_, err = client.SyncRepoFork(user.Did, forkSourceUrl, forkName, branch)
1928
+
_, err = client.SyncRepoFork(user.Did, forkSourceUrl, forkName, f.Ref)
1954
1929
if err != nil {
1955
1930
s.pages.Notice(w, "repo", "Failed to sync repository fork.")
1956
1931
return
+3
-1
appview/state/router.go
+3
-1
appview/state/router.go
···
113
113
r.Use(middleware.AuthMiddleware(s.oauth))
114
114
r.Get("/", s.ForkRepo)
115
115
r.Post("/", s.ForkRepo)
116
-
r.Post("/sync", s.SyncRepoFork)
116
+
r.With(RepoPermissionMiddleware(s, "repo:owner")).Route("/sync", func(r chi.Router) {
117
+
r.Post("/", s.SyncRepoFork)
118
+
})
117
119
})
118
120
119
121
r.Route("/pulls", func(r chi.Router) {
+1
-1
knotserver/handler.go
+1
-1
knotserver/handler.go
+15
-6
knotserver/routes.go
+15
-6
knotserver/routes.go
···
631
631
w.WriteHeader(http.StatusNoContent)
632
632
}
633
633
634
-
func (h *Handle) RepoForkSyncable(w http.ResponseWriter, r *http.Request) {
634
+
func (h *Handle) RepoForkAheadBehind(w http.ResponseWriter, r *http.Request) {
635
635
l := h.l.With("handler", "RepoForkSync")
636
636
637
637
data := struct {
···
689
689
return
690
690
}
691
691
692
-
isAncestor, err := forkCommit.IsAncestor(sourceCommit)
693
-
if err != nil {
694
-
log.Printf("error resolving whether %s is ancestor of %s: %s", branch, data.HiddenRef, err)
695
-
return
692
+
status := types.UpToDate
693
+
if forkCommit.Hash.String() != sourceCommit.Hash.String() {
694
+
isAncestor, err := forkCommit.IsAncestor(sourceCommit)
695
+
if err != nil {
696
+
log.Printf("error resolving whether %s is ancestor of %s: %s", branch, data.HiddenRef, err)
697
+
return
698
+
}
699
+
700
+
if isAncestor {
701
+
status = types.FastForwardable
702
+
} else {
703
+
status = types.Conflict
704
+
}
696
705
}
697
706
698
707
w.Header().Set("Content-Type", "application/json")
699
-
json.NewEncoder(w).Encode(types.AncestorCheckResponse{IsAncestor: isAncestor})
708
+
json.NewEncoder(w).Encode(types.AncestorCheckResponse{Status: status})
700
709
}
701
710
702
711
func (h *Handle) RepoForkSync(w http.ResponseWriter, r *http.Request) {
+15
-1
types/repo.go
+15
-1
types/repo.go
···
91
91
SizeHint uint64 `json:"size_hint,omitempty"`
92
92
}
93
93
94
+
type ForkStatus int
95
+
96
+
const (
97
+
UpToDate ForkStatus = 0
98
+
FastForwardable = 1
99
+
Conflict = 2
100
+
MissingBranch = 3
101
+
)
102
+
103
+
type ForkInfo struct {
104
+
IsFork bool
105
+
Status ForkStatus
106
+
}
107
+
94
108
type AncestorCheckResponse struct {
95
-
IsAncestor bool `json:"isAncestor"`
109
+
Status ForkStatus `json:"status"`
96
110
}