diff --git a/appview/state/signer.go b/appview/state/signer.go index 7127cb20..4278af75 100644 --- a/appview/state/signer.go +++ b/appview/state/signer.go @@ -243,6 +243,20 @@ func (s *SignedClient) MergeCheck(patch []byte, ownerDid, targetRepo, branch str return s.client.Do(req) } +func (s *SignedClient) NewHiddenRef(ownerDid, targetRepo, forkBranch, remoteBranch string) (*http.Response, error) { + const ( + Method = "POST" + ) + endpoint := fmt.Sprintf("/%s/%s/hidden-ref/%s/%s", ownerDid, targetRepo, forkBranch, remoteBranch) + + req, err := s.newRequest(Method, endpoint, nil) + if err != nil { + return nil, err + } + + return s.client.Do(req) +} + type UnsignedClient struct { Url *url.URL client *http.Client diff --git a/knotserver/git/fork.go b/knotserver/git/fork.go index b75c28c2..9ab1fdfe 100644 --- a/knotserver/git/fork.go +++ b/knotserver/git/fork.go @@ -2,19 +2,46 @@ package git import ( "fmt" + "os/exec" "github.com/go-git/go-git/v5" + "github.com/go-git/go-git/v5/config" ) func Fork(repoPath, source string) error { _, err := git.PlainClone(repoPath, true, &git.CloneOptions{ URL: source, - Depth: 1, SingleBranch: false, }) if err != nil { return fmt.Errorf("failed to bare clone repository: %w", err) } + + err = exec.Command("git", "-C", repoPath, "config", "receive.hideRefs", "refs/hidden").Run() + if err != nil { + return fmt.Errorf("failed to configure hidden refs: %w", err) + } + + return nil +} + +// TrackHiddenRemoteRef tracks a hidden remote in the repository. For example, +// if the feature branch on the fork (forkRef) is feature-1, and the remoteRef, +// i.e. the branch we want to merge into, is main, this will result in a refspec: +// +// +refs/heads/main:refs/hidden/feature-1/main +func (g *GitRepo) TrackHiddenRemoteRef(forkRef, remoteRef string) error { + fetchOpts := &git.FetchOptions{ + RefSpecs: []config.RefSpec{ + config.RefSpec(fmt.Sprintf("+refs/heads/%s:refs/hidden/%s/%s", forkRef, forkRef, remoteRef)), + }, + RemoteName: "origin", + } + + err := g.r.Fetch(fetchOpts) + if err != nil { + return fmt.Errorf("failed to fetch hidden remote: %s: %w", forkRef, err) + } return nil } diff --git a/knotserver/handler.go b/knotserver/handler.go index 499c169d..10978740 100644 --- a/knotserver/handler.go +++ b/knotserver/handler.go @@ -85,6 +85,8 @@ func Setup(ctx context.Context, c *config.Config, db *db.DB, e *rbac.Enforcer, j r.Post("/git-upload-pack", h.UploadPack) r.Get("/compare/{rev1}/{rev2}", h.Compare) // git diff-tree compare of two objects + r.With(h.VerifySignature).Post("/hidden-ref/{forkRef}/{remoteRef}", h.NewHiddenRef) + r.Route("/merge", func(r chi.Router) { r.With(h.VerifySignature) r.Post("/", h.Merge) diff --git a/knotserver/routes.go b/knotserver/routes.go index fe97d1fd..101f3760 100644 --- a/knotserver/routes.go +++ b/knotserver/routes.go @@ -786,6 +786,29 @@ func (h *Handle) Compare(w http.ResponseWriter, r *http.Request) { return } +func (h *Handle) NewHiddenRef(w http.ResponseWriter, r *http.Request) { + l := h.l.With("handler", "NewHiddenRef") + + forkRef := chi.URLParam(r, "forkRef") + remoteRef := chi.URLParam(r, "remoteRef") + path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r)) + gr, err := git.PlainOpen(path) + if err != nil { + notFound(w) + return + } + + err = gr.TrackHiddenRemoteRef(forkRef, remoteRef) + if err != nil { + l.Error("error tracking hidden remote ref", "msg", err.Error()) + writeError(w, "error tracking hidden remote ref", http.StatusBadRequest) + return + } + + w.WriteHeader(http.StatusNoContent) + return +} + func (h *Handle) AddMember(w http.ResponseWriter, r *http.Request) { l := h.l.With("handler", "AddMember")