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

feature: ability to change default branch for a repo

closes issue #52

Changed files
+211 -1
appview
pages
templates
state
knotserver
types
+2
appview/pages/pages.go
···
RepoInfo RepoInfo
Collaborators []Collaborator
Active string
+
Branches []string
+
DefaultBranch string
// TODO: use repoinfo.roles
IsCollaboratorInviteAllowed bool
}
+18
appview/pages/templates/repo/settings.html
···
<button class="btn my-2 dark:text-white dark:hover:bg-gray-700" type="text">add collaborator</button>
</form>
{{ end }}
+
+
<form hx-put="/{{ $.RepoInfo.FullName }}/settings/branches/default" class="mt-6">
+
<label for="branch">default branch:</label>
+
<select id="branch" name="branch" required class="p-1 border border-gray-200 bg-white">
+
{{ range .Branches }}
+
<option
+
value="{{ . }}"
+
class="py-1"
+
{{ if eq . $.DefaultBranch }}
+
selected
+
{{ end }}
+
>
+
{{ . }}
+
</option>
+
{{ end }}
+
</select>
+
<button class="btn my-2" type="text">save</button>
+
</form>
{{ end }}
+90
appview/state/repo.go
···
}
+
func (s *State) SetDefaultBranch(w http.ResponseWriter, r *http.Request) {
+
f, err := fullyResolvedRepo(r)
+
if err != nil {
+
log.Println("failed to get repo and knot", err)
+
return
+
}
+
+
branch := r.FormValue("branch")
+
if branch == "" {
+
http.Error(w, "malformed form", http.StatusBadRequest)
+
return
+
}
+
+
secret, err := db.GetRegistrationKey(s.db, f.Knot)
+
if err != nil {
+
log.Printf("no key found for domain %s: %s\n", f.Knot, err)
+
return
+
}
+
+
ksClient, err := NewSignedClient(f.Knot, secret, s.config.Dev)
+
if err != nil {
+
log.Println("failed to create client to ", f.Knot)
+
return
+
}
+
+
ksResp, err := ksClient.SetDefaultBranch(f.OwnerDid(), f.RepoName, branch)
+
if err != nil {
+
log.Printf("failed to make request to %s: %s", f.Knot, err)
+
return
+
}
+
+
if ksResp.StatusCode != http.StatusNoContent {
+
s.pages.Notice(w, "repo-settings", "Failed to set default branch. Try again later.")
+
return
+
}
+
+
w.Write([]byte(fmt.Sprint("default branch set to: ", branch)))
+
}
+
func (s *State) RepoSettings(w http.ResponseWriter, r *http.Request) {
f, err := fullyResolvedRepo(r)
if err != nil {
···
}
}
+
var branchNames []string
+
var defaultBranch string
+
us, err := NewUnsignedClient(f.Knot, s.config.Dev)
+
if err != nil {
+
log.Println("failed to create unsigned client", err)
+
} else {
+
resp, err := us.Branches(f.OwnerDid(), f.RepoName)
+
if err != nil {
+
log.Println("failed to reach knotserver", err)
+
} else {
+
defer resp.Body.Close()
+
+
body, err := io.ReadAll(resp.Body)
+
if err != nil {
+
log.Printf("Error reading response body: %v", err)
+
} else {
+
var result types.RepoBranchesResponse
+
err = json.Unmarshal(body, &result)
+
if err != nil {
+
log.Println("failed to parse response:", err)
+
} else {
+
for _, branch := range result.Branches {
+
branchNames = append(branchNames, branch.Name)
+
}
+
}
+
}
+
}
+
+
resp, err = us.DefaultBranch(f.OwnerDid(), f.RepoName)
+
if err != nil {
+
log.Println("failed to reach knotserver", err)
+
} else {
+
defer resp.Body.Close()
+
+
body, err := io.ReadAll(resp.Body)
+
if err != nil {
+
log.Printf("Error reading response body: %v", err)
+
} else {
+
var result types.RepoDefaultBranchResponse
+
err = json.Unmarshal(body, &result)
+
if err != nil {
+
log.Println("failed to parse response:", err)
+
} else {
+
defaultBranch = result.Branch
+
}
+
}
+
}
+
}
+
s.pages.RepoSettings(w, pages.RepoSettingsParams{
LoggedInUser: user,
RepoInfo: f.RepoInfo(s, user),
Collaborators: repoCollaborators,
IsCollaboratorInviteAllowed: isCollaboratorInviteAllowed,
+
Branches: branchNames,
+
DefaultBranch: defaultBranch,
})
}
}
+1
appview/state/router.go
···
r.With(RepoPermissionMiddleware(s, "repo:settings")).Route("/settings", func(r chi.Router) {
r.Get("/", s.RepoSettings)
r.With(RepoPermissionMiddleware(s, "repo:invite")).Put("/collaborator", s.AddCollaborator)
+
r.Put("/branches/default", s.SetDefaultBranch)
})
})
})
+33
appview/state/signer.go
···
return s.client.Do(req)
}
+
func (s *SignedClient) SetDefaultBranch(ownerDid, repoName, branch string) (*http.Response, error) {
+
const (
+
Method = "PUT"
+
)
+
endpoint := fmt.Sprintf("/%s/%s/branches/default", ownerDid, repoName)
+
+
body, _ := json.Marshal(map[string]any{
+
"branch": branch,
+
})
+
+
req, err := s.newRequest(Method, endpoint, body)
+
if err != nil {
+
return nil, err
+
}
+
+
return s.client.Do(req)
+
}
+
func (s *SignedClient) AddCollaborator(ownerDid, repoName, memberDid string) (*http.Response, error) {
const (
Method = "POST"
···
return us.client.Do(req)
}
+
+
func (us *UnsignedClient) DefaultBranch(ownerDid, repoName string) (*http.Response, error) {
+
const (
+
Method = "GET"
+
)
+
+
endpoint := fmt.Sprintf("/%s/%s/branches/default", ownerDid, repoName)
+
+
req, err := us.newRequest(Method, endpoint, nil)
+
if err != nil {
+
return nil, err
+
}
+
+
return us.client.Do(req)
+
}
+5
knotserver/git/git.go
···
return branches, nil
}
+
func (g *GitRepo) SetDefaultBranch(branch string) error {
+
ref := plumbing.NewSymbolicReference(plumbing.HEAD, plumbing.NewBranchReferenceName(branch))
+
return g.r.Storer.SetReference(ref)
+
}
+
func (g *GitRepo) FindMainBranch() (string, error) {
ref, err := g.r.Head()
if err != nil {
+7 -1
knotserver/handler.go
···
r.Get("/archive/{file}", h.Archive)
r.Get("/commit/{ref}", h.Diff)
r.Get("/tags", h.Tags)
-
r.Get("/branches", h.Branches)
+
r.Route("/branches", func(r chi.Router) {
+
r.Get("/", h.Branches)
+
r.Route("/default", func(r chi.Router) {
+
r.Get("/", h.DefaultBranch)
+
r.With(h.VerifySignature).Put("/", h.SetDefaultBranch)
+
})
+
})
})
})
+51
knotserver/routes.go
···
w.WriteHeader(http.StatusNoContent)
}
+
func (h *Handle) DefaultBranch(w http.ResponseWriter, r *http.Request) {
+
l := h.l.With("handler", "DefaultBranch")
+
path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r))
+
+
gr, err := git.Open(path, "")
+
if err != nil {
+
notFound(w)
+
return
+
}
+
+
branch, err := gr.FindMainBranch()
+
if err != nil {
+
writeError(w, err.Error(), http.StatusInternalServerError)
+
l.Error("getting default branch", "error", err.Error())
+
return
+
}
+
+
writeJSON(w, types.RepoDefaultBranchResponse{
+
Branch: branch,
+
})
+
}
+
+
func (h *Handle) SetDefaultBranch(w http.ResponseWriter, r *http.Request) {
+
l := h.l.With("handler", "SetDefaultBranch")
+
path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r))
+
+
data := struct {
+
Branch string `json:"branch"`
+
}{}
+
+
if err := json.NewDecoder(r.Body).Decode(&data); err != nil {
+
writeError(w, err.Error(), http.StatusBadRequest)
+
return
+
}
+
+
gr, err := git.Open(path, "")
+
if err != nil {
+
notFound(w)
+
return
+
}
+
+
err = gr.SetDefaultBranch(data.Branch)
+
if err != nil {
+
writeError(w, err.Error(), http.StatusInternalServerError)
+
l.Error("setting default branch", "error", err.Error())
+
return
+
}
+
+
w.WriteHeader(http.StatusNoContent)
+
}
+
func (h *Handle) Init(w http.ResponseWriter, r *http.Request) {
l := h.l.With("handler", "Init")
+4
types/repo.go
···
Branches []Branch `json:"branches,omitempty"`
}
+
type RepoDefaultBranchResponse struct {
+
Branch string `json:"branch,omitempty"`
+
}
+
type RepoBlobResponse struct {
Contents string `json:"contents,omitempty"`
Ref string `json:"ref,omitempty"`