Skip to content
Merged
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
469 changes: 151 additions & 318 deletions cmd/src/lsif_upload.go

Large diffs are not rendered by default.

191 changes: 191 additions & 0 deletions cmd/src/lsif_upload_flags.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
package main

import (
"errors"
"flag"
"fmt"
"os"
"strings"

"github.com/sourcegraph/sourcegraph/lib/codeintel/upload"
"github.com/sourcegraph/src-cli/internal/codeintel"
)

var lsifUploadFlags struct {
file string

// UploadRecordOptions
repo string
commit string
root string
indexer string
associatedIndexID int

// SourcegraphInstanceOptions
uploadRoute string
gitHubToken string
maxPayloadSizeMb int64

// Output and error behavior
ignoreUploadFailures bool
noProgress bool
verbosity int
json bool
open bool
}

var lsifUploadFlagSet = flag.NewFlagSet("upload", flag.ExitOnError)

func init() {
lsifUploadFlagSet.StringVar(&lsifUploadFlags.file, "file", "./dump.lsif", `The path to the LSIF dump file.`)

// UploadRecordOptions
lsifUploadFlagSet.StringVar(&lsifUploadFlags.repo, "repo", "", `The name of the repository (e.g. github.com/gorilla/mux). By default, derived from the origin remote.`)
lsifUploadFlagSet.StringVar(&lsifUploadFlags.commit, "commit", "", `The 40-character hash of the commit. Defaults to the currently checked-out commit.`)
lsifUploadFlagSet.StringVar(&lsifUploadFlags.root, "root", "", `The path in the repository that matches the LSIF projectRoot (e.g. cmd/project1). Defaults to the directory where the dump file is located.`)
lsifUploadFlagSet.StringVar(&lsifUploadFlags.indexer, "indexer", "", `The name of the indexer that generated the dump. This will override the 'toolInfo.name' field in the metadata vertex of the LSIF dump file. This must be supplied if the indexer does not set this field (in which case the upload will fail with an explicit message).`)
lsifUploadFlagSet.IntVar(&lsifUploadFlags.associatedIndexID, "associated-index-id", -1, "ID of the associated index record for this upload. For internal use only.")

// SourcegraphInstanceOptions
lsifUploadFlagSet.StringVar(&lsifUploadFlags.uploadRoute, "upload-route", "/.api/lsif/upload", "The path of the upload route. For internal use only.")
lsifUploadFlagSet.StringVar(&lsifUploadFlags.gitHubToken, "github-token", "", `A GitHub access token with 'public_repo' scope that Sourcegraph uses to verify you have access to the repository.`)
lsifUploadFlagSet.Int64Var(&lsifUploadFlags.maxPayloadSizeMb, "max-payload-size", 100, `The maximum upload size (in megabytes). Indexes exceeding this limit will be uploaded over multiple HTTP requests.`)

// Output and error behavior
lsifUploadFlagSet.BoolVar(&lsifUploadFlags.ignoreUploadFailures, "ignore-upload-failure", false, `Exit with status code zero on upload failure.`)
lsifUploadFlagSet.BoolVar(&lsifUploadFlags.noProgress, "no-progress", false, `Do not display progress updates.`)
lsifUploadFlagSet.IntVar(&lsifUploadFlags.verbosity, "trace", 0, "-trace=0 shows no logs; -trace=1 shows requests and response metadata; -trace=2 shows headers, -trace=3 shows response body")
lsifUploadFlagSet.BoolVar(&lsifUploadFlags.json, "json", false, `Output relevant state in JSON on success.`)
lsifUploadFlagSet.BoolVar(&lsifUploadFlags.open, "open", false, `Open the LSIF upload page in your browser.`)
}

// parseAndValidateLSIFUploadFlags calls lsifUploadFlagSet.Parse, then infers values for
// missing flags, normalizes supplied values, and validates the state of the lsifUploadFlags
// object.
//
// On success, the global lsifUploadFlags object will be populated with valid values. An
// error is returned on failure.
func parseAndValidateLSIFUploadFlags(args []string) error {
if err := lsifUploadFlagSet.Parse(args); err != nil {
return err
}

if inferenceErrors := inferMissingLSIFUploadFlags(); len(inferenceErrors) > 0 {
return errorWithHint{
err: inferenceErrors[0].err, hint: strings.Join([]string{
fmt.Sprintf(
"Unable to determine %s from environment. Check your working directory or supply -%s={value} explicitly",
inferenceErrors[0].argument,
inferenceErrors[0].argument,
),
}, "\n"),
}
}

if err := validateLSIFUploadFlags(); err != nil {
return err
}

return nil
}

type argumentInferenceError struct {
argument string
err error
}

// inferMissingLSIFUploadFlags updates the flags values which were not explicitly
// supplied by the user with default values inferred from the current git state and
// filesystem.
//
// Note: This function must not be called before lsifUploadFlagSet.Parse.
func inferMissingLSIFUploadFlags() (inferErrors []argumentInferenceError) {
if _, err := os.Stat(lsifUploadFlags.file); os.IsNotExist(err) {
inferErrors = append(inferErrors, argumentInferenceError{"file", err})
}

if err := inferUnsetFlag("repo", &lsifUploadFlags.repo, codeintel.InferRepo); err != nil {
inferErrors = append(inferErrors, *err)
}
if err := inferUnsetFlag("commit", &lsifUploadFlags.commit, codeintel.InferCommit); err != nil {
inferErrors = append(inferErrors, *err)
}
if err := inferUnsetFlag("root", &lsifUploadFlags.root, inferIndexRoot); err != nil {
inferErrors = append(inferErrors, *err)
}
if err := inferUnsetFlag("indexer", &lsifUploadFlags.indexer, readIndexerName); err != nil {
inferErrors = append(inferErrors, *err)
}

return inferErrors
}

// inferUnsetFlag conditionally updates the value of the given pointer with the
// return value of the given function. If the flag with the given name was supplied
// by the user, then this function no-ops. An argumentInferenceError is returned if
// the given function returns an error.
//
// Note: This function must not be called before lsifUploadFlagSet.Parse.
func inferUnsetFlag(name string, target *string, f func() (string, error)) *argumentInferenceError {
if isFlagSet(lsifUploadFlagSet, name) {
return nil
}

value, err := f()
if err != nil {
return &argumentInferenceError{name, err}
}

*target = value
return nil
}

// isFlagSet returns true if the flag with the given name was supplied by the user.
// This lets us distinguish between zero-values (empty strings) and void values without
// requiring pointers and adding a layer of indirection deeper in the program.
func isFlagSet(fs *flag.FlagSet, name string) (found bool) {
fs.Visit(func(f *flag.Flag) {
if f.Name == name {
found = true
}
})

return found
}

// inferIndexRoot returns the root directory based on the configured index file path.
//
// Note: This function must not be called before lsifUploadFlagSet.Parse.
func inferIndexRoot() (string, error) {
return codeintel.InferRoot(lsifUploadFlags.file)
}

// readIndexerName returns the indexer name read from the configured index file.
//
// Note: This function must not be called before lsifUploadFlagSet.Parse.
func readIndexerName() (string, error) {
file, err := os.Open(lsifUploadFlags.file)
if err != nil {
return "", err
}
defer file.Close()

return upload.ReadIndexerName(file)
}

// validateLSIFUploadFlags returns an error if any of the parsed flag values are illegal.
//
// Note: This function must not be called before lsifUploadFlagSet.Parse.
func validateLSIFUploadFlags() error {
lsifUploadFlags.root = codeintel.SanitizeRoot(lsifUploadFlags.root)

if strings.HasPrefix(lsifUploadFlags.root, "..") {
return errors.New("root must not be outside of repository")
}

if lsifUploadFlags.maxPayloadSizeMb < 25 {
return errors.New("max-payload-size must be at least 25 (MB)")
}

return nil
}
9 changes: 4 additions & 5 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,11 @@ go 1.13
require (
github.com/Masterminds/semver v1.5.0
github.com/dustin/go-humanize v1.0.0
github.com/efritz/pentimento v0.0.0-20190429011147-ade47d831101
github.com/gobwas/glob v0.2.3
github.com/google/go-cmp v0.5.5
github.com/hashicorp/go-multierror v1.1.1
github.com/jig/teereadcloser v0.0.0-20181016160506-953720c48e05
github.com/json-iterator/go v1.1.10
github.com/json-iterator/go v1.1.11
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
github.com/mattn/go-isatty v0.0.12
github.com/neelance/parallel v0.0.0-20160708114440-4de9ce63d14c
Expand All @@ -22,11 +21,11 @@ require (
github.com/sourcegraph/batch-change-utils v0.0.0-20210309183117-206c057cc03e
github.com/sourcegraph/go-diff v0.6.1
github.com/sourcegraph/jsonx v0.0.0-20200629203448-1a936bd500cf
github.com/sourcegraph/sourcegraph/lib v0.0.0-20210425142047-b8f4790093e5
github.com/sourcegraph/sourcegraph/lib v0.0.0-20210520231824-520a2ae26af0
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
golang.org/x/net v0.0.0-20200625001655-4c5254603344
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e
golang.org/x/net v0.0.0-20201021035429-f5854403a974
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9
golang.org/x/sys v0.0.0-20210426080607-c94f62235c83 // indirect
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776
jaytaylor.com/html2text v0.0.0-20200412013138-3577fbdbcff7
Expand Down
Loading