diff --git a/src/git/models/repository.ts b/src/git/models/repository.ts index 11619ad..529e5bc 100644 --- a/src/git/models/repository.ts +++ b/src/git/models/repository.ts @@ -23,7 +23,7 @@ import { Logger } from '../../logger'; import { Messages } from '../../messages'; import { GitBranchReference, GitReference, GitTagReference } from './models'; import { RemoteProviderFactory, RemoteProviders, RichRemoteProvider } from '../remotes/factory'; -import { Arrays, debug, Functions, gate, Iterables, log, logName } from '../../system'; +import { Arrays, Dates, debug, Functions, gate, Iterables, log, logName } from '../../system'; import { runGitCommandInTerminal } from '../../terminal'; const ignoreGitRegex = /\.git(?:\/|\\|$)/; @@ -88,6 +88,21 @@ export interface RepositoryFileSystemChangeEvent { @logName((r, name) => `${name}(${r.id})`) export class Repository implements Disposable { + static formatLastFetched(lastFetched: number): string { + return Date.now() - lastFetched < Dates.MillisecondsPerDay + ? Dates.getFormatter(new Date(lastFetched)).fromNow() + : Dates.getFormatter(new Date(lastFetched)).format( + Container.config.defaultDateShortFormat ?? 'MMM D, YYYY', + ); + } + + static getLastFetchedUpdateInterval(lastFetched: number): number { + const timeDiff = Date.now() - lastFetched; + return timeDiff < Dates.MillisecondsPerDay + ? (timeDiff < Dates.MillisecondsPerHour ? Dates.MillisecondsPerMinute : Dates.MillisecondsPerHour) / 2 + : 0; + } + static sort(repositories: Repository[]) { return repositories.sort((a, b) => (a.starred ? -1 : 1) - (b.starred ? -1 : 1) || a.index - b.index); } @@ -229,6 +244,8 @@ export class Repository implements Disposable { @debug() private onRepositoryChanged(uri: Uri | undefined) { + this._lastFetched = undefined; + if (uri == null) { this.fireChange(RepositoryChange.Unknown); @@ -454,16 +471,25 @@ export class Repository implements Disposable { return Container.git.getContributors(this.path); } + private _lastFetched: number | undefined; + @gate() async getLastFetched(): Promise { - const hasRemotes = await this.hasRemotes(); - if (!hasRemotes || Container.vsls.isMaybeGuest) return 0; + if (this._lastFetched == null) { + const hasRemotes = await this.hasRemotes(); + if (!hasRemotes || Container.vsls.isMaybeGuest) return 0; + } try { const stat = await workspace.fs.stat(Uri.file(paths.join(this.path, '.git/FETCH_HEAD'))); - return stat.mtime; + // If the file is empty, assume the fetch failed, and don't update the timestamp + if (stat.size > 0) { + this._lastFetched = stat.mtime; + } } catch { - return 0; + this._lastFetched = undefined; } + + return this._lastFetched ?? 0; } getRemotes(_options: { sort?: boolean } = {}): Promise { diff --git a/src/views/commitsView.ts b/src/views/commitsView.ts index 46d267d..038e0f3 100644 --- a/src/views/commitsView.ts +++ b/src/views/commitsView.ts @@ -3,6 +3,7 @@ import { CancellationToken, commands, ConfigurationChangeEvent, + Disposable, ProgressLocation, TreeItem, TreeItemCollapsibleState, @@ -16,6 +17,7 @@ import { GitReference, GitRemote, GitRevisionReference, + Repository, RepositoryChange, RepositoryChangeEvent, } from '../git/git'; @@ -29,7 +31,7 @@ import { unknownGitUri, ViewNode, } from './nodes'; -import { Dates, debug, gate, Strings } from '../system'; +import { Dates, debug, Functions, gate, Strings } from '../system'; import { ViewBase } from './viewBase'; export class CommitsRepositoryNode extends RepositoryFolderNode { @@ -87,9 +89,7 @@ export class CommitsRepositoryNode extends RepositoryFolderNode 0) { + return Disposable.from( + await super.subscribe(), + Functions.interval(() => { + // Check if the interval should change, and if so, reset it + if (interval !== Repository.getLastFetchedUpdateInterval(lastFetched)) { + void this.resetSubscription(); + } + void this.view.triggerNodeChange(this); + }, interval), + ); + } + + return super.subscribe(); + } + protected changed(e: RepositoryChangeEvent) { return ( e.changed(RepositoryChange.Config) || @@ -179,9 +200,7 @@ export class CommitsViewNode extends ViewNode { const status = branch.getTrackingStatus(); this.view.description = `${status ? `${status} ${GlyphChars.Dot} ` : ''}${branch.name}${ - lastFetched - ? ` ${GlyphChars.Dot} Last fetched ${Dates.getFormatter(new Date(lastFetched)).fromNow()}` - : '' + lastFetched ? ` ${GlyphChars.Dot} Last fetched ${Repository.formatLastFetched(lastFetched)}` : '' }`; } else { this.view.description = undefined; diff --git a/src/views/contributorsView.ts b/src/views/contributorsView.ts index 0c00887..3fb789f 100644 --- a/src/views/contributorsView.ts +++ b/src/views/contributorsView.ts @@ -19,9 +19,9 @@ export class ContributorsRepositoryNode extends RepositoryFolderNode this.child?.updateAvatar(e.email)), ); } diff --git a/src/views/nodes/viewNode.ts b/src/views/nodes/viewNode.ts index 6d83a67..b04d5b2 100644 --- a/src/views/nodes/viewNode.ts +++ b/src/views/nodes/viewNode.ts @@ -298,6 +298,13 @@ export abstract class SubscribeableViewNode extends V this.subscription = Promise.resolve(this.subscribe()); await this.subscription; } + + @gate() + @debug() + async resetSubscription() { + await this.unsubscribe(); + await this.ensureSubscription(); + } } export abstract class RepositoryFolderNode< @@ -375,7 +382,7 @@ export abstract class RepositoryFolderNode< } @debug() - protected subscribe() { + protected subscribe(): Disposable | Promise { return this.repo.onDidChange(this.onRepositoryChanged, this); }