From a2e81892f7bd167538676122258be198c7343df7 Mon Sep 17 00:00:00 2001 From: oppiliappan Date: Thu, 29 Jan 2026 06:50:14 +0000 Subject: [PATCH] knotserver/xrpc: introduce sh.tangled.repo.tag to get a single tag produces info about a single tag object Signed-off-by: oppiliappan --- api/tangled/repotag.go | 33 ++++++++++++++ knotserver/xrpc/repo_tag.go | 85 +++++++++++++++++++++++++++++++++++++ knotserver/xrpc/xrpc.go | 1 + lexicons/repo/tag.json | 43 +++++++++++++++++++ types/repo.go | 4 ++ 5 files changed, 166 insertions(+) create mode 100644 api/tangled/repotag.go create mode 100644 knotserver/xrpc/repo_tag.go create mode 100644 lexicons/repo/tag.json diff --git a/api/tangled/repotag.go b/api/tangled/repotag.go new file mode 100644 index 00000000..e7e59cf7 --- /dev/null +++ b/api/tangled/repotag.go @@ -0,0 +1,33 @@ +// Code generated by cmd/lexgen (see Makefile's lexgen); DO NOT EDIT. + +package tangled + +// schema: sh.tangled.repo.tag + +import ( + "bytes" + "context" + + "github.com/bluesky-social/indigo/lex/util" +) + +const ( + RepoTagNSID = "sh.tangled.repo.tag" +) + +// RepoTag calls the XRPC method "sh.tangled.repo.tag". +// +// repo: Repository identifier in format 'did:plc:.../repoName' +// tag: Name of tag, such as v1.3.0 +func RepoTag(ctx context.Context, c util.LexClient, repo string, tag string) ([]byte, error) { + buf := new(bytes.Buffer) + + params := map[string]interface{}{} + params["repo"] = repo + params["tag"] = tag + if err := c.LexDo(ctx, util.Query, "", "sh.tangled.repo.tag", params, nil, buf); err != nil { + return nil, err + } + + return buf.Bytes(), nil +} diff --git a/knotserver/xrpc/repo_tag.go b/knotserver/xrpc/repo_tag.go new file mode 100644 index 00000000..3d7c2e34 --- /dev/null +++ b/knotserver/xrpc/repo_tag.go @@ -0,0 +1,85 @@ +package xrpc + +import ( + "fmt" + "net/http" + + "github.com/go-git/go-git/v5/plumbing" + "github.com/go-git/go-git/v5/plumbing/object" + + "tangled.org/core/knotserver/git" + "tangled.org/core/types" + xrpcerr "tangled.org/core/xrpc/errors" +) + +func (x *Xrpc) RepoTag(w http.ResponseWriter, r *http.Request) { + repo := r.URL.Query().Get("repo") + repoPath, err := x.parseRepoParam(repo) + if err != nil { + writeError(w, err.(xrpcerr.XrpcError), http.StatusBadRequest) + return + } + + tagName := r.URL.Query().Get("tag") + if tagName == "" { + writeError(w, xrpcerr.NewXrpcError( + xrpcerr.WithTag("InvalidRequest"), + xrpcerr.WithMessage("missing name parameter"), + ), http.StatusBadRequest) + return + } + + gr, err := git.PlainOpen(repoPath) + if err != nil { + x.Logger.Error("failed to open", "error", err) + writeError(w, xrpcerr.RepoNotFoundError, http.StatusNoContent) + return + } + + // if this is not already formatted as refs/tags/v0.1.0, then format it + if !plumbing.ReferenceName(tagName).IsTag() { + tagName = plumbing.NewTagReferenceName(tagName).String() + } + + tags, err := gr.Tags(&git.TagsOptions{ + Pattern: tagName, + }) + + if len(tags) != 1 { + writeError(w, xrpcerr.NewXrpcError( + xrpcerr.WithTag("TagNotFound"), + xrpcerr.WithMessage(fmt.Sprintf("expected 1 tag to be returned, got %d tags", len(tags))), + ), http.StatusBadRequest) + return + } + + tag := tags[0] + + if err != nil { + x.Logger.Warn("getting tags", "error", err.Error()) + tags = []object.Tag{} + } + + var target *object.Tag + if tag.Target != plumbing.ZeroHash { + target = &tag + } + tr := types.TagReference{ + Tag: target, + } + + tr.Reference = types.Reference{ + Name: tag.Name, + Hash: tag.Hash.String(), + } + + if tag.Message != "" { + tr.Message = tag.Message + } + + response := types.RepoTagResponse{ + Tag: &tr, + } + + writeJson(w, response) +} diff --git a/knotserver/xrpc/xrpc.go b/knotserver/xrpc/xrpc.go index c110356f..940d543a 100644 --- a/knotserver/xrpc/xrpc.go +++ b/knotserver/xrpc/xrpc.go @@ -59,6 +59,7 @@ func (x *Xrpc) Router() http.Handler { r.Get("/"+tangled.RepoLogNSID, x.RepoLog) r.Get("/"+tangled.RepoBranchesNSID, x.RepoBranches) r.Get("/"+tangled.RepoTagsNSID, x.RepoTags) + r.Get("/"+tangled.RepoTagNSID, x.RepoTag) r.Get("/"+tangled.RepoBlobNSID, x.RepoBlob) r.Get("/"+tangled.RepoDiffNSID, x.RepoDiff) r.Get("/"+tangled.RepoCompareNSID, x.RepoCompare) diff --git a/lexicons/repo/tag.json b/lexicons/repo/tag.json new file mode 100644 index 00000000..06dfdf69 --- /dev/null +++ b/lexicons/repo/tag.json @@ -0,0 +1,43 @@ +{ + "lexicon": 1, + "id": "sh.tangled.repo.tag", + "defs": { + "main": { + "type": "query", + "parameters": { + "type": "params", + "required": [ + "repo", + "tag" + ], + "properties": { + "repo": { + "type": "string", + "description": "Repository identifier in format 'did:plc:.../repoName'" + }, + "tag": { + "type": "string", + "description": "Name of tag, such as v1.3.0" + } + } + }, + "output": { + "encoding": "*/*" + }, + "errors": [ + { + "name": "RepoNotFound", + "description": "Repository not found or access denied" + }, + { + "name": "TagNotFound", + "description": "Tag not found" + }, + { + "name": "InvalidRequest", + "description": "Invalid request parameters" + } + ] + } + } +} diff --git a/types/repo.go b/types/repo.go index 0d4dca3e..c2d3dc6a 100644 --- a/types/repo.go +++ b/types/repo.go @@ -94,6 +94,10 @@ type RepoTagsResponse struct { Tags []*TagReference `json:"tags,omitempty"` } +type RepoTagResponse struct { + Tag *TagReference `json:"tag,omitempty"` +} + type RepoBranchesResponse struct { Branches []Branch `json:"branches,omitempty"` } -- 2.51.2