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