Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 38 additions & 34 deletions cmd/src/codeowners.go
Original file line number Diff line number Diff line change
@@ -1,48 +1,28 @@
package main

import (
"flag"
"fmt"
"io"
"os"
)

var codeownersCommands commander
"github.com/sourcegraph/sourcegraph/lib/errors"
"github.com/sourcegraph/src-cli/internal/clicompat"
"github.com/urfave/cli/v3"
)

func init() {
usage := `'src codeowners' is a tool that manages ingested code ownership data in a Sourcegraph instance.
const codeownersExamples = `'src codeowners' manages ingested code ownership data in a Sourcegraph instance.

Usage:

src codeowners command [command options]
src codeowners [command options]

The commands are:
Examples:

get returns the codeowners file for a repository, if exists
create create a codeowners file
update update a codeowners file
delete delete a codeowners file

Use "src codeowners [command] -h" for more information about a command.
$ src codeowners get -repo='github.com/sourcegraph/sourcegraph'
$ src codeowners create -repo='github.com/sourcegraph/sourcegraph' -f CODEOWNERS
$ src codeowners update -repo='github.com/sourcegraph/sourcegraph' -f CODEOWNERS
$ src codeowners delete -repo='github.com/sourcegraph/sourcegraph'
`

flagSet := flag.NewFlagSet("codeowners", flag.ExitOnError)
handler := func(args []string) error {
codeownersCommands.run(flagSet, "src codeowners", usage, args)
return nil
}

// Register the command.
commands = append(commands, &command{
flagSet: flagSet,
aliases: []string{"codeowner"},
handler: handler,
usageFunc: func() {
fmt.Println(usage)
},
})
}

const codeownersFragment = `
fragment CodeownersFileFields on CodeownersIngestedFile {
contents
Expand All @@ -59,9 +39,33 @@ type CodeownersIngestedFile struct {
} `json:"repository"`
}

func readFile(f string) (io.Reader, error) {
var codeownersCommand = clicompat.Wrap(&cli.Command{
Name: "codeowners",
Aliases: []string{"codeowner"},
Usage: "manages ingested code ownership data",
UsageText: "src codeowners [command options]",
Description: codeownersExamples,
HideVersion: true,
Commands: []*cli.Command{
codeownersGetCommand,
codeownersCreateCommand,
codeownersUpdateCommand,
codeownersDeleteCommand,
},
})

func readFile(f string) ([]byte, error) {
if f == "-" {
return os.Stdin, nil
return io.ReadAll(os.Stdin)
}
return os.ReadFile(f)
}

func requiresNotEmpty(errMsg string) func(string) error {
return func(value string) error {
if value == "" {
return errors.New(errMsg)
}
return nil
}
return os.Open(f)
}
104 changes: 40 additions & 64 deletions cmd/src/codeowners_create.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,74 +2,57 @@ package main

import (
"context"
"flag"
"fmt"
"io"
"strings"

"github.com/sourcegraph/sourcegraph/lib/errors"

"github.com/sourcegraph/src-cli/internal/api"
"github.com/sourcegraph/src-cli/internal/clicompat"
"github.com/sourcegraph/src-cli/internal/cmderrors"
"github.com/urfave/cli/v3"
)

func init() {
usage := `
Examples:

Create a codeowners file for the repository "github.com/sourcegraph/sourcegraph":

$ src codeowners create -repo='github.com/sourcegraph/sourcegraph' -f CODEOWNERS
const codeownersCreateExamples = `
Create a codeowners file for a repository.

Create a codeowners file for the repository "github.com/sourcegraph/sourcegraph" from stdin:
Examples:

$ src codeowners create -repo='github.com/sourcegraph/sourcegraph' -f -
$ src codeowners create -repo='github.com/sourcegraph/sourcegraph' -f CODEOWNERS
$ src codeowners create -repo='github.com/sourcegraph/sourcegraph' -f -
`

flagSet := flag.NewFlagSet("create", flag.ExitOnError)
usageFunc := func() {
fmt.Fprintf(flag.CommandLine.Output(), "Usage of 'src codeowners %s':\n", flagSet.Name())
flagSet.PrintDefaults()
fmt.Println(usage)
}
var (
repoFlag = flagSet.String("repo", "", "The repository to attach the data to")
fileFlag = flagSet.String("file", "", "File path to read ownership information from (- for stdin)")
fileShortFlag = flagSet.String("f", "", "File path to read ownership information from (- for stdin). Alias for -file")

apiFlags = api.NewFlags(flagSet)
)

handler := func(args []string) error {
if err := flagSet.Parse(args); err != nil {
return err
}

if *repoFlag == "" {
return errors.New("provide a repo name using -repo")
}

if *fileFlag == "" && *fileShortFlag == "" {
return errors.New("provide a file using -file")
}
if *fileFlag != "" && *fileShortFlag != "" {
return errors.New("have to provide either -file or -f")
}
if *fileShortFlag != "" {
*fileFlag = *fileShortFlag
}

file, err := readFile(*fileFlag)
var codeownersCreateCommand = clicompat.Wrap(&cli.Command{
Name: "create",
Usage: "create a codeowners file",
UsageText: "src codeowners create [options]",
Description: codeownersCreateExamples,
HideVersion: true,
Flags: clicompat.WithAPIFlags(
&cli.StringFlag{
Name: "repo",
Usage: "The repository to attach the data to",
Required: true,
Validator: requiresNotEmpty("provide a repo name using -repo"),
},
&cli.StringFlag{
Name: "file",
Aliases: []string{"f"},
Usage: "File path to read ownership information from (- for stdin)",
TakesFile: true,
Required: true,
Validator: requiresNotEmpty("provide a file using -file"),
},
),
Action: func(ctx context.Context, cmd *cli.Command) error {
repoName := cmd.String("repo")
fileName := cmd.String("file")

content, err := readFile(fileName)
if err != nil {
return err
}

content, err := io.ReadAll(file)
if err != nil {
return err
}

client := cfg.apiClient(apiFlags, flagSet.Output())
client := cfg.apiClient(clicompat.APIFlagsFromCmd(cmd), cmd.Writer)

query := `mutation CreateCodeownersFile(
$repoName: String!,
Expand All @@ -89,14 +72,14 @@ Examples:
AddCodeownersFile CodeownersIngestedFile
}
if ok, err := client.NewRequest(query, map[string]any{
"repoName": *repoFlag,
"repoName": repoName,
"content": string(content),
}).Do(context.Background(), &result); err != nil || !ok {
}).Do(ctx, &result); err != nil || !ok {
var gqlErr api.GraphQlErrors
if errors.As(err, &gqlErr) {
for _, e := range gqlErr {
if strings.Contains(e.Error(), "repo not found:") {
return cmderrors.ExitCode(2, errors.Newf("repository %q not found", *repoFlag))
return cmderrors.ExitCode(2, errors.Newf("repository %q not found", repoName))
}
if strings.Contains(e.Error(), "codeowners file has already been ingested for this repository") {
return cmderrors.ExitCode(2, errors.New("codeowners file has already been ingested for this repository"))
Expand All @@ -107,12 +90,5 @@ Examples:
}

return nil
}

// Register the command.
codeownersCommands = append(codeownersCommands, &command{
flagSet: flagSet,
handler: handler,
usageFunc: usageFunc,
})
}
},
})
70 changes: 29 additions & 41 deletions cmd/src/codeowners_delete.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,46 +2,41 @@ package main

import (
"context"
"flag"
"fmt"
"strings"

"github.com/sourcegraph/sourcegraph/lib/errors"

"github.com/sourcegraph/src-cli/internal/api"
"github.com/sourcegraph/src-cli/internal/clicompat"
"github.com/sourcegraph/src-cli/internal/cmderrors"
"github.com/urfave/cli/v3"
)

func init() {
usage := `
Examples:
const codeownersDeleteExamples = `
Delete a codeowners file for a repository.

Delete a codeowners file for the repository "github.com/sourcegraph/sourcegraph":
Examples:

$ src codeowners delete -repo='github.com/sourcegraph/sourcegraph'
$ src codeowners delete -repo='github.com/sourcegraph/sourcegraph'
`

flagSet := flag.NewFlagSet("delete", flag.ExitOnError)
usageFunc := func() {
fmt.Fprintf(flag.CommandLine.Output(), "Usage of 'src codeowners %s':\n", flagSet.Name())
flagSet.PrintDefaults()
fmt.Println(usage)
}
var (
repoFlag = flagSet.String("repo", "", "The repository to delete the data for")
apiFlags = api.NewFlags(flagSet)
)

handler := func(args []string) error {
if err := flagSet.Parse(args); err != nil {
return err
}

if *repoFlag == "" {
return errors.New("provide a repo name using -repo")
}

client := cfg.apiClient(apiFlags, flagSet.Output())
var codeownersDeleteCommand = clicompat.Wrap(&cli.Command{
Name: "delete",
Usage: "delete a codeowners file",
UsageText: "src codeowners delete [options]",
Description: codeownersDeleteExamples,
HideVersion: true,
Flags: clicompat.WithAPIFlags(
&cli.StringFlag{
Name: "repo",
Usage: "The repository to delete the data for",
Required: true,
Validator: requiresNotEmpty("provide a repo name using -repo"),
},
),
Action: func(ctx context.Context, cmd *cli.Command) error {
repoName := cmd.String("repo")
client := cfg.apiClient(clicompat.APIFlagsFromCmd(cmd), cmd.Writer)

query := `mutation DeleteCodeownersFile(
$repoName: String!,
Expand All @@ -58,29 +53,22 @@ Examples:
DeleteCodeownersFile CodeownersIngestedFile
}
if ok, err := client.NewRequest(query, map[string]any{
"repoName": *repoFlag,
}).Do(context.Background(), &result); err != nil || !ok {
"repoName": repoName,
}).Do(ctx, &result); err != nil || !ok {
var gqlErr api.GraphQlErrors
if errors.As(err, &gqlErr) {
for _, e := range gqlErr {
if strings.Contains(e.Error(), "repo not found:") {
return cmderrors.ExitCode(2, errors.Newf("repository %q not found", *repoFlag))
return cmderrors.ExitCode(2, errors.Newf("repository %q not found", repoName))
}
if strings.Contains(e.Error(), "codeowners file not found:") {
return cmderrors.ExitCode(2, errors.Newf("no data found for repository %q", *repoFlag))
return cmderrors.ExitCode(2, errors.Newf("no data found for repository %q", repoName))
}
}
}
return err
}

return nil
}

// Register the command.
codeownersCommands = append(codeownersCommands, &command{
flagSet: flagSet,
handler: handler,
usageFunc: usageFunc,
})
}
},
})
Loading
Loading