Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
0d3f7ca
Bring extension update check in line with gh check
andyfeller Nov 17, 2024
7b91b17
Refactor extension command testing
andyfeller Nov 18, 2024
6bd01d5
Initial extension update check testing
Dec 1, 2024
97630fe
Enhance extension manager and tests
Dec 9, 2024
a55f50b
Remove Extension.FullName() from interface
Dec 9, 2024
a79e086
Test whether extension update should be checked
andyfeller Dec 9, 2024
10f62b1
Expand update tests for local extensions
andyfeller Dec 9, 2024
a6b8bec
Remove unnecessary Extension.FullName()
andyfeller Dec 9, 2024
52b845b
Refactor local installation manager test by OS
andyfeller Dec 11, 2024
56841d9
Add build constraint for non-Windows systems
andyfeller Dec 12, 2024
c8501d8
Add build constraint for non-Windows systems
andyfeller Dec 12, 2024
c12e369
Implement partial feedback before trunk update
andyfeller Dec 14, 2024
5ad6ccf
Merge branch 'trunk' into andyfeller/cli-cli-8183-timeout-pt2
andyfeller Dec 14, 2024
bfc63a1
Implement remaining PR feedback
andyfeller Dec 14, 2024
edc0351
Fix failig local extension tests
andyfeller Dec 14, 2024
6bbd703
Comment exported functions
andyfeller Jan 9, 2025
889c4c7
Collapse dryrun checks in ext bin upgrade
williammartin Jan 10, 2025
1c32c98
Capture greater detail on updaterEnabled
andyfeller Jan 10, 2025
2006223
Separate logic for checking updates
andyfeller Jan 10, 2025
97eb8a1
Simplifying cleanExtensionUpdateDir logic
andyfeller Jan 10, 2025
0a1c9be
Expand docs on cleaning extension update dir
andyfeller Jan 10, 2025
3ada5af
Enhance help docs on ext upgrade notices
andyfeller Jan 13, 2025
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
4 changes: 4 additions & 0 deletions cmd/gen-docs/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,3 +127,7 @@ func (e *em) Create(_ string, _ extensions.ExtTemplateType) error {
}

func (e *em) EnableDryRunMode() {}

func (e *em) UpdateDir(_ string) string {
return ""
}
33 changes: 8 additions & 25 deletions internal/ghcmd/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,16 @@ import (
"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"
)

// updaterEnabled is a statically linked build property set in gh formula within homebrew/homebrew-core
// used to control whether users are notified of newer GitHub CLI releases.
// This needs to be set to 'cli/cli' as it affects where update.CheckForUpdate() checks for releases.
// It is unclear whether this means that only homebrew builds will check for updates or not.
// Development builds leave this empty as impossible to determine if newer or not.
// For more information, <https://github.com/Homebrew/homebrew-core/blob/master/Formula/g/gh.rb>.
var updaterEnabled = ""
Comment thread
andyfeller marked this conversation as resolved.

type exitCode int
Expand Down Expand Up @@ -216,38 +221,16 @@ func printError(out io.Writer, err error, cmd *cobra.Command, debug bool) {
}
}

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() {
if updaterEnabled == "" || !update.ShouldCheckForUpdate() {
Comment thread
andyfeller marked this conversation as resolved.
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)
return update.CheckForUpdate(ctx, httpClient, stateFilePath, updaterEnabled, currentVersion)
}

func isRecentRelease(publishedAt time.Time) bool {
Expand Down
70 changes: 69 additions & 1 deletion internal/update/update.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ import (
"strings"
"time"

"github.com/cli/cli/v2/pkg/extensions"
"github.com/hashicorp/go-version"
"github.com/mattn/go-isatty"
"gopkg.in/yaml.v3"
)

Expand All @@ -31,7 +33,60 @@ type StateEntry struct {
LatestRelease ReleaseInfo `yaml:"latest_release"`
}

// CheckForUpdate checks whether this software has had a newer release on GitHub
// ShouldCheckForExtensionUpdate decides whether we check for updates for GitHub CLI extensions based on user preferences and current execution context.
// During cli/cli#9934, this logic was split out from ShouldCheckForUpdate() because we envisioned it going in a different direction.
func ShouldCheckForExtensionUpdate() bool {
if os.Getenv("GH_NO_EXTENSION_UPDATE_NOTIFIER") != "" {
return false
}
if os.Getenv("CODESPACES") != "" {
return false
}
return !IsCI() && IsTerminal(os.Stdout) && IsTerminal(os.Stderr)
}

// CheckForExtensionUpdate checks whether an update exists for a specific extension based on extension type and recency of last check within past 24 hours.
func CheckForExtensionUpdate(em extensions.ExtensionManager, ext extensions.Extension, now time.Time) (*ReleaseInfo, error) {
// local extensions cannot have updates, so avoid work that ultimately returns nothing.
if ext.IsLocal() {
Comment thread
andyfeller marked this conversation as resolved.
return nil, nil
}

stateFilePath := filepath.Join(em.UpdateDir(ext.Name()), "state.yml")
stateEntry, _ := getStateEntry(stateFilePath)
if stateEntry != nil && now.Sub(stateEntry.CheckedForUpdateAt).Hours() < 24 {
return nil, nil
}

releaseInfo := &ReleaseInfo{
Version: ext.LatestVersion(),
URL: ext.URL(),
}

err := setStateEntry(stateFilePath, now, *releaseInfo)
if err != nil {
return nil, err
}

if ext.UpdateAvailable() {
return releaseInfo, nil
}

return nil, nil
}

// ShouldCheckForUpdate decides whether we check for updates for the GitHub CLI based on user preferences and current execution context.
func ShouldCheckForUpdate() bool {
if os.Getenv("GH_NO_UPDATE_NOTIFIER") != "" {
return false
}
if os.Getenv("CODESPACES") != "" {
return false
}
return !IsCI() && IsTerminal(os.Stdout) && IsTerminal(os.Stderr)
}

// CheckForUpdate checks whether an update exists for the GitHub CLI based on recency of last check within past 24 hours.
func CheckForUpdate(ctx context.Context, client *http.Client, stateFilePath, repo, currentVersion string) (*ReleaseInfo, error) {
stateEntry, _ := getStateEntry(stateFilePath)
if stateEntry != nil && time.Since(stateEntry.CheckedForUpdateAt).Hours() < 24 {
Expand Down Expand Up @@ -122,3 +177,16 @@ func versionGreaterThan(v, w string) bool {

return ve == nil && we == nil && vv.GreaterThan(vw)
}

// IsTerminal determines if a file descriptor is an interactive terminal / TTY.
func IsTerminal(f *os.File) bool {
return isatty.IsTerminal(f.Fd()) || isatty.IsCygwinTerminal(f.Fd())
}

// IsCI determines if the current execution context is within a known CI/CD system.
// This is 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
}
Loading