internal/control/explore.go

e9566eed86ebcd185c4b85f63d667b5e671fe787
gitbay/internal/control/explore.go history · blame · raw

114 lines · 3501 bytes

  1package control
  2
  3import (
  4	"fmt"
  5	"io"
  6	"strings"
  7
  8	"gitbay.org/gitbay/internal/gitutil"
  9	"gitbay.org/gitbay/internal/policy"
 10	"gitbay.org/gitbay/internal/protocol"
 11)
 12
 13func init() {
 14	register(Command{
 15		Path:     []string{"explore"},
 16		Summary:  "list public repositories",
 17		Usage:    "explore [--limit <n>] [--cursor <c>]",
 18		ReadOnly: true,
 19		Run:      runExplore,
 20	})
 21	register(Command{
 22		Path: []string{"repo", "download"},
 23		// Not "repo archive": that name is taken by the read-only flag,
 24		// and renaming it would break every script that sets it.
 25		Summary:  "write a tar.gz of a ref to stdout",
 26		Usage:    "repo download <owner/name> [--ref <r>] > repo.tar.gz",
 27		ReadOnly: true,
 28		Run:      runRepoDownload,
 29	})
 30}
 31
 32// runExplore is the public listing the web serves at /explore. Without a
 33// command there was no way to browse what an instance hosts without
 34// already knowing a name to search for.
 35func runExplore(c *Ctx, args []string) int {
 36	rest, p, code := parsePageFlags(c, args, "explore", false)
 37	if code >= 0 {
 38		return code
 39	}
 40	if len(rest) != 0 {
 41		return c.usage()
 42	}
 43	repos, err := c.Store.ListPublicRepos()
 44	if err != nil {
 45		return c.fail(protocol.ExitFailure, "%v", err)
 46	}
 47	// Public means public, but an archived repo is still worth marking,
 48	// and the cursor is the path since the listing is ordered by it.
 49	type out struct {
 50		Path        string   `json:"path"`
 51		Description string   `json:"description,omitempty"`
 52		Archived    bool     `json:"archived,omitempty"`
 53		Topics      []string `json:"topics,omitempty"`
 54	}
 55	var ds []out
 56	for _, repo := range repos {
 57		// A dot-repo is infrastructure, not a project: .gitbay holds an
 58		// owner's profile content and has nothing to explore.
 59		if strings.HasPrefix(repo.Name, ".") {
 60			continue
 61		}
 62		if p.key != "" && repo.Path() <= p.key {
 63			continue
 64		}
 65		topics, _ := c.Store.ListTopics(repo.ID)
 66		ds = append(ds, out{
 67			Path:        repo.Path(),
 68			Description: gitutil.ReadDescription(RepoDir(c.Cfg.Server.Root, repo.OwnerName, repo.Name)),
 69			Archived:    repo.Settings.Archived,
 70			Topics:      topics,
 71		})
 72		if p.limit > 0 && len(ds) > p.limit {
 73			break
 74		}
 75	}
 76	ds, next := trimPage(p, ds, "explore", func(o out) string { return o.Path })
 77	return c.emitPage(p, ds, next, func(w io.Writer) {
 78		for _, d := range ds {
 79			fmt.Fprintf(w, "%s\t%s\n", d.Path, d.Description)
 80		}
 81	})
 82}
 83
 84// runRepoDownload writes a gzipped tarball of a ref to stdout, the way
 85// release asset get writes an asset. The web's /archive route is the
 86// same bytes with a Content-Disposition on them.
 87func runRepoDownload(c *Ctx, args []string) int {
 88	f, err := parseFlags(args, flagSpec{Values: []string{"--ref"}, MaxPos: -1, Usage: c.Cmd.Usage})
 89	if err != nil {
 90		return c.fail(protocol.ExitUsage, "%v", err)
 91	}
 92	rest, ref := f.Pos, f.Value("--ref")
 93	if len(rest) != 1 {
 94		return c.usage()
 95	}
 96	repo, code := resolveRepo(c, rest[0], policy.CanRead)
 97	if code >= 0 {
 98		return code
 99	}
100	dir := RepoDir(c.Cfg.Server.Root, repo.OwnerName, repo.Name)
101	if ref == "" {
102		ref = repo.DefaultBranch
103	}
104	if _, err := gitutil.ResolveRef(dir, ref); err != nil {
105		return c.fail(protocol.ExitNotFound, "no ref %q in %s", ref, repo.Path())
106	}
107	// The prefix git puts on every path inside the archive, so unpacking
108	// lands in a named directory rather than the current one.
109	prefix := repo.Name + "-" + ref
110	if err := gitutil.Archive(dir, ref, prefix, c.Stdout); err != nil {
111		return c.fail(protocol.ExitFailure, "%v", err)
112	}
113	return protocol.ExitOK
114}