forked from tangled.org/core
Monorepo for Tangled

feature: ability to change default branch for a repo

closes issue #52

authored by dvjn.dev and committed by Tangled a4599698 5fc76ee3

Changed files
+211 -1
appview
pages
templates
state
knotserver
types
+2
appview/pages/pages.go
··· 478 478 RepoInfo RepoInfo 479 479 Collaborators []Collaborator 480 480 Active string 481 + Branches []string 482 + DefaultBranch string 481 483 // TODO: use repoinfo.roles 482 484 IsCollaboratorInviteAllowed bool 483 485 }
+18
appview/pages/templates/repo/settings.html
··· 28 28 <button class="btn my-2 dark:text-white dark:hover:bg-gray-700" type="text">add collaborator</button> 29 29 </form> 30 30 {{ end }} 31 + 32 + <form hx-put="/{{ $.RepoInfo.FullName }}/settings/branches/default" class="mt-6"> 33 + <label for="branch">default branch:</label> 34 + <select id="branch" name="branch" required class="p-1 border border-gray-200 bg-white"> 35 + {{ range .Branches }} 36 + <option 37 + value="{{ . }}" 38 + class="py-1" 39 + {{ if eq . $.DefaultBranch }} 40 + selected 41 + {{ end }} 42 + > 43 + {{ . }} 44 + </option> 45 + {{ end }} 46 + </select> 47 + <button class="btn my-2" type="text">save</button> 48 + </form> 31 49 {{ end }}
+90
appview/state/repo.go
··· 594 594 595 595 } 596 596 597 + func (s *State) SetDefaultBranch(w http.ResponseWriter, r *http.Request) { 598 + f, err := fullyResolvedRepo(r) 599 + if err != nil { 600 + log.Println("failed to get repo and knot", err) 601 + return 602 + } 603 + 604 + branch := r.FormValue("branch") 605 + if branch == "" { 606 + http.Error(w, "malformed form", http.StatusBadRequest) 607 + return 608 + } 609 + 610 + secret, err := db.GetRegistrationKey(s.db, f.Knot) 611 + if err != nil { 612 + log.Printf("no key found for domain %s: %s\n", f.Knot, err) 613 + return 614 + } 615 + 616 + ksClient, err := NewSignedClient(f.Knot, secret, s.config.Dev) 617 + if err != nil { 618 + log.Println("failed to create client to ", f.Knot) 619 + return 620 + } 621 + 622 + ksResp, err := ksClient.SetDefaultBranch(f.OwnerDid(), f.RepoName, branch) 623 + if err != nil { 624 + log.Printf("failed to make request to %s: %s", f.Knot, err) 625 + return 626 + } 627 + 628 + if ksResp.StatusCode != http.StatusNoContent { 629 + s.pages.Notice(w, "repo-settings", "Failed to set default branch. Try again later.") 630 + return 631 + } 632 + 633 + w.Write([]byte(fmt.Sprint("default branch set to: ", branch))) 634 + } 635 + 597 636 func (s *State) RepoSettings(w http.ResponseWriter, r *http.Request) { 598 637 f, err := fullyResolvedRepo(r) 599 638 if err != nil { ··· 618 657 } 619 658 } 620 659 660 + var branchNames []string 661 + var defaultBranch string 662 + us, err := NewUnsignedClient(f.Knot, s.config.Dev) 663 + if err != nil { 664 + log.Println("failed to create unsigned client", err) 665 + } else { 666 + resp, err := us.Branches(f.OwnerDid(), f.RepoName) 667 + if err != nil { 668 + log.Println("failed to reach knotserver", err) 669 + } else { 670 + defer resp.Body.Close() 671 + 672 + body, err := io.ReadAll(resp.Body) 673 + if err != nil { 674 + log.Printf("Error reading response body: %v", err) 675 + } else { 676 + var result types.RepoBranchesResponse 677 + err = json.Unmarshal(body, &result) 678 + if err != nil { 679 + log.Println("failed to parse response:", err) 680 + } else { 681 + for _, branch := range result.Branches { 682 + branchNames = append(branchNames, branch.Name) 683 + } 684 + } 685 + } 686 + } 687 + 688 + resp, err = us.DefaultBranch(f.OwnerDid(), f.RepoName) 689 + if err != nil { 690 + log.Println("failed to reach knotserver", err) 691 + } else { 692 + defer resp.Body.Close() 693 + 694 + body, err := io.ReadAll(resp.Body) 695 + if err != nil { 696 + log.Printf("Error reading response body: %v", err) 697 + } else { 698 + var result types.RepoDefaultBranchResponse 699 + err = json.Unmarshal(body, &result) 700 + if err != nil { 701 + log.Println("failed to parse response:", err) 702 + } else { 703 + defaultBranch = result.Branch 704 + } 705 + } 706 + } 707 + } 708 + 621 709 s.pages.RepoSettings(w, pages.RepoSettingsParams{ 622 710 LoggedInUser: user, 623 711 RepoInfo: f.RepoInfo(s, user), 624 712 Collaborators: repoCollaborators, 625 713 IsCollaboratorInviteAllowed: isCollaboratorInviteAllowed, 714 + Branches: branchNames, 715 + DefaultBranch: defaultBranch, 626 716 }) 627 717 } 628 718 }
+1
appview/state/router.go
··· 143 143 r.With(RepoPermissionMiddleware(s, "repo:settings")).Route("/settings", func(r chi.Router) { 144 144 r.Get("/", s.RepoSettings) 145 145 r.With(RepoPermissionMiddleware(s, "repo:invite")).Put("/collaborator", s.AddCollaborator) 146 + r.Put("/branches/default", s.SetDefaultBranch) 146 147 }) 147 148 }) 148 149 })
+33
appview/state/signer.go
··· 140 140 return s.client.Do(req) 141 141 } 142 142 143 + func (s *SignedClient) SetDefaultBranch(ownerDid, repoName, branch string) (*http.Response, error) { 144 + const ( 145 + Method = "PUT" 146 + ) 147 + endpoint := fmt.Sprintf("/%s/%s/branches/default", ownerDid, repoName) 148 + 149 + body, _ := json.Marshal(map[string]any{ 150 + "branch": branch, 151 + }) 152 + 153 + req, err := s.newRequest(Method, endpoint, body) 154 + if err != nil { 155 + return nil, err 156 + } 157 + 158 + return s.client.Do(req) 159 + } 160 + 143 161 func (s *SignedClient) AddCollaborator(ownerDid, repoName, memberDid string) (*http.Response, error) { 144 162 const ( 145 163 Method = "POST" ··· 268 286 269 287 return us.client.Do(req) 270 288 } 289 + 290 + func (us *UnsignedClient) DefaultBranch(ownerDid, repoName string) (*http.Response, error) { 291 + const ( 292 + Method = "GET" 293 + ) 294 + 295 + endpoint := fmt.Sprintf("/%s/%s/branches/default", ownerDid, repoName) 296 + 297 + req, err := us.newRequest(Method, endpoint, nil) 298 + if err != nil { 299 + return nil, err 300 + } 301 + 302 + return us.client.Do(req) 303 + }
+5
knotserver/git/git.go
··· 228 228 return branches, nil 229 229 } 230 230 231 + func (g *GitRepo) SetDefaultBranch(branch string) error { 232 + ref := plumbing.NewSymbolicReference(plumbing.HEAD, plumbing.NewBranchReferenceName(branch)) 233 + return g.r.Storer.SetReference(ref) 234 + } 235 + 231 236 func (g *GitRepo) FindMainBranch() (string, error) { 232 237 ref, err := g.r.Head() 233 238 if err != nil {
+7 -1
knotserver/handler.go
··· 102 102 r.Get("/archive/{file}", h.Archive) 103 103 r.Get("/commit/{ref}", h.Diff) 104 104 r.Get("/tags", h.Tags) 105 - r.Get("/branches", h.Branches) 105 + r.Route("/branches", func(r chi.Router) { 106 + r.Get("/", h.Branches) 107 + r.Route("/default", func(r chi.Router) { 108 + r.Get("/", h.DefaultBranch) 109 + r.With(h.VerifySignature).Put("/", h.SetDefaultBranch) 110 + }) 111 + }) 106 112 }) 107 113 }) 108 114
+51
knotserver/routes.go
··· 739 739 w.WriteHeader(http.StatusNoContent) 740 740 } 741 741 742 + func (h *Handle) DefaultBranch(w http.ResponseWriter, r *http.Request) { 743 + l := h.l.With("handler", "DefaultBranch") 744 + path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r)) 745 + 746 + gr, err := git.Open(path, "") 747 + if err != nil { 748 + notFound(w) 749 + return 750 + } 751 + 752 + branch, err := gr.FindMainBranch() 753 + if err != nil { 754 + writeError(w, err.Error(), http.StatusInternalServerError) 755 + l.Error("getting default branch", "error", err.Error()) 756 + return 757 + } 758 + 759 + writeJSON(w, types.RepoDefaultBranchResponse{ 760 + Branch: branch, 761 + }) 762 + } 763 + 764 + func (h *Handle) SetDefaultBranch(w http.ResponseWriter, r *http.Request) { 765 + l := h.l.With("handler", "SetDefaultBranch") 766 + path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r)) 767 + 768 + data := struct { 769 + Branch string `json:"branch"` 770 + }{} 771 + 772 + if err := json.NewDecoder(r.Body).Decode(&data); err != nil { 773 + writeError(w, err.Error(), http.StatusBadRequest) 774 + return 775 + } 776 + 777 + gr, err := git.Open(path, "") 778 + if err != nil { 779 + notFound(w) 780 + return 781 + } 782 + 783 + err = gr.SetDefaultBranch(data.Branch) 784 + if err != nil { 785 + writeError(w, err.Error(), http.StatusInternalServerError) 786 + l.Error("setting default branch", "error", err.Error()) 787 + return 788 + } 789 + 790 + w.WriteHeader(http.StatusNoContent) 791 + } 792 + 742 793 func (h *Handle) Init(w http.ResponseWriter, r *http.Request) { 743 794 l := h.l.With("handler", "Init") 744 795
+4
types/repo.go
··· 63 63 Branches []Branch `json:"branches,omitempty"` 64 64 } 65 65 66 + type RepoDefaultBranchResponse struct { 67 + Branch string `json:"branch,omitempty"` 68 + } 69 + 66 70 type RepoBlobResponse struct { 67 71 Contents string `json:"contents,omitempty"` 68 72 Ref string `json:"ref,omitempty"`