+1
appview/pages/pages.go
+1
appview/pages/pages.go
+12
-9
appview/repo/index.go
+12
-9
appview/repo/index.go
···
24
25
func (rp *Repo) RepoIndex(w http.ResponseWriter, r *http.Request) {
26
ref := chi.URLParam(r, "ref")
27
f, err := rp.repoResolver.Resolve(r)
28
if err != nil {
29
log.Println("failed to fully resolve repo", err)
···
118
119
var forkInfo *types.ForkInfo
120
if user != nil && (repoInfo.Roles.IsOwner() || repoInfo.Roles.IsCollaborator()) {
121
-
forkInfo, err = getForkInfo(repoInfo, rp, f, user, signedClient)
122
if err != nil {
123
log.Printf("Failed to fetch fork information: %v", err)
124
return
···
126
}
127
128
// TODO: a bit dirty
129
-
languageInfo, err := rp.getLanguageInfo(f, signedClient, chi.URLParam(r, "ref") == "")
130
if err != nil {
131
log.Printf("failed to compute language percentages: %s", err)
132
// non-fatal
···
161
func (rp *Repo) getLanguageInfo(
162
f *reporesolver.ResolvedRepo,
163
signedClient *knotclient.SignedClient,
164
isDefaultRef bool,
165
) ([]types.RepoLanguageDetails, error) {
166
// first attempt to fetch from db
167
langs, err := db.GetRepoLanguages(
168
rp.db,
169
db.FilterEq("repo_at", f.RepoAt()),
170
-
db.FilterEq("ref", f.Ref),
171
)
172
173
if err != nil || langs == nil {
174
// non-fatal, fetch langs from ks
175
-
ls, err := signedClient.RepoLanguages(f.OwnerDid(), f.Name, f.Ref)
176
if err != nil {
177
return nil, err
178
}
···
183
for l, s := range ls.Languages {
184
langs = append(langs, db.RepoLanguage{
185
RepoAt: f.RepoAt(),
186
-
Ref: f.Ref,
187
IsDefaultRef: isDefaultRef,
188
Language: l,
189
Bytes: s,
···
234
repoInfo repoinfo.RepoInfo,
235
rp *Repo,
236
f *reporesolver.ResolvedRepo,
237
user *oauth.User,
238
signedClient *knotclient.SignedClient,
239
) (*types.ForkInfo, error) {
···
264
}
265
266
if !slices.ContainsFunc(result.Branches, func(branch types.Branch) bool {
267
-
return branch.Name == f.Ref
268
}) {
269
forkInfo.Status = types.MissingBranch
270
return &forkInfo, nil
271
}
272
273
-
newHiddenRefResp, err := signedClient.NewHiddenRef(user.Did, repoInfo.Name, f.Ref, f.Ref)
274
if err != nil || newHiddenRefResp.StatusCode != http.StatusNoContent {
275
log.Printf("failed to update tracking branch: %s", err)
276
return nil, err
277
}
278
279
-
hiddenRef := fmt.Sprintf("hidden/%s/%s", f.Ref, f.Ref)
280
281
var status types.AncestorCheckResponse
282
-
forkSyncableResp, err := signedClient.RepoForkAheadBehind(user.Did, string(f.RepoAt()), repoInfo.Name, f.Ref, hiddenRef)
283
if err != nil {
284
log.Printf("failed to check if fork is ahead/behind: %s", err)
285
return nil, err
···
24
25
func (rp *Repo) RepoIndex(w http.ResponseWriter, r *http.Request) {
26
ref := chi.URLParam(r, "ref")
27
+
28
f, err := rp.repoResolver.Resolve(r)
29
if err != nil {
30
log.Println("failed to fully resolve repo", err)
···
119
120
var forkInfo *types.ForkInfo
121
if user != nil && (repoInfo.Roles.IsOwner() || repoInfo.Roles.IsCollaborator()) {
122
+
forkInfo, err = getForkInfo(repoInfo, rp, f, result.Ref, user, signedClient)
123
if err != nil {
124
log.Printf("Failed to fetch fork information: %v", err)
125
return
···
127
}
128
129
// TODO: a bit dirty
130
+
languageInfo, err := rp.getLanguageInfo(f, signedClient, result.Ref, ref == "")
131
if err != nil {
132
log.Printf("failed to compute language percentages: %s", err)
133
// non-fatal
···
162
func (rp *Repo) getLanguageInfo(
163
f *reporesolver.ResolvedRepo,
164
signedClient *knotclient.SignedClient,
165
+
currentRef string,
166
isDefaultRef bool,
167
) ([]types.RepoLanguageDetails, error) {
168
// first attempt to fetch from db
169
langs, err := db.GetRepoLanguages(
170
rp.db,
171
db.FilterEq("repo_at", f.RepoAt()),
172
+
db.FilterEq("ref", currentRef),
173
)
174
175
if err != nil || langs == nil {
176
// non-fatal, fetch langs from ks
177
+
ls, err := signedClient.RepoLanguages(f.OwnerDid(), f.Name, currentRef)
178
if err != nil {
179
return nil, err
180
}
···
185
for l, s := range ls.Languages {
186
langs = append(langs, db.RepoLanguage{
187
RepoAt: f.RepoAt(),
188
+
Ref: currentRef,
189
IsDefaultRef: isDefaultRef,
190
Language: l,
191
Bytes: s,
···
236
repoInfo repoinfo.RepoInfo,
237
rp *Repo,
238
f *reporesolver.ResolvedRepo,
239
+
currentRef string,
240
user *oauth.User,
241
signedClient *knotclient.SignedClient,
242
) (*types.ForkInfo, error) {
···
267
}
268
269
if !slices.ContainsFunc(result.Branches, func(branch types.Branch) bool {
270
+
return branch.Name == currentRef
271
}) {
272
forkInfo.Status = types.MissingBranch
273
return &forkInfo, nil
274
}
275
276
+
newHiddenRefResp, err := signedClient.NewHiddenRef(user.Did, repoInfo.Name, currentRef, currentRef)
277
if err != nil || newHiddenRefResp.StatusCode != http.StatusNoContent {
278
log.Printf("failed to update tracking branch: %s", err)
279
return nil, err
280
}
281
282
+
hiddenRef := fmt.Sprintf("hidden/%s/%s", currentRef, currentRef)
283
284
var status types.AncestorCheckResponse
285
+
forkSyncableResp, err := signedClient.RepoForkAheadBehind(user.Did, string(f.RepoAt()), repoInfo.Name, currentRef, hiddenRef)
286
if err != nil {
287
log.Printf("failed to check if fork is ahead/behind: %s", err)
288
return nil, err
+3
-1
appview/repo/repo.go
+3
-1
appview/repo/repo.go
···
1315
}
1316
1317
func (rp *Repo) SyncRepoFork(w http.ResponseWriter, r *http.Request) {
1318
user := rp.oauth.GetUser(r)
1319
f, err := rp.repoResolver.Resolve(r)
1320
if err != nil {
···
1345
forkName := fmt.Sprintf("%s", f.Name)
1346
forkSourceUrl := fmt.Sprintf("%s://%s/%s/%s", uri, f.Knot, f.OwnerDid(), f.Repo.Name)
1347
1348
-
_, err = client.SyncRepoFork(user.Did, forkSourceUrl, forkName, f.Ref)
1349
if err != nil {
1350
rp.pages.Notice(w, "repo", "Failed to sync repository fork.")
1351
return
···
1315
}
1316
1317
func (rp *Repo) SyncRepoFork(w http.ResponseWriter, r *http.Request) {
1318
+
ref := chi.URLParam(r, "ref")
1319
+
1320
user := rp.oauth.GetUser(r)
1321
f, err := rp.repoResolver.Resolve(r)
1322
if err != nil {
···
1347
forkName := fmt.Sprintf("%s", f.Name)
1348
forkSourceUrl := fmt.Sprintf("%s://%s/%s/%s", uri, f.Knot, f.OwnerDid(), f.Repo.Name)
1349
1350
+
_, err = client.SyncRepoFork(user.Did, forkSourceUrl, forkName, ref)
1351
if err != nil {
1352
rp.pages.Notice(w, "repo", "Failed to sync repository fork.")
1353
return
+18
-39
appview/reporesolver/resolver.go
+18
-39
appview/reporesolver/resolver.go
···
7
"fmt"
8
"log"
9
"net/http"
10
-
"net/url"
11
"path"
12
"strings"
13
14
"github.com/bluesky-social/indigo/atproto/identity"
···
26
27
type ResolvedRepo struct {
28
db.Repo
29
-
OwnerId identity.Identity
30
-
Ref string
31
-
CurrentDir string
32
33
rr *RepoResolver
34
}
···
56
return nil, fmt.Errorf("malformed middleware")
57
}
58
59
ref := chi.URLParam(r, "ref")
60
61
-
if ref == "" {
62
-
us, err := knotclient.NewUnsignedClient(repo.Knot, rr.config.Core.Dev)
63
-
if err != nil {
64
-
return nil, err
65
-
}
66
-
67
-
defaultBranch, err := us.DefaultBranch(id.DID.String(), repo.Name)
68
-
if err != nil {
69
-
return nil, err
70
-
}
71
-
72
-
ref = defaultBranch.Branch
73
-
}
74
-
75
-
currentDir := path.Dir(extractPathAfterRef(r.URL.EscapedPath(), ref))
76
-
77
return &ResolvedRepo{
78
Repo: *repo,
79
OwnerId: id,
80
-
Ref: ref,
81
CurrentDir: currentDir,
82
83
rr: rr,
84
}, nil
···
200
if err != nil {
201
log.Printf("failed to create unsigned client for %s: %v", knot, err)
202
} else {
203
-
result, err := us.Branches(f.OwnerDid(), f.Name)
204
-
if err != nil {
205
log.Printf("failed to get branches for %s/%s: %v", f.OwnerDid(), f.Name, err)
206
-
}
207
-
208
-
if len(result.Branches) == 0 {
209
disableFork = true
210
}
211
}
···
216
Name: f.Name,
217
RepoAt: repoAt,
218
Description: f.Description,
219
-
Ref: f.Ref,
220
IsStarred: isStarred,
221
Knot: knot,
222
Spindle: f.Spindle,
···
228
},
229
DisableFork: disableFork,
230
CurrentDir: f.CurrentDir,
231
}
232
233
if sourceRepo != nil {
···
251
// after the ref. for example:
252
//
253
// /@icyphox.sh/foorepo/blob/main/abc/xyz/ => abc/xyz/
254
-
func extractPathAfterRef(fullPath, ref string) string {
255
fullPath = strings.TrimPrefix(fullPath, "/")
256
257
-
ref = url.PathEscape(ref)
258
259
-
prefixes := []string{
260
-
fmt.Sprintf("blob/%s/", ref),
261
-
fmt.Sprintf("tree/%s/", ref),
262
-
fmt.Sprintf("raw/%s/", ref),
263
-
}
264
265
-
for _, prefix := range prefixes {
266
-
idx := strings.Index(fullPath, prefix)
267
-
if idx != -1 {
268
-
return fullPath[idx+len(prefix):]
269
-
}
270
}
271
272
return ""
···
7
"fmt"
8
"log"
9
"net/http"
10
"path"
11
+
"regexp"
12
"strings"
13
14
"github.com/bluesky-social/indigo/atproto/identity"
···
26
27
type ResolvedRepo struct {
28
db.Repo
29
+
OwnerId identity.Identity
30
+
CurrentDir string
31
+
Ref string
32
33
rr *RepoResolver
34
}
···
56
return nil, fmt.Errorf("malformed middleware")
57
}
58
59
+
currentDir := path.Dir(extractPathAfterRef(r.URL.EscapedPath()))
60
ref := chi.URLParam(r, "ref")
61
62
return &ResolvedRepo{
63
Repo: *repo,
64
OwnerId: id,
65
CurrentDir: currentDir,
66
+
Ref: ref,
67
68
rr: rr,
69
}, nil
···
185
if err != nil {
186
log.Printf("failed to create unsigned client for %s: %v", knot, err)
187
} else {
188
+
if result, err := us.Branches(f.OwnerDid(), f.Name); err != nil {
189
log.Printf("failed to get branches for %s/%s: %v", f.OwnerDid(), f.Name, err)
190
+
} else if len(result.Branches) == 0 {
191
disableFork = true
192
}
193
}
···
198
Name: f.Name,
199
RepoAt: repoAt,
200
Description: f.Description,
201
IsStarred: isStarred,
202
Knot: knot,
203
Spindle: f.Spindle,
···
209
},
210
DisableFork: disableFork,
211
CurrentDir: f.CurrentDir,
212
+
Ref: f.Ref,
213
}
214
215
if sourceRepo != nil {
···
233
// after the ref. for example:
234
//
235
// /@icyphox.sh/foorepo/blob/main/abc/xyz/ => abc/xyz/
236
+
func extractPathAfterRef(fullPath string) string {
237
fullPath = strings.TrimPrefix(fullPath, "/")
238
239
+
// match blob/, tree/, or raw/ followed by any ref and then a slash
240
+
//
241
+
// captures everything after the final slash
242
+
pattern := `(?:blob|tree|raw)/[^/]+/(.*)$`
243
244
+
re := regexp.MustCompile(pattern)
245
+
matches := re.FindStringSubmatch(fullPath)
246
247
+
if len(matches) > 1 {
248
+
return matches[1]
249
}
250
251
return ""
-19
appview/state/profile.go
-19
appview/state/profile.go
···
89
log.Printf("failed to create profile timeline for %s: %s", ident.DID.String(), err)
90
}
91
92
-
var didsToResolve []string
93
-
for _, r := range collaboratingRepos {
94
-
didsToResolve = append(didsToResolve, r.Did)
95
-
}
96
-
for _, byMonth := range timeline.ByMonth {
97
-
for _, pe := range byMonth.PullEvents.Items {
98
-
didsToResolve = append(didsToResolve, pe.Repo.Did)
99
-
}
100
-
for _, ie := range byMonth.IssueEvents.Items {
101
-
didsToResolve = append(didsToResolve, ie.Metadata.Repo.Did)
102
-
}
103
-
for _, re := range byMonth.RepoEvents {
104
-
didsToResolve = append(didsToResolve, re.Repo.Did)
105
-
if re.Source != nil {
106
-
didsToResolve = append(didsToResolve, re.Source.Did)
107
-
}
108
-
}
109
-
}
110
-
111
followers, following, err := db.GetFollowerFollowingCount(s.db, ident.DID.String())
112
if err != nil {
113
log.Printf("getting follow stats repos for %s: %s", ident.DID.String(), err)
+4
-2
knotserver/git/fork.go
+4
-2
knotserver/git/fork.go
+2
-2
knotserver/handler.go
+2
-2
knotserver/handler.go
+4
-4
knotserver/routes.go
+4
-4
knotserver/routes.go
···
710
}
711
712
func (h *Handle) RepoForkAheadBehind(w http.ResponseWriter, r *http.Request) {
713
-
l := h.l.With("handler", "RepoForkSync")
714
715
data := struct {
716
Did string `json:"did"`
···
845
name = filepath.Base(source)
846
}
847
848
-
branch := chi.URLParam(r, "branch")
849
branch, _ = url.PathUnescape(branch)
850
851
relativeRepoPath := filepath.Join(did, name)
852
repoPath, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, relativeRepoPath)
853
854
-
gr, err := git.PlainOpen(repoPath)
855
if err != nil {
856
log.Println(err)
857
notFound(w)
858
return
859
}
860
861
-
err = gr.Sync(branch)
862
if err != nil {
863
l.Error("error syncing repo fork", "error", err.Error())
864
writeError(w, err.Error(), http.StatusInternalServerError)
···
710
}
711
712
func (h *Handle) RepoForkAheadBehind(w http.ResponseWriter, r *http.Request) {
713
+
l := h.l.With("handler", "RepoForkAheadBehind")
714
715
data := struct {
716
Did string `json:"did"`
···
845
name = filepath.Base(source)
846
}
847
848
+
branch := chi.URLParam(r, "*")
849
branch, _ = url.PathUnescape(branch)
850
851
relativeRepoPath := filepath.Join(did, name)
852
repoPath, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, relativeRepoPath)
853
854
+
gr, err := git.Open(repoPath, branch)
855
if err != nil {
856
log.Println(err)
857
notFound(w)
858
return
859
}
860
861
+
err = gr.Sync()
862
if err != nil {
863
l.Error("error syncing repo fork", "error", err.Error())
864
writeError(w, err.Error(), http.StatusInternalServerError)