Initial testscript introduction

This commit is contained in:
William Martin 2024-10-08 13:51:54 +02:00
parent 76ea939627
commit d7465bdf3c
7 changed files with 389 additions and 271 deletions

85
acceptance/pr_test.go Normal file
View file

@ -0,0 +1,85 @@
package acceptance_test
import (
"os"
"path"
"strings"
"testing"
"math/rand"
"github.com/cli/cli/v2/internal/ghcmd"
"github.com/rogpeppe/go-internal/testscript"
)
func ghMain() int {
return int(ghcmd.Main())
}
func TestMain(m *testing.M) {
os.Exit(testscript.RunMain(m, map[string]func() int{
"gh": ghMain,
}))
}
func TestPullRequests(t *testing.T) {
testscript.Run(t, params("pr"))
}
func params(dir string) testscript.Params {
return testscript.Params{
Dir: path.Join("testdata", dir),
Files: []string{},
Setup: sharedSetup,
Cmds: sharedCmds,
RequireExplicitExec: true,
RequireUniqueNames: true,
}
}
var sharedSetup = func(ts *testscript.Env) error {
scriptName, ok := extractScriptName(ts.Vars)
if !ok {
ts.T().Fatal("script name not found")
}
ts.Setenv("SCRIPT_NAME", scriptName)
ts.Setenv("HOME", ts.Cd)
ts.Setenv("GH_CONFIG_DIR", ts.Cd)
ts.Setenv("GH_TOKEN", os.Getenv("GH_TOKEN"))
ts.Setenv("ORG", os.Getenv("GH_ACCEPTANCE_ORG"))
ts.Setenv("RANDOM_STRING", randomString(10))
return nil
}
var sharedCmds = map[string]func(ts *testscript.TestScript, neg bool, args []string){
"defer": func(ts *testscript.TestScript, neg bool, args []string) {
ts.Defer(func() {
if err := ts.Exec(args[0], args[1:]...); err != nil {
ts.Fatalf("deferred command failed: %v", err)
}
})
}}
var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
func randomString(n int) string {
b := make([]rune, n)
for i := range b {
b[i] = letters[rand.Intn(len(letters))]
}
return string(b)
}
func extractScriptName(vars []string) (string, bool) {
for _, kv := range vars {
if strings.HasPrefix(kv, "WORK=") {
v := strings.Split(kv, "=")[1]
return strings.CutPrefix(path.Base(v), "script-")
}
}
return "", false
}

View file

@ -0,0 +1,24 @@
# Use gh as a credential helper
exec gh auth setup-git
# Create a repository with a file so it has a default branch
exec gh repo create $ORG/$SCRIPT_NAME-$RANDOM_STRING --add-readme --private
# Defer repo cleanup
defer gh repo delete --yes $ORG/$SCRIPT_NAME-$RANDOM_STRING
# Clone the repo
exec gh repo clone $ORG/$SCRIPT_NAME-$RANDOM_STRING
# Prepare a branch to PR
cd $SCRIPT_NAME-$RANDOM_STRING
exec git checkout -b feature-branch
exec git commit --allow-empty -m 'Empty Commit'
exec git push -u origin feature-branch
# Create the PR
exec gh pr create --title 'Feature Title' --body 'Feature Body'
# Check the PR is indeed created
exec gh pr list
stdout 'Feature Title'

View file

@ -1,276 +1,12 @@
package main
import (
"context"
"errors"
"fmt"
"io"
"net"
"os"
"os/exec"
"path/filepath"
"strings"
"time"
surveyCore "github.com/AlecAivazis/survey/v2/core"
"github.com/AlecAivazis/survey/v2/terminal"
"github.com/cli/cli/v2/api"
"github.com/cli/cli/v2/internal/build"
"github.com/cli/cli/v2/internal/config"
"github.com/cli/cli/v2/internal/config/migration"
"github.com/cli/cli/v2/internal/update"
"github.com/cli/cli/v2/pkg/cmd/factory"
"github.com/cli/cli/v2/pkg/cmd/root"
"github.com/cli/cli/v2/pkg/cmdutil"
"github.com/cli/cli/v2/pkg/iostreams"
"github.com/cli/cli/v2/utils"
"github.com/cli/safeexec"
"github.com/mattn/go-isatty"
"github.com/mgutz/ansi"
"github.com/spf13/cobra"
)
var updaterEnabled = ""
type exitCode int
const (
exitOK exitCode = 0
exitError exitCode = 1
exitCancel exitCode = 2
exitAuth exitCode = 4
exitPending exitCode = 8
"github.com/cli/cli/v2/internal/ghcmd"
)
func main() {
code := mainRun()
code := ghcmd.Main()
os.Exit(int(code))
}
func mainRun() exitCode {
buildDate := build.Date
buildVersion := build.Version
hasDebug, _ := utils.IsDebugEnabled()
cmdFactory := factory.New(buildVersion)
stderr := cmdFactory.IOStreams.ErrOut
ctx := context.Background()
if cfg, err := cmdFactory.Config(); err == nil {
var m migration.MultiAccount
if err := cfg.Migrate(m); err != nil {
fmt.Fprintln(stderr, err)
return exitError
}
}
updateCtx, updateCancel := context.WithCancel(ctx)
defer updateCancel()
updateMessageChan := make(chan *update.ReleaseInfo)
go func() {
rel, err := checkForUpdate(updateCtx, cmdFactory, buildVersion)
if err != nil && hasDebug {
fmt.Fprintf(stderr, "warning: checking for update failed: %v", err)
}
updateMessageChan <- rel
}()
if !cmdFactory.IOStreams.ColorEnabled() {
surveyCore.DisableColor = true
ansi.DisableColors(true)
} else {
// override survey's poor choice of color
surveyCore.TemplateFuncsWithColor["color"] = func(style string) string {
switch style {
case "white":
return ansi.ColorCode("default")
default:
return ansi.ColorCode(style)
}
}
}
// Enable running gh from Windows File Explorer's address bar. Without this, the user is told to stop and run from a
// terminal. With this, a user can clone a repo (or take other actions) directly from explorer.
if len(os.Args) > 1 && os.Args[1] != "" {
cobra.MousetrapHelpText = ""
}
rootCmd, err := root.NewCmdRoot(cmdFactory, buildVersion, buildDate)
if err != nil {
fmt.Fprintf(stderr, "failed to create root command: %s\n", err)
return exitError
}
expandedArgs := []string{}
if len(os.Args) > 0 {
expandedArgs = os.Args[1:]
}
// translate `gh help <command>` to `gh <command> --help` for extensions.
if len(expandedArgs) >= 2 && expandedArgs[0] == "help" && isExtensionCommand(rootCmd, expandedArgs[1:]) {
expandedArgs = expandedArgs[1:]
expandedArgs = append(expandedArgs, "--help")
}
rootCmd.SetArgs(expandedArgs)
if cmd, err := rootCmd.ExecuteContextC(ctx); err != nil {
var pagerPipeError *iostreams.ErrClosedPagerPipe
var noResultsError cmdutil.NoResultsError
var extError *root.ExternalCommandExitError
var authError *root.AuthError
if err == cmdutil.SilentError {
return exitError
} else if err == cmdutil.PendingError {
return exitPending
} else if cmdutil.IsUserCancellation(err) {
if errors.Is(err, terminal.InterruptErr) {
// ensure the next shell prompt will start on its own line
fmt.Fprint(stderr, "\n")
}
return exitCancel
} else if errors.As(err, &authError) {
return exitAuth
} else if errors.As(err, &pagerPipeError) {
// ignore the error raised when piping to a closed pager
return exitOK
} else if errors.As(err, &noResultsError) {
if cmdFactory.IOStreams.IsStdoutTTY() {
fmt.Fprintln(stderr, noResultsError.Error())
}
// no results is not a command failure
return exitOK
} else if errors.As(err, &extError) {
// pass on exit codes from extensions and shell aliases
return exitCode(extError.ExitCode())
}
printError(stderr, err, cmd, hasDebug)
if strings.Contains(err.Error(), "Incorrect function") {
fmt.Fprintln(stderr, "You appear to be running in MinTTY without pseudo terminal support.")
fmt.Fprintln(stderr, "To learn about workarounds for this error, run: gh help mintty")
return exitError
}
var httpErr api.HTTPError
if errors.As(err, &httpErr) && httpErr.StatusCode == 401 {
fmt.Fprintln(stderr, "Try authenticating with: gh auth login")
} else if u := factory.SSOURL(); u != "" {
// handles organization SAML enforcement error
fmt.Fprintf(stderr, "Authorize in your web browser: %s\n", u)
} else if msg := httpErr.ScopesSuggestion(); msg != "" {
fmt.Fprintln(stderr, msg)
}
return exitError
}
if root.HasFailed() {
return exitError
}
updateCancel() // if the update checker hasn't completed by now, abort it
newRelease := <-updateMessageChan
if newRelease != nil {
isHomebrew := isUnderHomebrew(cmdFactory.Executable())
if isHomebrew && isRecentRelease(newRelease.PublishedAt) {
// do not notify Homebrew users before the version bump had a chance to get merged into homebrew-core
return exitOK
}
fmt.Fprintf(stderr, "\n\n%s %s → %s\n",
ansi.Color("A new release of gh is available:", "yellow"),
ansi.Color(strings.TrimPrefix(buildVersion, "v"), "cyan"),
ansi.Color(strings.TrimPrefix(newRelease.Version, "v"), "cyan"))
if isHomebrew {
fmt.Fprintf(stderr, "To upgrade, run: %s\n", "brew upgrade gh")
}
fmt.Fprintf(stderr, "%s\n\n",
ansi.Color(newRelease.URL, "yellow"))
}
return exitOK
}
// isExtensionCommand returns true if args resolve to an extension command.
func isExtensionCommand(rootCmd *cobra.Command, args []string) bool {
c, _, err := rootCmd.Find(args)
return err == nil && c != nil && c.GroupID == "extension"
}
func printError(out io.Writer, err error, cmd *cobra.Command, debug bool) {
var dnsError *net.DNSError
if errors.As(err, &dnsError) {
fmt.Fprintf(out, "error connecting to %s\n", dnsError.Name)
if debug {
fmt.Fprintln(out, dnsError)
}
fmt.Fprintln(out, "check your internet connection or https://githubstatus.com")
return
}
fmt.Fprintln(out, err)
var flagError *cmdutil.FlagError
if errors.As(err, &flagError) || strings.HasPrefix(err.Error(), "unknown command ") {
if !strings.HasSuffix(err.Error(), "\n") {
fmt.Fprintln(out)
}
fmt.Fprintln(out, cmd.UsageString())
}
}
func shouldCheckForUpdate() bool {
if os.Getenv("GH_NO_UPDATE_NOTIFIER") != "" {
return false
}
if os.Getenv("CODESPACES") != "" {
return false
}
return updaterEnabled != "" && !isCI() && isTerminal(os.Stdout) && isTerminal(os.Stderr)
}
func isTerminal(f *os.File) bool {
return isatty.IsTerminal(f.Fd()) || isatty.IsCygwinTerminal(f.Fd())
}
// based on https://github.com/watson/ci-info/blob/HEAD/index.js
func isCI() bool {
return os.Getenv("CI") != "" || // GitHub Actions, Travis CI, CircleCI, Cirrus CI, GitLab CI, AppVeyor, CodeShip, dsari
os.Getenv("BUILD_NUMBER") != "" || // Jenkins, TeamCity
os.Getenv("RUN_ID") != "" // TaskCluster, dsari
}
func checkForUpdate(ctx context.Context, f *cmdutil.Factory, currentVersion string) (*update.ReleaseInfo, error) {
if !shouldCheckForUpdate() {
return nil, nil
}
httpClient, err := f.HttpClient()
if err != nil {
return nil, err
}
repo := updaterEnabled
stateFilePath := filepath.Join(config.StateDir(), "state.yml")
return update.CheckForUpdate(ctx, httpClient, stateFilePath, repo, currentVersion)
}
func isRecentRelease(publishedAt time.Time) bool {
return !publishedAt.IsZero() && time.Since(publishedAt) < time.Hour*24
}
// Check whether the gh binary was found under the Homebrew prefix
func isUnderHomebrew(ghBinary string) bool {
brewExe, err := safeexec.LookPath("brew")
if err != nil {
return false
}
brewPrefixBytes, err := exec.Command(brewExe, "--prefix").Output()
if err != nil {
return false
}
brewBinPrefix := filepath.Join(strings.TrimSpace(string(brewPrefixBytes)), "bin") + string(filepath.Separator)
return strings.HasPrefix(ghBinary, brewBinPrefix)
}

2
go.mod
View file

@ -125,6 +125,7 @@ require (
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/rodaine/table v1.0.1 // indirect
github.com/rogpeppe/go-internal v1.13.1
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/sagikazarmark/locafero v0.4.0 // indirect
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
@ -160,6 +161,7 @@ require (
golang.org/x/mod v0.20.0 // indirect
golang.org/x/net v0.27.0 // indirect
golang.org/x/sys v0.26.0 // indirect
golang.org/x/tools v0.22.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20240520151616-dc85e6b867a5 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240520151616-dc85e6b867a5 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect

8
go.sum
View file

@ -366,8 +366,8 @@ github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rodaine/table v1.0.1 h1:U/VwCnUxlVYxw8+NJiLIuCxA/xa6jL38MY3FYysVWWQ=
github.com/rodaine/table v1.0.1/go.mod h1:UVEtfBsflpeEcD56nF4F5AocNFta0ZuolpSVdPtlmP4=
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk=
@ -533,8 +533,8 @@ golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA=
golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.172.0 h1:/1OcMZGPmW1rX2LCu2CmGUD1KXK1+pfzxotxyRUCCdk=
google.golang.org/api v0.172.0/go.mod h1:+fJZq6QXWfa9pXhnIzsjx4yI22d4aI9ZpLb58gvXjis=

271
internal/ghcmd/cmd.go Normal file
View file

@ -0,0 +1,271 @@
package ghcmd
import (
"context"
"errors"
"fmt"
"io"
"net"
"os"
"os/exec"
"path/filepath"
"strings"
"time"
surveyCore "github.com/AlecAivazis/survey/v2/core"
"github.com/AlecAivazis/survey/v2/terminal"
"github.com/cli/cli/v2/api"
"github.com/cli/cli/v2/internal/build"
"github.com/cli/cli/v2/internal/config"
"github.com/cli/cli/v2/internal/config/migration"
"github.com/cli/cli/v2/internal/update"
"github.com/cli/cli/v2/pkg/cmd/factory"
"github.com/cli/cli/v2/pkg/cmd/root"
"github.com/cli/cli/v2/pkg/cmdutil"
"github.com/cli/cli/v2/pkg/iostreams"
"github.com/cli/cli/v2/utils"
"github.com/cli/safeexec"
"github.com/mattn/go-isatty"
"github.com/mgutz/ansi"
"github.com/spf13/cobra"
)
var updaterEnabled = ""
type exitCode int
const (
exitOK exitCode = 0
exitError exitCode = 1
exitCancel exitCode = 2
exitAuth exitCode = 4
exitPending exitCode = 8
)
func Main() exitCode {
buildDate := build.Date
buildVersion := build.Version
hasDebug, _ := utils.IsDebugEnabled()
cmdFactory := factory.New(buildVersion)
stderr := cmdFactory.IOStreams.ErrOut
ctx := context.Background()
if cfg, err := cmdFactory.Config(); err == nil {
var m migration.MultiAccount
if err := cfg.Migrate(m); err != nil {
fmt.Fprintln(stderr, err)
return exitError
}
}
updateCtx, updateCancel := context.WithCancel(ctx)
defer updateCancel()
updateMessageChan := make(chan *update.ReleaseInfo)
go func() {
rel, err := checkForUpdate(updateCtx, cmdFactory, buildVersion)
if err != nil && hasDebug {
fmt.Fprintf(stderr, "warning: checking for update failed: %v", err)
}
updateMessageChan <- rel
}()
if !cmdFactory.IOStreams.ColorEnabled() {
surveyCore.DisableColor = true
ansi.DisableColors(true)
} else {
// override survey's poor choice of color
surveyCore.TemplateFuncsWithColor["color"] = func(style string) string {
switch style {
case "white":
return ansi.ColorCode("default")
default:
return ansi.ColorCode(style)
}
}
}
// Enable running gh from Windows File Explorer's address bar. Without this, the user is told to stop and run from a
// terminal. With this, a user can clone a repo (or take other actions) directly from explorer.
if len(os.Args) > 1 && os.Args[1] != "" {
cobra.MousetrapHelpText = ""
}
rootCmd, err := root.NewCmdRoot(cmdFactory, buildVersion, buildDate)
if err != nil {
fmt.Fprintf(stderr, "failed to create root command: %s\n", err)
return exitError
}
expandedArgs := []string{}
if len(os.Args) > 0 {
expandedArgs = os.Args[1:]
}
// translate `gh help <command>` to `gh <command> --help` for extensions.
if len(expandedArgs) >= 2 && expandedArgs[0] == "help" && isExtensionCommand(rootCmd, expandedArgs[1:]) {
expandedArgs = expandedArgs[1:]
expandedArgs = append(expandedArgs, "--help")
}
rootCmd.SetArgs(expandedArgs)
if cmd, err := rootCmd.ExecuteContextC(ctx); err != nil {
var pagerPipeError *iostreams.ErrClosedPagerPipe
var noResultsError cmdutil.NoResultsError
var extError *root.ExternalCommandExitError
var authError *root.AuthError
if err == cmdutil.SilentError {
return exitError
} else if err == cmdutil.PendingError {
return exitPending
} else if cmdutil.IsUserCancellation(err) {
if errors.Is(err, terminal.InterruptErr) {
// ensure the next shell prompt will start on its own line
fmt.Fprint(stderr, "\n")
}
return exitCancel
} else if errors.As(err, &authError) {
return exitAuth
} else if errors.As(err, &pagerPipeError) {
// ignore the error raised when piping to a closed pager
return exitOK
} else if errors.As(err, &noResultsError) {
if cmdFactory.IOStreams.IsStdoutTTY() {
fmt.Fprintln(stderr, noResultsError.Error())
}
// no results is not a command failure
return exitOK
} else if errors.As(err, &extError) {
// pass on exit codes from extensions and shell aliases
return exitCode(extError.ExitCode())
}
printError(stderr, err, cmd, hasDebug)
if strings.Contains(err.Error(), "Incorrect function") {
fmt.Fprintln(stderr, "You appear to be running in MinTTY without pseudo terminal support.")
fmt.Fprintln(stderr, "To learn about workarounds for this error, run: gh help mintty")
return exitError
}
var httpErr api.HTTPError
if errors.As(err, &httpErr) && httpErr.StatusCode == 401 {
fmt.Fprintln(stderr, "Try authenticating with: gh auth login")
} else if u := factory.SSOURL(); u != "" {
// handles organization SAML enforcement error
fmt.Fprintf(stderr, "Authorize in your web browser: %s\n", u)
} else if msg := httpErr.ScopesSuggestion(); msg != "" {
fmt.Fprintln(stderr, msg)
}
return exitError
}
if root.HasFailed() {
return exitError
}
updateCancel() // if the update checker hasn't completed by now, abort it
newRelease := <-updateMessageChan
if newRelease != nil {
isHomebrew := isUnderHomebrew(cmdFactory.Executable())
if isHomebrew && isRecentRelease(newRelease.PublishedAt) {
// do not notify Homebrew users before the version bump had a chance to get merged into homebrew-core
return exitOK
}
fmt.Fprintf(stderr, "\n\n%s %s → %s\n",
ansi.Color("A new release of gh is available:", "yellow"),
ansi.Color(strings.TrimPrefix(buildVersion, "v"), "cyan"),
ansi.Color(strings.TrimPrefix(newRelease.Version, "v"), "cyan"))
if isHomebrew {
fmt.Fprintf(stderr, "To upgrade, run: %s\n", "brew upgrade gh")
}
fmt.Fprintf(stderr, "%s\n\n",
ansi.Color(newRelease.URL, "yellow"))
}
return exitOK
}
// isExtensionCommand returns true if args resolve to an extension command.
func isExtensionCommand(rootCmd *cobra.Command, args []string) bool {
c, _, err := rootCmd.Find(args)
return err == nil && c != nil && c.GroupID == "extension"
}
func printError(out io.Writer, err error, cmd *cobra.Command, debug bool) {
var dnsError *net.DNSError
if errors.As(err, &dnsError) {
fmt.Fprintf(out, "error connecting to %s\n", dnsError.Name)
if debug {
fmt.Fprintln(out, dnsError)
}
fmt.Fprintln(out, "check your internet connection or https://githubstatus.com")
return
}
fmt.Fprintln(out, err)
var flagError *cmdutil.FlagError
if errors.As(err, &flagError) || strings.HasPrefix(err.Error(), "unknown command ") {
if !strings.HasSuffix(err.Error(), "\n") {
fmt.Fprintln(out)
}
fmt.Fprintln(out, cmd.UsageString())
}
}
func shouldCheckForUpdate() bool {
if os.Getenv("GH_NO_UPDATE_NOTIFIER") != "" {
return false
}
if os.Getenv("CODESPACES") != "" {
return false
}
return updaterEnabled != "" && !isCI() && isTerminal(os.Stdout) && isTerminal(os.Stderr)
}
func isTerminal(f *os.File) bool {
return isatty.IsTerminal(f.Fd()) || isatty.IsCygwinTerminal(f.Fd())
}
// based on https://github.com/watson/ci-info/blob/HEAD/index.js
func isCI() bool {
return os.Getenv("CI") != "" || // GitHub Actions, Travis CI, CircleCI, Cirrus CI, GitLab CI, AppVeyor, CodeShip, dsari
os.Getenv("BUILD_NUMBER") != "" || // Jenkins, TeamCity
os.Getenv("RUN_ID") != "" // TaskCluster, dsari
}
func checkForUpdate(ctx context.Context, f *cmdutil.Factory, currentVersion string) (*update.ReleaseInfo, error) {
if !shouldCheckForUpdate() {
return nil, nil
}
httpClient, err := f.HttpClient()
if err != nil {
return nil, err
}
repo := updaterEnabled
stateFilePath := filepath.Join(config.StateDir(), "state.yml")
return update.CheckForUpdate(ctx, httpClient, stateFilePath, repo, currentVersion)
}
func isRecentRelease(publishedAt time.Time) bool {
return !publishedAt.IsZero() && time.Since(publishedAt) < time.Hour*24
}
// Check whether the gh binary was found under the Homebrew prefix
func isUnderHomebrew(ghBinary string) bool {
brewExe, err := safeexec.LookPath("brew")
if err != nil {
return false
}
brewPrefixBytes, err := exec.Command(brewExe, "--prefix").Output()
if err != nil {
return false
}
brewBinPrefix := filepath.Join(strings.TrimSpace(string(brewPrefixBytes)), "bin") + string(filepath.Separator)
return strings.HasPrefix(ghBinary, brewBinPrefix)
}

View file

@ -1,4 +1,4 @@
package main
package ghcmd
import (
"bytes"