package cmd import ( "fmt" "code.gitea.io/sdk/gitea" "github.com/spf13/cobra" ) var repoSearchCmd = &cobra.Command{ Use: "search [query]", Aliases: []string{"s"}, Short: "Search repositories on the current host", Long: `Search repositories using the host's search index. The query is matched against name by default. Pass --topic to match against topics only, or --description to include descriptions. --type limits results to "source" (non-fork, non-mirror), "fork", or "mirror" repositories.`, Example: ` # Search by name substring fgj repo search tea # Search by topic fgj repo search ci --topic # Find only forks fgj repo search go --type fork # List private repos owned by a user (no query) fgj repo search --owner alice --private --limit 50 # Output as JSON fgj repo search platform --json`, Args: cobra.MaximumNArgs(1), RunE: runRepoSearch, } func init() { repoCmd.AddCommand(repoSearchCmd) repoSearchCmd.Flags().Bool("topic", false, "Match query against topics only") repoSearchCmd.Flags().Bool("description", false, "Include descriptions in the search") repoSearchCmd.Flags().Bool("private", false, "Limit to private repositories") repoSearchCmd.Flags().Bool("archived", false, "Include archived repositories") repoSearchCmd.Flags().Bool("exclude-templates", false, "Exclude template repositories") repoSearchCmd.Flags().String("type", "", "Filter by repo type: source, fork, mirror") repoSearchCmd.Flags().String("owner", "", "Limit to repos owned by this user or org") repoSearchCmd.Flags().String("sort", "", "Sort by: alpha, created, updated, size, id") repoSearchCmd.Flags().String("order", "", "Order: asc or desc") repoSearchCmd.Flags().IntP("limit", "L", 30, "Maximum number of results") addJSONFlags(repoSearchCmd, "Output as JSON") } func runRepoSearch(cmd *cobra.Command, args []string) error { client, err := loadClient() if err != nil { return err } query := "" if len(args) == 1 { query = args[0] } topic, _ := cmd.Flags().GetBool("topic") desc, _ := cmd.Flags().GetBool("description") private, _ := cmd.Flags().GetBool("private") archived, _ := cmd.Flags().GetBool("archived") excludeTemplates, _ := cmd.Flags().GetBool("exclude-templates") sort, _ := cmd.Flags().GetString("sort") order, _ := cmd.Flags().GetString("order") typeFlag, _ := cmd.Flags().GetString("type") limit, _ := cmd.Flags().GetInt("limit") if limit <= 0 { limit = 30 } var repoType gitea.RepoType switch typeFlag { case "": repoType = gitea.RepoTypeNone case "source": repoType = gitea.RepoTypeSource case "fork": repoType = gitea.RepoTypeFork case "mirror": repoType = gitea.RepoTypeMirror default: return fmt.Errorf("invalid --type %q (must be source, fork, or mirror)", typeFlag) } opt := gitea.SearchRepoOptions{ ListOptions: gitea.ListOptions{PageSize: limit}, Keyword: query, KeywordIsTopic: topic, KeywordInDescription: desc, IsPrivate: optionalBool(private), IsArchived: optionalBool(archived), ExcludeTemplate: excludeTemplates, Type: repoType, Sort: sort, Order: order, } if o, _ := cmd.Flags().GetString("owner"); o != "" { u, _, err := client.GetUserInfo(o) if err != nil { return fmt.Errorf("failed to resolve owner %q: %w", o, err) } opt.OwnerID = u.ID } repos, _, err := client.SearchRepos(opt) if err != nil { return fmt.Errorf("search failed: %w", err) } if wantJSON(cmd) { return outputJSON(cmd, repos) } if len(repos) == 0 { fmt.Fprintln(ios.Out, "No repositories match.") return nil } tp := ios.NewTablePrinter() tp.AddHeader("FULL NAME", "VISIBILITY", "DESCRIPTION", "STARS") for _, r := range repos { visibility := "public" if r.Private { visibility = "private" } tp.AddRow(r.FullName, visibility, r.Description, fmt.Sprintf("%d", r.Stars)) } return tp.Render() } // optionalBool returns a pointer when the user explicitly wants the positive // filter (IsPrivate/IsArchived); nil means "no filter" to the SDK. func optionalBool(v bool) *bool { if !v { return nil } return &v }