forked from tangled.org/core
this repo has no description

appview: state: introduce get route to check if fork is syncable

Changed files
+79 -72
appview
knotclient
pages
state
knotserver
types
+23 -2
appview/knotclient/signer.go
···
return s.client.Do(req)
}
func (s *SignedClient) SyncRepoFork(ownerDid, source, name, branch string) (*http.Response, error) {
const (
-
Method = "POST"
)
-
endpoint := fmt.Sprintf("/repo/fork/sync/%s", branch)
body, _ := json.Marshal(map[string]any{
"did": ownerDid,
···
return s.client.Do(req)
}
+
func (s *SignedClient) RepoForkAheadBehind(ownerDid, source, name, branch, hiddenRef string) (*http.Response, error) {
+
const (
+
Method = "GET"
+
)
+
endpoint := fmt.Sprintf("/repo/fork/sync/%s", url.PathEscape(branch))
+
+
body, _ := json.Marshal(map[string]any{
+
"did": ownerDid,
+
"source": source,
+
"name": name,
+
"hiddenref": hiddenRef,
+
})
+
+
req, err := s.newRequest(Method, endpoint, body)
+
if err != nil {
+
return nil, err
+
}
+
+
return s.client.Do(req)
+
}
+
func (s *SignedClient) SyncRepoFork(ownerDid, source, name, branch string) (*http.Response, error) {
const (
+
Method = "POST"
)
+
endpoint := fmt.Sprintf("/repo/fork/sync/%s", url.PathEscape(branch))
body, _ := json.Marshal(map[string]any{
"did": ownerDid,
+1 -15
appview/pages/pages.go
···
return p.executePlain("repo/fragments/repoDescription", w, params)
}
-
type ForkStatus int
-
-
const (
-
UpToDate ForkStatus = 0
-
FastForwardable = 1
-
Conflict = 2
-
MissingBranch = 3
-
)
-
-
type ForkInfo struct {
-
IsFork bool
-
Status ForkStatus
-
}
-
type RepoIndexParams struct {
LoggedInUser *oauth.User
RepoInfo repoinfo.RepoInfo
···
CommitsTrunc []*object.Commit
TagsTrunc []*types.TagReference
BranchesTrunc []types.Branch
-
ForkInfo ForkInfo
types.RepoIndexResponse
HTMLReadme template.HTML
Raw bool
···
return p.executePlain("repo/fragments/repoDescription", w, params)
}
type RepoIndexParams struct {
LoggedInUser *oauth.User
RepoInfo repoinfo.RepoInfo
···
CommitsTrunc []*object.Commit
TagsTrunc []*types.TagReference
BranchesTrunc []types.Branch
+
ForkInfo types.ForkInfo
types.RepoIndexResponse
HTMLReadme template.HTML
Raw bool
+21 -46
appview/state/repo.go
···
"tangled.sh/tangled.sh/core/appview/pages/markup"
"tangled.sh/tangled.sh/core/appview/pages/repoinfo"
"tangled.sh/tangled.sh/core/appview/pagination"
-
"tangled.sh/tangled.sh/core/knotserver"
"tangled.sh/tangled.sh/core/types"
"github.com/bluesky-social/indigo/atproto/data"
···
user := s.oauth.GetUser(r)
repoInfo := f.RepoInfo(s, user)
-
forkInfo, err := GetForkInfo(repoInfo, s, f, us, w, user)
if err != nil {
log.Printf("Failed to fetch fork information: %v", err)
return
···
return
}
-
func GetForkInfo(
repoInfo repoinfo.RepoInfo,
s *State,
f *FullyResolvedRepo,
us *knotclient.UnsignedClient,
w http.ResponseWriter,
user *oauth.User,
-
) (*pages.ForkInfo, error) {
-
forkInfo := pages.ForkInfo{
IsFork: repoInfo.Source != nil,
-
Status: pages.UpToDate,
}
secret, err := db.GetRegistrationKey(s.db, f.Knot)
···
return nil, err
}
-
var contains = false
-
for _, branch := range result.Branches {
-
if branch.Name == f.Ref {
-
contains = true
-
break
-
}
-
}
-
-
if contains == false {
-
forkInfo.Status = pages.MissingBranch
return &forkInfo, nil
}
···
}
hiddenRef := fmt.Sprintf("hidden/%s/%s", f.Ref, f.Ref)
-
comparison, err := us.Compare(user.Did, repoInfo.Name, f.Ref, hiddenRef)
-
if err != nil {
-
log.Printf("failed to compare branches '%s' and '%s': %s", f.Ref, hiddenRef, err)
-
return nil, err
-
}
-
if len(comparison.FormatPatch) == 0 {
-
return &forkInfo, nil
-
}
-
-
var isAncestor types.AncestorCheckResponse
-
forkSyncableResp, err := signedClient.RepoForkSyncable(user.Did, string(f.RepoAt), repoInfo.Name, f.Ref, hiddenRef)
if err != nil {
-
log.Printf("failed to check if fork is syncable: %s", err)
return nil, err
}
-
if err := json.NewDecoder(forkSyncableResp.Body).Decode(&isAncestor); err != nil {
-
log.Printf("failed to decode 'isAncestor': %s", err)
return nil, err
}
-
if isAncestor.IsAncestor {
-
forkInfo.Status = pages.FastForwardable
-
} else {
-
forkInfo.Status = pages.Conflict
-
}
-
return &forkInfo, nil
}
···
}
func (s *State) SyncRepoFork(w http.ResponseWriter, r *http.Request) {
-
user := s.auth.GetUser(r)
f, err := s.fullyResolvedRepo(r)
if err != nil {
log.Printf("failed to resolve source repo: %v", err)
return
}
-
params := r.URL.Query()
-
knot := params.Get("knot")
-
branch := params.Get("branch")
-
switch r.Method {
case http.MethodPost:
-
secret, err := db.GetRegistrationKey(s.db, knot)
if err != nil {
-
s.pages.Notice(w, "repo", fmt.Sprintf("No registration key found for knot %s.", knot))
return
}
-
client, err := NewSignedClient(knot, secret, s.config.Dev)
if err != nil {
s.pages.Notice(w, "repo", "Failed to reach knot server.")
return
}
var uri string
-
if s.config.Dev {
uri = "http"
} else {
uri = "https"
···
forkName := fmt.Sprintf("%s", f.RepoName)
forkSourceUrl := fmt.Sprintf("%s://%s/%s/%s", uri, f.Knot, f.OwnerDid(), f.RepoName)
-
_, err = client.SyncRepoFork(user.Did, forkSourceUrl, forkName, branch)
if err != nil {
s.pages.Notice(w, "repo", "Failed to sync repository fork.")
return
···
"tangled.sh/tangled.sh/core/appview/pages/markup"
"tangled.sh/tangled.sh/core/appview/pages/repoinfo"
"tangled.sh/tangled.sh/core/appview/pagination"
"tangled.sh/tangled.sh/core/types"
"github.com/bluesky-social/indigo/atproto/data"
···
user := s.oauth.GetUser(r)
repoInfo := f.RepoInfo(s, user)
+
forkInfo, err := getForkInfo(repoInfo, s, f, us, w, user)
if err != nil {
log.Printf("Failed to fetch fork information: %v", err)
return
···
return
}
+
func getForkInfo(
repoInfo repoinfo.RepoInfo,
s *State,
f *FullyResolvedRepo,
us *knotclient.UnsignedClient,
w http.ResponseWriter,
user *oauth.User,
+
) (*types.ForkInfo, error) {
+
forkInfo := types.ForkInfo{
IsFork: repoInfo.Source != nil,
+
Status: types.UpToDate,
}
secret, err := db.GetRegistrationKey(s.db, f.Knot)
···
return nil, err
}
+
if !slices.ContainsFunc(result.Branches, func(branch types.Branch) bool {
+
return branch.Name == f.Ref
+
}) {
+
forkInfo.Status = types.MissingBranch
return &forkInfo, nil
}
···
}
hiddenRef := fmt.Sprintf("hidden/%s/%s", f.Ref, f.Ref)
+
var status types.AncestorCheckResponse
+
forkSyncableResp, err := signedClient.RepoForkAheadBehind(user.Did, string(f.RepoAt), repoInfo.Name, f.Ref, hiddenRef)
if err != nil {
+
log.Printf("failed to check if fork is ahead/behind: %s", err)
return nil, err
}
+
if err := json.NewDecoder(forkSyncableResp.Body).Decode(&status); err != nil {
+
log.Printf("failed to decode fork status: %s", err)
return nil, err
}
+
forkInfo.Status = status.Status
return &forkInfo, nil
}
···
}
func (s *State) SyncRepoFork(w http.ResponseWriter, r *http.Request) {
+
user := s.oauth.GetUser(r)
f, err := s.fullyResolvedRepo(r)
if err != nil {
log.Printf("failed to resolve source repo: %v", err)
return
}
switch r.Method {
case http.MethodPost:
+
secret, err := db.GetRegistrationKey(s.db, f.Knot)
if err != nil {
+
s.pages.Notice(w, "repo", fmt.Sprintf("No registration key found for knot %s.", f.Knot))
return
}
+
client, err := knotclient.NewSignedClient(f.Knot, secret, s.config.Core.Dev)
if err != nil {
s.pages.Notice(w, "repo", "Failed to reach knot server.")
return
}
var uri string
+
if s.config.Core.Dev {
uri = "http"
} else {
uri = "https"
···
forkName := fmt.Sprintf("%s", f.RepoName)
forkSourceUrl := fmt.Sprintf("%s://%s/%s/%s", uri, f.Knot, f.OwnerDid(), f.RepoName)
+
_, err = client.SyncRepoFork(user.Did, forkSourceUrl, forkName, f.Ref)
if err != nil {
s.pages.Notice(w, "repo", "Failed to sync repository fork.")
return
+3 -1
appview/state/router.go
···
r.Use(middleware.AuthMiddleware(s.oauth))
r.Get("/", s.ForkRepo)
r.Post("/", s.ForkRepo)
-
r.Post("/sync", s.SyncRepoFork)
})
r.Route("/pulls", func(r chi.Router) {
···
r.Use(middleware.AuthMiddleware(s.oauth))
r.Get("/", s.ForkRepo)
r.Post("/", s.ForkRepo)
+
r.With(RepoPermissionMiddleware(s, "repo:owner")).Route("/sync", func(r chi.Router) {
+
r.Post("/", s.SyncRepoFork)
+
})
})
r.Route("/pulls", func(r chi.Router) {
+1 -1
knotserver/handler.go
···
r.Route("/fork", func(r chi.Router) {
r.Post("/", h.RepoFork)
r.Post("/sync/{branch}", h.RepoForkSync)
-
r.Get("/sync/{branch}", h.RepoForkSyncable)
})
})
···
r.Route("/fork", func(r chi.Router) {
r.Post("/", h.RepoFork)
r.Post("/sync/{branch}", h.RepoForkSync)
+
r.Get("/sync/{branch}", h.RepoForkAheadBehind)
})
})
+15 -6
knotserver/routes.go
···
w.WriteHeader(http.StatusNoContent)
}
-
func (h *Handle) RepoForkSyncable(w http.ResponseWriter, r *http.Request) {
l := h.l.With("handler", "RepoForkSync")
data := struct {
···
return
}
-
isAncestor, err := forkCommit.IsAncestor(sourceCommit)
-
if err != nil {
-
log.Printf("error resolving whether %s is ancestor of %s: %s", branch, data.HiddenRef, err)
-
return
}
w.Header().Set("Content-Type", "application/json")
-
json.NewEncoder(w).Encode(types.AncestorCheckResponse{IsAncestor: isAncestor})
}
func (h *Handle) RepoForkSync(w http.ResponseWriter, r *http.Request) {
···
w.WriteHeader(http.StatusNoContent)
}
+
func (h *Handle) RepoForkAheadBehind(w http.ResponseWriter, r *http.Request) {
l := h.l.With("handler", "RepoForkSync")
data := struct {
···
return
}
+
status := types.UpToDate
+
if forkCommit.Hash.String() != sourceCommit.Hash.String() {
+
isAncestor, err := forkCommit.IsAncestor(sourceCommit)
+
if err != nil {
+
log.Printf("error resolving whether %s is ancestor of %s: %s", branch, data.HiddenRef, err)
+
return
+
}
+
+
if isAncestor {
+
status = types.FastForwardable
+
} else {
+
status = types.Conflict
+
}
}
w.Header().Set("Content-Type", "application/json")
+
json.NewEncoder(w).Encode(types.AncestorCheckResponse{Status: status})
}
func (h *Handle) RepoForkSync(w http.ResponseWriter, r *http.Request) {
+15 -1
types/repo.go
···
SizeHint uint64 `json:"size_hint,omitempty"`
}
type AncestorCheckResponse struct {
-
IsAncestor bool `json:"isAncestor"`
}
···
SizeHint uint64 `json:"size_hint,omitempty"`
}
+
type ForkStatus int
+
+
const (
+
UpToDate ForkStatus = 0
+
FastForwardable = 1
+
Conflict = 2
+
MissingBranch = 3
+
)
+
+
type ForkInfo struct {
+
IsFork bool
+
Status ForkStatus
+
}
+
type AncestorCheckResponse struct {
+
Status ForkStatus `json:"status"`
}