Browse Source

Adds missing upstream indication

main
Eric Amodio 3 years ago
parent
commit
ddc1e4da63
22 changed files with 220 additions and 133 deletions
  1. +1
    -0
      CHANGELOG.md
  2. +1
    -0
      README.md
  3. +9
    -0
      package.json
  4. +3
    -2
      src/commands/git/branch.ts
  5. +2
    -1
      src/commands/git/status.ts
  6. +1
    -1
      src/commands/git/switch.ts
  7. +4
    -1
      src/commands/openFileOnRemote.ts
  8. +27
    -14
      src/commands/quickCommand.steps.ts
  9. +10
    -10
      src/git/gitService.ts
  10. +27
    -7
      src/git/models/branch.ts
  11. +2
    -2
      src/git/models/models.ts
  12. +11
    -12
      src/git/models/repository.ts
  13. +28
    -16
      src/git/models/status.ts
  14. +6
    -1
      src/git/models/tag.ts
  15. +6
    -3
      src/git/parsers/branchParser.ts
  16. +25
    -21
      src/quickpicks/gitQuickPickItems.ts
  17. +2
    -3
      src/quickpicks/referencePicker.ts
  18. +35
    -29
      src/views/nodes/branchNode.ts
  19. +1
    -1
      src/views/nodes/repositoryNode.ts
  20. +9
    -5
      src/views/nodes/viewNode.ts
  21. +4
    -4
      src/views/viewCommands.ts
  22. +6
    -0
      src/views/viewDecorationProvider.ts

+ 1
- 0
CHANGELOG.md View File

@ -8,6 +8,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p
### Added ### Added
- Adds a branch indicator to show when a branch is missing its upstream, both in views and quick pick menus
- Adds _Open Folder History_ command to folders in the _Explorer_ view to show the folder's history in the _File History_ view - Adds _Open Folder History_ command to folders in the _Explorer_ view to show the folder's history in the _File History_ view
- Adds Gitea remote provider support — closes [#1379](https://github.com/eamodio/vscode-gitlens/issues/1379) thanks to [PR #1396](https://github.com/eamodio/vscode-gitlens/pull/1396) by Nils K ([septatrix](https://github.com/septatrix)) - Adds Gitea remote provider support — closes [#1379](https://github.com/eamodio/vscode-gitlens/issues/1379) thanks to [PR #1396](https://github.com/eamodio/vscode-gitlens/pull/1396) by Nils K ([septatrix](https://github.com/septatrix))
- Adds a `gitlens.advanced.commitOrdering` setting to specify the order by which commits will be shown. If unspecified, commits will be shown in reverse chronological order — closes [#1257](https://github.com/eamodio/vscode-gitlens/issues/1257) thanks to [PR #1344](https://github.com/eamodio/vscode-gitlens/pull/1344) by Andy Tang ([thewindsofwinter](https://github.com/thewindsofwinter)) and Shashank Shastri ([Shashank-Shastri](https://github.com/Shashank-Shastri)) - Adds a `gitlens.advanced.commitOrdering` setting to specify the order by which commits will be shown. If unspecified, commits will be shown in reverse chronological order — closes [#1257](https://github.com/eamodio/vscode-gitlens/issues/1257) thanks to [PR #1344](https://github.com/eamodio/vscode-gitlens/pull/1344) by Andy Tang ([thewindsofwinter](https://github.com/thewindsofwinter)) and Shashank Shastri ([Shashank-Shastri](https://github.com/Shashank-Shastri))

+ 1
- 0
README.md View File

@ -384,6 +384,7 @@ The _Branches_ view lists all of the local branches, and additionally provides,
- `⮟` + red colorization — indicates that the branch has unpulled changes (behind) - `⮟` + red colorization — indicates that the branch has unpulled changes (behind)
- `⮟⮝` + yellow colorization — indicates that the branch has diverged from its upstream; meaning it has both unpulled and unpushed changes - `⮟⮝` + yellow colorization — indicates that the branch has diverged from its upstream; meaning it has both unpulled and unpushed changes
- `⮙+` + green colorization — indicates that the branch hasn't yet been published to an upstream remote - `⮙+` + green colorization — indicates that the branch hasn't yet been published to an upstream remote
- `!` + dark red colorization — indicates that the branch has a missing upstream (e.g. the upstream branch was deleted)
- a branch comparison tool (**Compare <branch> with <branch, tag, or ref>**) — [optionally](#branches-view-settings- 'Jump to the Branches view settings') shows a comparison of the branch to a user-selected reference - a branch comparison tool (**Compare <branch> with <branch, tag, or ref>**) — [optionally](#branches-view-settings- 'Jump to the Branches view settings') shows a comparison of the branch to a user-selected reference
- **Behind** — lists the commits that are missing from the branch (i.e. behind) but exist in the selected reference - **Behind** — lists the commits that are missing from the branch (i.e. behind) but exist in the selected reference
- **# files changed** — lists all of the files changed in the behind commits - **# files changed** — lists all of the files changed in the behind commits

+ 9
- 0
package.json View File

@ -2968,6 +2968,15 @@
"light": "#35b15e", "light": "#35b15e",
"highContrast": "#4dff88" "highContrast": "#4dff88"
} }
},
{
"id": "gitlens.decorations.branchMissingUpstreamForegroundColor",
"description": "Specifies the decoration foreground color of branches that have a missing upstream",
"defaults": {
"light": "#ad0707",
"dark": "#c74e39",
"highContrast": "#c74e39"
}
} }
], ],
"commands": [ "commands": [

+ 3
- 2
src/commands/git/branch.ts View File

@ -336,9 +336,10 @@ export class BranchGitCommand extends QuickCommand {
context.title = getTitle('Branches', state.subcommand); context.title = getTitle('Branches', state.subcommand);
const result = yield* pickBranchesStep(state, context, { const result = yield* pickBranchesStep(state, context, {
filterBranches: b => !b.current,
filter: b => !b.current,
picked: state.references?.map(r => r.ref), picked: state.references?.map(r => r.ref),
placeholder: 'Choose branches to delete', placeholder: 'Choose branches to delete',
sort: { current: false, missingUpstream: true },
}); });
// Always break on the first step (so we will go back) // Always break on the first step (so we will go back)
if (result === StepResult.Break) break; if (result === StepResult.Break) break;
@ -430,7 +431,7 @@ export class BranchGitCommand extends QuickCommand {
while (this.canStepsContinue(state)) { while (this.canStepsContinue(state)) {
if (state.counter < 3 || state.reference == null) { if (state.counter < 3 || state.reference == null) {
const result = yield* pickBranchStep(state, context, { const result = yield* pickBranchStep(state, context, {
filterBranches: b => !b.remote,
filter: b => !b.remote,
picked: state.reference?.ref, picked: state.reference?.ref,
placeholder: 'Choose a branch to rename', placeholder: 'Choose a branch to rename',
}); });

+ 2
- 1
src/commands/git/status.ts View File

@ -91,7 +91,8 @@ export class StatusGitCommand extends QuickCommand {
refType: 'branch', refType: 'branch',
name: context.status.branch, name: context.status.branch,
remote: false, remote: false,
upstream: context.status.upstream,
upstream:
context.status.upstream != null ? { name: context.status.upstream, missing: false } : undefined,
}), }),
{ icon: false }, { icon: false },
)}`; )}`;

+ 1
- 1
src/commands/git/switch.ts View File

@ -147,7 +147,7 @@ export class SwitchGitCommand extends QuickCommand {
context.title = `Create Branch and ${this.title}`; context.title = `Create Branch and ${this.title}`;
const branches = await Container.git.getBranches(state.reference.repoPath, { const branches = await Container.git.getBranches(state.reference.repoPath, {
filter: b => b.upstream === state.reference!.name,
filter: b => b.upstream?.name === state.reference!.name,
sort: { orderBy: BranchSorting.DateDesc }, sort: { orderBy: BranchSorting.DateDesc },
}); });

+ 4
- 1
src/commands/openFileOnRemote.ts View File

@ -160,13 +160,16 @@ export class OpenFileOnRemoteCommand extends ActiveEditorCommand {
if (pick.refType === 'branch') { if (pick.refType === 'branch') {
if (pick.remote) { if (pick.remote) {
args.branchOrTag = GitBranch.getNameWithoutRemote(pick.name);
const remoteName = GitBranch.getRemote(pick.name); const remoteName = GitBranch.getRemote(pick.name);
const remote = remotes.find(r => r.name === remoteName); const remote = remotes.find(r => r.name === remoteName);
if (remote != null) { if (remote != null) {
remotes = [remote]; remotes = [remote];
} }
} else {
args.branchOrTag = pick.name;
} }
args.branchOrTag = pick.remote ? GitBranch.getNameWithoutRemote(pick.name) : pick.name;
sha = undefined; sha = undefined;
} else if (pick.refType === 'tag') { } else if (pick.refType === 'tag') {
args.branchOrTag = pick.ref; args.branchOrTag = pick.ref;

+ 27
- 14
src/commands/quickCommand.steps.ts View File

@ -5,6 +5,7 @@ import { BranchSorting, configuration, TagSorting } from '../configuration';
import { Container } from '../container'; import { Container } from '../container';
import { GlyphChars, quickPickTitleMaxChars } from '../constants'; import { GlyphChars, quickPickTitleMaxChars } from '../constants';
import { import {
BranchSortOptions,
GitBranch, GitBranch,
GitBranchReference, GitBranchReference,
GitContributor, GitContributor,
@ -23,6 +24,7 @@ import {
RemoteResourceType, RemoteResourceType,
Repository, Repository,
SearchPattern, SearchPattern,
TagSortOptions,
} from '../git/git'; } from '../git/git';
import { GitService } from '../git/gitService'; import { GitService } from '../git/gitService';
import { import {
@ -107,16 +109,24 @@ export function appendReposToTitle<
export async function getBranches( export async function getBranches(
repos: Repository | Repository[], repos: Repository | Repository[],
options: { filterBranches?: (b: GitBranch) => boolean; picked?: string | string[] } = {},
options: { filter?: (b: GitBranch) => boolean; picked?: string | string[]; sort?: BranchSortOptions } = {},
): Promise<BranchQuickPickItem[]> { ): Promise<BranchQuickPickItem[]> {
return getBranchesAndOrTags(repos, ['branches'], { sort: true, ...options }) as Promise<BranchQuickPickItem[]>;
return getBranchesAndOrTags(repos, ['branches'], {
filter: options?.filter != null ? { branches: options.filter } : undefined,
picked: options?.picked,
sort: options?.sort != null ? { branches: options.sort } : true,
}) as Promise<BranchQuickPickItem[]>;
} }
export async function getTags( export async function getTags(
repos: Repository | Repository[], repos: Repository | Repository[],
options: { filterTags?: (t: GitTag) => boolean; picked?: string | string[] } = {},
options?: { filter?: (t: GitTag) => boolean; picked?: string | string[]; sort?: TagSortOptions },
): Promise<TagQuickPickItem[]> { ): Promise<TagQuickPickItem[]> {
return getBranchesAndOrTags(repos, ['tags'], { sort: true, ...options }) as Promise<TagQuickPickItem[]>;
return getBranchesAndOrTags(repos, ['tags'], {
filter: options?.filter != null ? { tags: options.filter } : undefined,
picked: options?.picked,
sort: options?.sort != null ? { tags: options.sort } : true,
}) as Promise<TagQuickPickItem[]>;
} }
export async function getBranchesAndOrTags( export async function getBranchesAndOrTags(
@ -129,7 +139,7 @@ export async function getBranchesAndOrTags(
}: { }: {
filter?: { branches?: (b: GitBranch) => boolean; tags?: (t: GitTag) => boolean }; filter?: { branches?: (b: GitBranch) => boolean; tags?: (t: GitTag) => boolean };
picked?: string | string[]; picked?: string | string[];
sort?: boolean | { branches?: { current?: boolean; orderBy?: BranchSortingspan> }; tags?: { orderBy?: TagSorting } };
sort?: boolean | { branches?: BranchSortOptions; tags?: TagSortOptions };
} = {}, } = {},
): Promise<(BranchQuickPickItem | TagQuickPickItem)[]> { ): Promise<(BranchQuickPickItem | TagQuickPickItem)[]> {
let branches: GitBranch[] | undefined; let branches: GitBranch[] | undefined;
@ -384,19 +394,19 @@ export async function* pickBranchStep<
state: State, state: State,
context: Context, context: Context,
{ {
filterBranches,
filter,
picked, picked,
placeholder, placeholder,
titleContext, titleContext,
}: { }: {
filterBranches?: (b: GitBranch) => boolean;
filter?: (b: GitBranch) => boolean;
picked?: string | string[]; picked?: string | string[];
placeholder: string; placeholder: string;
titleContext?: string; titleContext?: string;
}, },
): AsyncStepResultGenerator<GitBranchReference> { ): AsyncStepResultGenerator<GitBranchReference> {
const branches = await getBranches(state.repo, { const branches = await getBranches(state.repo, {
filterBranches: filterBranches,
filter: filter,
picked: picked, picked: picked,
}); });
@ -448,20 +458,23 @@ export async function* pickBranchesStep<
state: State, state: State,
context: Context, context: Context,
{ {
filterBranches,
filter,
picked, picked,
placeholder, placeholder,
sort,
titleContext, titleContext,
}: { }: {
filterBranches?: (b: GitBranch) => boolean;
filter?: (b: GitBranch) => boolean;
picked?: string | string[]; picked?: string | string[];
placeholder: string; placeholder: string;
sort?: BranchSortOptions;
titleContext?: string; titleContext?: string;
}, },
): AsyncStepResultGenerator<GitBranchReference[]> { ): AsyncStepResultGenerator<GitBranchReference[]> {
const branches = await getBranches(state.repo, { const branches = await getBranches(state.repo, {
filterBranches: filterBranches,
filter: filter,
picked: picked, picked: picked,
sort: sort,
}); });
const step = QuickCommand.createPickStep<BranchQuickPickItem>({ const step = QuickCommand.createPickStep<BranchQuickPickItem>({
@ -1254,19 +1267,19 @@ export async function* pickTagsStep<
state: State, state: State,
context: Context, context: Context,
{ {
filterTags,
filter,
picked, picked,
placeholder, placeholder,
titleContext, titleContext,
}: { }: {
filterTags?: (b: GitTag) => boolean;
filter?: (b: GitTag) => boolean;
picked?: string | string[]; picked?: string | string[];
placeholder: string; placeholder: string;
titleContext?: string; titleContext?: string;
}, },
): AsyncStepResultGenerator<GitTagReference[]> { ): AsyncStepResultGenerator<GitTagReference[]> {
const tags = await getTags(state.repo, { const tags = await getTags(state.repo, {
filterTags: filterTags,
filter: filter,
picked: picked, picked: picked,
}); });

+ 10
- 10
src/git/gitService.ts View File

@ -22,13 +22,14 @@ import {
} from 'vscode'; } from 'vscode';
import { API as BuiltInGitApi, Repository as BuiltInGitRepository, GitExtension } from '../@types/git'; import { API as BuiltInGitApi, Repository as BuiltInGitRepository, GitExtension } from '../@types/git';
import { resetAvatarCache } from '../avatars'; import { resetAvatarCache } from '../avatars';
import { BranchSorting, configuration, TagSorting } from '../configuration';
import { configuration } from '../configuration';
import { BuiltInGitConfiguration, ContextKeys, DocumentSchemes, GlyphChars, setContext } from '../constants'; import { BuiltInGitConfiguration, ContextKeys, DocumentSchemes, GlyphChars, setContext } from '../constants';
import { Container } from '../container'; import { Container } from '../container';
import { setEnabled } from '../extension'; import { setEnabled } from '../extension';
import { import {
Authentication, Authentication,
BranchDateFormatting, BranchDateFormatting,
BranchSortOptions,
CommitDateFormatting, CommitDateFormatting,
Git, Git,
GitAuthor, GitAuthor,
@ -79,6 +80,7 @@ import {
RepositoryChangeComparisonMode, RepositoryChangeComparisonMode,
RepositoryChangeEvent, RepositoryChangeEvent,
SearchPattern, SearchPattern,
TagSortOptions,
} from './git'; } from './git';
import { GitUri } from './gitUri'; import { GitUri } from './gitUri';
import { LogCorrelationContext, Logger } from '../logger'; import { LogCorrelationContext, Logger } from '../logger';
@ -1232,7 +1234,7 @@ export class GitService implements Disposable {
true, true,
committerDate != null ? new Date(Number(committerDate) * 1000) : undefined, committerDate != null ? new Date(Number(committerDate) * 1000) : undefined,
data[1], data[1],
upstream,
upstream ? { name: upstream, missing: false } : undefined,
undefined, undefined,
undefined, undefined,
undefined, undefined,
@ -1250,7 +1252,7 @@ export class GitService implements Disposable {
}) })
async getBranchAheadRange(branch: GitBranch) { async getBranchAheadRange(branch: GitBranch) {
if (branch.state.ahead > 0) { if (branch.state.ahead > 0) {
return GitRevision.createRange(branch.upstream, branch.ref);
return GitRevision.createRange(branch.upstream?.name, branch.ref);
} }
if (branch.upstream == null) { if (branch.upstream == null) {
@ -1269,7 +1271,7 @@ export class GitService implements Disposable {
if (weightedBranch.weight === maxDefaultBranchWeight) break; if (weightedBranch.weight === maxDefaultBranchWeight) break;
} }
const possibleBranch = weightedBranch!.branch.upstream ?? weightedBranch!.branch.ref;
const possibleBranch = weightedBranch!.branch.upstream?.name ?? weightedBranch!.branch.ref;
if (possibleBranch !== branch.ref) { if (possibleBranch !== branch.ref) {
return GitRevision.createRange(possibleBranch, branch.ref); return GitRevision.createRange(possibleBranch, branch.ref);
} }
@ -1288,7 +1290,7 @@ export class GitService implements Disposable {
repoPath: string | undefined, repoPath: string | undefined,
options: { options: {
filter?: (b: GitBranch) => boolean; filter?: (b: GitBranch) => boolean;
sort?: boolean | { current?: boolean; orderBy?: BranchSorting };
sort?: boolean | BranchSortOptions;
} = {}, } = {},
): Promise<GitBranch[]> { ): Promise<GitBranch[]> {
if (repoPath == null) return []; if (repoPath == null) return [];
@ -1315,7 +1317,7 @@ export class GitService implements Disposable {
true, true,
committerDate != null ? new Date(Number(committerDate) * 1000) : undefined, committerDate != null ? new Date(Number(committerDate) * 1000) : undefined,
data[1], data[1],
upstream,
{ name: upstream, missing: false },
undefined, undefined,
undefined, undefined,
undefined, undefined,
@ -1363,9 +1365,7 @@ export class GitService implements Disposable {
}: { }: {
filter?: { branches?: (b: GitBranch) => boolean; tags?: (t: GitTag) => boolean }; filter?: { branches?: (b: GitBranch) => boolean; tags?: (t: GitTag) => boolean };
include?: 'all' | 'branches' | 'tags'; include?: 'all' | 'branches' | 'tags';
sort?:
| boolean
| { branches?: { current?: boolean; orderBy?: BranchSorting }; tags?: { orderBy?: TagSorting } };
sort?: boolean | { branches?: BranchSortOptions; tags?: TagSortOptions };
} = {}, } = {},
) { ) {
const [branches, tags] = await Promise.all<GitBranch[] | undefined, GitTag[] | undefined>([ const [branches, tags] = await Promise.all<GitBranch[] | undefined, GitTag[] | undefined>([
@ -3665,7 +3665,7 @@ export class GitService implements Disposable {
}) })
async getTags( async getTags(
repoPath: string | undefined, repoPath: string | undefined,
options: { filter?: (t: GitTag) => boolean; sort?: boolean | { orderBy?: TagSorting } } = {},
options: { filter?: (t: GitTag) => boolean; sort?: boolean | TagSortOptions } = {},
): Promise<GitTag[]> { ): Promise<GitTag[]> {
if (repoPath == null) return []; if (repoPath == null) return [];

+ 27
- 7
src/git/models/branch.ts View File

@ -30,11 +30,18 @@ export enum GitBranchStatus {
Behind = 'behind', Behind = 'behind',
Diverged = 'diverged', Diverged = 'diverged',
Local = 'local', Local = 'local',
MissingUpstream = 'missingUpstream',
Remote = 'remote', Remote = 'remote',
UpToDate = 'upToDate', UpToDate = 'upToDate',
Unpublished = 'unpublished', Unpublished = 'unpublished',
} }
export interface BranchSortOptions {
current?: boolean;
missingUpstream?: boolean;
orderBy?: BranchSorting;
}
export class GitBranch implements GitBranchReference { export class GitBranch implements GitBranchReference {
static is(branch: any): branch is GitBranch { static is(branch: any): branch is GitBranch {
return branch instanceof GitBranch; return branch instanceof GitBranch;
@ -44,13 +51,16 @@ export class GitBranch implements GitBranchReference {
return branch?.refType === 'branch'; return branch?.refType === 'branch';
} }
static sort(branches: GitBranch[], options?: { current?: boolean; orderBy?: BranchSorting }) {
static sort(branches: GitBranch[], options?: BranchSortOptions) {
options = { current: true, orderBy: configuration.get('sortBranchesBy'), ...options }; options = { current: true, orderBy: configuration.get('sortBranchesBy'), ...options };
switch (options.orderBy) { switch (options.orderBy) {
case BranchSorting.DateAsc: case BranchSorting.DateAsc:
return branches.sort( return branches.sort(
(a, b) => (a, b) =>
(options!.missingUpstream
? (a.upstream?.missing ? -1 : 1) - (b.upstream?.missing ? -1 : 1)
: 0) ||
(options!.current ? (a.current ? -1 : 1) - (b.current ? -1 : 1) : 0) || (options!.current ? (a.current ? -1 : 1) - (b.current ? -1 : 1) : 0) ||
(a.starred ? -1 : 1) - (b.starred ? -1 : 1) || (a.starred ? -1 : 1) - (b.starred ? -1 : 1) ||
(b.remote ? -1 : 1) - (a.remote ? -1 : 1) || (b.remote ? -1 : 1) - (a.remote ? -1 : 1) ||
@ -59,6 +69,9 @@ export class GitBranch implements GitBranchReference {
case BranchSorting.DateDesc: case BranchSorting.DateDesc:
return branches.sort( return branches.sort(
(a, b) => (a, b) =>
(options!.missingUpstream
? (a.upstream?.missing ? -1 : 1) - (b.upstream?.missing ? -1 : 1)
: 0) ||
(options!.current ? (a.current ? -1 : 1) - (b.current ? -1 : 1) : 0) || (options!.current ? (a.current ? -1 : 1) - (b.current ? -1 : 1) : 0) ||
(a.starred ? -1 : 1) - (b.starred ? -1 : 1) || (a.starred ? -1 : 1) - (b.starred ? -1 : 1) ||
(b.remote ? -1 : 1) - (a.remote ? -1 : 1) || (b.remote ? -1 : 1) - (a.remote ? -1 : 1) ||
@ -67,6 +80,9 @@ export class GitBranch implements GitBranchReference {
case BranchSorting.NameAsc: case BranchSorting.NameAsc:
return branches.sort( return branches.sort(
(a, b) => (a, b) =>
(options!.missingUpstream
? (a.upstream?.missing ? -1 : 1) - (b.upstream?.missing ? -1 : 1)
: 0) ||
(options!.current ? (a.current ? -1 : 1) - (b.current ? -1 : 1) : 0) || (options!.current ? (a.current ? -1 : 1) - (b.current ? -1 : 1) : 0) ||
(a.starred ? -1 : 1) - (b.starred ? -1 : 1) || (a.starred ? -1 : 1) - (b.starred ? -1 : 1) ||
(a.name === 'main' ? -1 : 1) - (b.name === 'main' ? -1 : 1) || (a.name === 'main' ? -1 : 1) - (b.name === 'main' ? -1 : 1) ||
@ -78,6 +94,9 @@ export class GitBranch implements GitBranchReference {
default: default:
return branches.sort( return branches.sort(
(a, b) => (a, b) =>
(options!.missingUpstream
? (a.upstream?.missing ? -1 : 1) - (b.upstream?.missing ? -1 : 1)
: 0) ||
(options!.current ? (a.current ? -1 : 1) - (b.current ? -1 : 1) : 0) || (options!.current ? (a.current ? -1 : 1) - (b.current ? -1 : 1) : 0) ||
(a.starred ? -1 : 1) - (b.starred ? -1 : 1) || (a.starred ? -1 : 1) - (b.starred ? -1 : 1) ||
(a.name === 'main' ? -1 : 1) - (b.name === 'main' ? -1 : 1) || (a.name === 'main' ? -1 : 1) - (b.name === 'main' ? -1 : 1) ||
@ -92,7 +111,7 @@ export class GitBranch implements GitBranchReference {
readonly refType = 'branch'; readonly refType = 'branch';
readonly detached: boolean; readonly detached: boolean;
readonly id: string; readonly id: string;
readonly upstream?: string;
readonly upstream?: { name: string; missing: boolean };
readonly state: GitTrackingState; readonly state: GitTrackingState;
constructor( constructor(
@ -102,7 +121,7 @@ export class GitBranch implements GitBranchReference {
public readonly current: boolean, public readonly current: boolean,
public readonly date: Date | undefined, public readonly date: Date | undefined,
public readonly sha?: string, public readonly sha?: string,
upstream?: string,
upstream?: { name: string; missing: boolean },
ahead: number = 0, ahead: number = 0,
behind: number = 0, behind: number = 0,
detached: boolean = false, detached: boolean = false,
@ -115,7 +134,7 @@ export class GitBranch implements GitBranchReference {
this.name = GitBranch.formatDetached(this.sha!); this.name = GitBranch.formatDetached(this.sha!);
} }
this.upstream = upstream == null || upstream.length === 0 ? undefined : upstream;
this.upstream = upstream == null || upstream.name.length === 0 ? undefined : upstream;
this.state = { this.state = {
ahead: ahead, ahead: ahead,
behind: behind, behind: behind,
@ -177,7 +196,7 @@ export class GitBranch implements GitBranchReference {
@memoize() @memoize()
getTrackingWithoutRemote(): string | undefined { getTrackingWithoutRemote(): string | undefined {
return this.upstream?.substring(this.upstream.indexOf('/') + 1);
return this.upstream?.name.substring(this.upstream.name.indexOf('/') + 1);
} }
@memoize() @memoize()
@ -194,7 +213,7 @@ export class GitBranch implements GitBranchReference {
@memoize() @memoize()
getRemoteName(): string | undefined { getRemoteName(): string | undefined {
if (this.remote) return GitBranch.getRemote(this.name); if (this.remote) return GitBranch.getRemote(this.name);
if (this.upstream != null) return GitBranch.getRemote(this.upstream);
if (this.upstream != null) return GitBranch.getRemote(this.upstream.name);
return undefined; return undefined;
} }
@ -203,7 +222,8 @@ export class GitBranch implements GitBranchReference {
async getStatus(): Promise<GitBranchStatus> { async getStatus(): Promise<GitBranchStatus> {
if (this.remote) return GitBranchStatus.Remote; if (this.remote) return GitBranchStatus.Remote;
if (this.upstream) {
if (this.upstream != null) {
if (this.upstream.missing) return GitBranchStatus.MissingUpstream;
if (this.state.ahead && this.state.behind) return GitBranchStatus.Diverged; if (this.state.ahead && this.state.behind) return GitBranchStatus.Diverged;
if (this.state.ahead) return GitBranchStatus.Ahead; if (this.state.ahead) return GitBranchStatus.Ahead;
if (this.state.behind) return GitBranchStatus.Behind; if (this.state.behind) return GitBranchStatus.Behind;

+ 2
- 2
src/git/models/models.ts View File

@ -110,7 +110,7 @@ export interface GitBranchReference {
name: string; name: string;
ref: string; ref: string;
readonly remote: boolean; readonly remote: boolean;
readonly upstream?: string;
readonly upstream?: { name: string; missing: boolean };
repoPath: string; repoPath: string;
} }
@ -147,7 +147,7 @@ export namespace GitReference {
export function create( export function create(
ref: string, ref: string,
repoPath: string, repoPath: string,
options: { refType: 'branch'; name: string; remote: boolean; upstream?: string },
options: { refType: 'branch'; name: string; remote: boolean; upstream?: { name: string; missing: boolean } },
): GitBranchReference; ): GitBranchReference;
export function create( export function create(
ref: string, ref: string,

+ 11
- 12
src/git/models/repository.ts View File

@ -15,7 +15,7 @@ import {
} from 'vscode'; } from 'vscode';
import { CreatePullRequestActionContext } from '../../api/gitlens'; import { CreatePullRequestActionContext } from '../../api/gitlens';
import { executeActionCommand } from '../../commands'; import { executeActionCommand } from '../../commands';
import { BranchSorting, configuration, TagSorting } from '../../configuration';
import { configuration } from '../../configuration';
import { BuiltInGitCommands, BuiltInGitConfiguration, Starred, WorkspaceState } from '../../constants'; import { BuiltInGitCommands, BuiltInGitConfiguration, Starred, WorkspaceState } from '../../constants';
import { Container } from '../../container'; import { Container } from '../../container';
import { import {
@ -33,6 +33,7 @@ import { GitUri } from '../gitUri';
import { Logger } from '../../logger'; import { Logger } from '../../logger';
import { Messages } from '../../messages'; import { Messages } from '../../messages';
import { import {
BranchSortOptions,
GitBranchReference, GitBranchReference,
GitLog, GitLog,
GitLogCommit, GitLogCommit,
@ -40,6 +41,7 @@ import {
GitRebaseStatus, GitRebaseStatus,
GitReference, GitReference,
GitTagReference, GitTagReference,
TagSortOptions,
} from './models'; } from './models';
import { RemoteProviderFactory, RemoteProviders, RichRemoteProvider } from '../remotes/factory'; import { RemoteProviderFactory, RemoteProviders, RichRemoteProvider } from '../remotes/factory';
import { Arrays, Dates, debug, Functions, gate, Iterables, log, logName } from '../../system'; import { Arrays, Dates, debug, Functions, gate, Iterables, log, logName } from '../../system';
@ -445,14 +447,16 @@ export class Repository implements Disposable {
if (remote) { if (remote) {
const trackingBranches = localBranches.filter(b => b.upstream != null); const trackingBranches = localBranches.filter(b => b.upstream != null);
if (trackingBranches.length !== 0) { if (trackingBranches.length !== 0) {
const branchesByOrigin = Arrays.groupByMap(trackingBranches, b => GitBranch.getRemote(b.upstream!));
const branchesByOrigin = Arrays.groupByMap(trackingBranches, b =>
GitBranch.getRemote(b.upstream!.name),
);
for (const [remote, branches] of branchesByOrigin.entries()) { for (const [remote, branches] of branchesByOrigin.entries()) {
this.runTerminalCommand( this.runTerminalCommand(
'push', 'push',
'-d', '-d',
remote, remote,
...branches.map(b => GitBranch.getNameWithoutRemote(b.upstream!)),
...branches.map(b => GitBranch.getNameWithoutRemote(b.upstream!.name)),
); );
} }
} }
@ -543,7 +547,7 @@ export class Repository implements Disposable {
getBranches( getBranches(
options: { options: {
filter?: (b: GitBranch) => boolean; filter?: (b: GitBranch) => boolean;
sort?: boolean | { current?: boolean; orderBy?: BranchSorting };
sort?: boolean | BranchSortOptions;
} = {}, } = {},
): Promise<GitBranch[]> { ): Promise<GitBranch[]> {
return Container.git.getBranches(this.path, options); return Container.git.getBranches(this.path, options);
@ -553,9 +557,7 @@ export class Repository implements Disposable {
options: { options: {
filter?: { branches?: (b: GitBranch) => boolean; tags?: (t: GitTag) => boolean }; filter?: { branches?: (b: GitBranch) => boolean; tags?: (t: GitTag) => boolean };
include?: 'all' | 'branches' | 'tags'; include?: 'all' | 'branches' | 'tags';
sort?:
| boolean
| { branches?: { current?: boolean; orderBy?: BranchSorting }; tags?: { orderBy?: TagSorting } };
sort?: boolean | { branches?: BranchSortOptions; tags?: TagSortOptions };
} = {}, } = {},
) { ) {
return Container.git.getBranchesAndOrTags(this.path, options); return Container.git.getBranchesAndOrTags(this.path, options);
@ -646,10 +648,7 @@ export class Repository implements Disposable {
return Container.git.getStatusForRepo(this.path); return Container.git.getStatusForRepo(this.path);
} }
getTags(options?: {
filter?: (t: GitTag) => boolean;
sort?: boolean | { orderBy?: TagSorting };
}): Promise<GitTag[]> {
getTags(options?: { filter?: (t: GitTag) => boolean; sort?: boolean | TagSortOptions }): Promise<GitTag[]> {
return Container.git.getTags(this.path, options); return Container.git.getTags(this.path, options);
} }
@ -760,7 +759,7 @@ export class Repository implements Disposable {
branch: { branch: {
name: branch.name, name: branch.name,
isRemote: branch.remote, isRemote: branch.remote,
upstream: branch.upstream,
upstream: branch.upstream?.name,
}, },
}); });
} }

+ 28
- 16
src/git/models/status.ts View File

@ -249,11 +249,15 @@ export class GitStatus {
separator?: string; separator?: string;
suffix?: string; suffix?: string;
}): string { }): string {
return GitStatus.getUpstreamStatus(this.upstream, this.state, options);
return GitStatus.getUpstreamStatus(
this.upstream ? { name: this.upstream, missing: false } : undefined,
this.state,
options,
);
} }
static getUpstreamStatus( static getUpstreamStatus(
upstream: string | undefined,
upstream: { name: string; missing: boolean } | undefined,
state: { ahead: number; behind: number }, state: { ahead: number; behind: number },
options: { options: {
count?: boolean; count?: boolean;
@ -270,25 +274,33 @@ export class GitStatus {
if (expand) { if (expand) {
let status = ''; let status = '';
if (state.behind) {
status += `${Strings.pluralize('commit', state.behind, {
infix: icons ? '$(arrow-down) ' : undefined,
})} behind`;
}
if (state.ahead) {
status += `${status.length === 0 ? '' : separator}${Strings.pluralize('commit', state.ahead, {
infix: icons ? '$(arrow-up) ' : undefined,
})} ahead`;
if (suffix.startsWith(` ${upstream.split('/')[0]}`)) {
status += ' of';
if (upstream.missing) {
status = 'missing';
} else {
if (state.behind) {
status += `${Strings.pluralize('commit', state.behind, {
infix: icons ? '$(arrow-down) ' : undefined,
})} behind`;
}
if (state.ahead) {
status += `${status.length === 0 ? '' : separator}${Strings.pluralize('commit', state.ahead, {
infix: icons ? '$(arrow-up) ' : undefined,
})} ahead`;
if (suffix.startsWith(` ${upstream.name.split('/')[0]}`)) {
status += ' of';
}
} }
} }
return `${prefix}${status}${suffix}`; return `${prefix}${status}${suffix}`;
} }
return `${prefix}${count ? state.behind : ''}${
count || state.behind !== 0 ? GlyphChars.ArrowDown : ''
}${separator}${count ? state.ahead : ''}${count || state.ahead !== 0 ? GlyphChars.ArrowUp : ''}${suffix}`;
const showCounts = count && !upstream.missing;
return `${prefix}${showCounts ? state.behind : ''}${
showCounts || state.behind !== 0 ? GlyphChars.ArrowDown : ''
}${separator}${showCounts ? state.ahead : ''}${
showCounts || state.ahead !== 0 ? GlyphChars.ArrowUp : ''
}${suffix}`;
} }
} }

+ 6
- 1
src/git/models/tag.ts View File

@ -13,6 +13,11 @@ export const TagDateFormatting = {
}, },
}; };
export interface TagSortOptions {
current?: boolean;
orderBy?: TagSorting;
}
export class GitTag implements GitTagReference { export class GitTag implements GitTagReference {
static is(tag: any): tag is GitTag { static is(tag: any): tag is GitTag {
return tag instanceof GitTag; return tag instanceof GitTag;
@ -22,7 +27,7 @@ export class GitTag implements GitTagReference {
return tag?.refType === 'tag'; return tag?.refType === 'tag';
} }
static sort(tags: GitTag[], options?: { orderBy?: TagSortingspan> }) {
static sort(tags: GitTag[], options?: TagSortOptions) {
options = { orderBy: configuration.get('sortTagsBy'), ...options }; options = { orderBy: configuration.get('sortTagsBy'), ...options };
switch (options.orderBy) { switch (options.orderBy) {

+ 6
- 3
src/git/parsers/branchParser.ts View File

@ -2,7 +2,7 @@
import { GitBranch } from '../models/branch'; import { GitBranch } from '../models/branch';
import { debug } from '../../system'; import { debug } from '../../system';
const branchWithTrackingRegex = /^<h>(.+)<n>(.+)<u>(.*)<t>(?:\[(?:ahead ([0-9]+))?[,\s]*(?:behind ([0-9]+))?]|\[gone])?<r>(.*)<d>(.*)$/gm;
const branchWithTrackingRegex = /^<h>(.+)<n>(.+)<u>(.*)<t>(?:\[(?:ahead ([0-9]+))?[,\s]*(?:behind ([0-9]+))?]|\[(gone)])?<r>(.*)<d>(.*)$/gm;
// Using %x00 codes because some shells seem to try to expand things if not // Using %x00 codes because some shells seem to try to expand things if not
const lb = '%3c'; // `%${'<'.charCodeAt(0).toString(16)}`; const lb = '%3c'; // `%${'<'.charCodeAt(0).toString(16)}`;
@ -29,6 +29,7 @@ export class GitBranchParser {
let upstream; let upstream;
let ahead; let ahead;
let behind; let behind;
let missing;
let ref; let ref;
let date; let date;
@ -39,7 +40,7 @@ export class GitBranchParser {
match = branchWithTrackingRegex.exec(data); match = branchWithTrackingRegex.exec(data);
if (match == null) break; if (match == null) break;
[, current, name, upstream, ahead, behind, ref, date] = match;
[, current, name, upstream, ahead, behind, missing, ref, date] = match;
if (name.startsWith('refs/remotes/')) { if (name.startsWith('refs/remotes/')) {
// Strip off refs/remotes/ // Strip off refs/remotes/
@ -63,7 +64,9 @@ export class GitBranchParser {
// Stops excessive memory usage -- https://bugs.chromium.org/p/v8/issues/detail?id=2869 // Stops excessive memory usage -- https://bugs.chromium.org/p/v8/issues/detail?id=2869
ref == null || ref.length === 0 ? undefined : ` ${ref}`.substr(1), ref == null || ref.length === 0 ? undefined : ` ${ref}`.substr(1),
// Stops excessive memory usage -- https://bugs.chromium.org/p/v8/issues/detail?id=2869 // Stops excessive memory usage -- https://bugs.chromium.org/p/v8/issues/detail?id=2869
upstream == null || upstream.length === 0 ? undefined : ` ${upstream}`.substr(1),
upstream == null || upstream.length === 0
? undefined
: { name: ` ${upstream}`.substr(1), missing: Boolean(missing) },
Number(ahead) || 0, Number(ahead) || 0,
Number(behind) || 0, Number(behind) || 0,
), ),

+ 25
- 21
src/quickpicks/gitQuickPickItems.ts View File

@ -63,37 +63,41 @@ export namespace BranchQuickPickItem {
description = 'current branch'; description = 'current branch';
} }
if (options.status && !branch.remote && branch.upstream !== undefined) {
if (options.status && !branch.remote && branch.upstream != null) {
let arrows = GlyphChars.Dash; let arrows = GlyphChars.Dash;
const remote = await branch.getRemote(); const remote = await branch.getRemote();
if (remote !== undefined) {
let left;
let right;
for (const { type } of remote.urls) {
if (type === GitRemoteType.Fetch) {
left = true;
if (right) break;
} else if (type === GitRemoteType.Push) {
right = true;
if (left) break;
if (!branch.upstream.missing) {
if (remote != null) {
let left;
let right;
for (const { type } of remote.urls) {
if (type === GitRemoteType.Fetch) {
left = true;
if (right) break;
} else if (type === GitRemoteType.Push) {
right = true;
if (left) break;
}
} }
}
if (left && right) {
arrows = GlyphChars.ArrowsRightLeft;
} else if (right) {
arrows = GlyphChars.ArrowRight;
} else if (left) {
arrows = GlyphChars.ArrowLeft;
if (left && right) {
arrows = GlyphChars.ArrowsRightLeft;
} else if (right) {
arrows = GlyphChars.ArrowRight;
} else if (left) {
arrows = GlyphChars.ArrowLeft;
}
} }
} else {
arrows = GlyphChars.Warning;
} }
const status = `${branch.getTrackingStatus({ suffix: `${GlyphChars.Space} ` })}${arrows}${ const status = `${branch.getTrackingStatus({ suffix: `${GlyphChars.Space} ` })}${arrows}${
GlyphChars.Space GlyphChars.Space
} ${branch.upstream}`;
} ${branch.upstream.name}`;
description = `${description ? `${description}${GlyphChars.Space.repeat(2)}${status}` : status}`; description = `${description ? `${description}${GlyphChars.Space.repeat(2)}${status}` : status}`;
} }

+ 2
- 3
src/quickpicks/referencePicker.ts View File

@ -2,11 +2,10 @@
import { CancellationTokenSource, Disposable, QuickPick, window } from 'vscode'; import { CancellationTokenSource, Disposable, QuickPick, window } from 'vscode';
import { GlyphChars } from '../constants'; import { GlyphChars } from '../constants';
import { Container } from '../container'; import { Container } from '../container';
import { GitBranch, GitReference, GitTag } from '../git/git';
import { BranchSortOptions, GitBranch, GitReference, GitTag, TagSortOptions } from '../git/git';
import { KeyboardScope, Keys } from '../keyboard'; import { KeyboardScope, Keys } from '../keyboard';
import { BranchQuickPickItem, getQuickPickIgnoreFocusOut, RefQuickPickItem, TagQuickPickItem } from '../quickpicks'; import { BranchQuickPickItem, getQuickPickIgnoreFocusOut, RefQuickPickItem, TagQuickPickItem } from '../quickpicks';
import { getBranchesAndOrTags, getValidateGitReferenceFn } from '../commands/quickCommand'; import { getBranchesAndOrTags, getValidateGitReferenceFn } from '../commands/quickCommand';
import { BranchSorting, TagSorting } from '../configuration';
export type ReferencesQuickPickItem = BranchQuickPickItem | TagQuickPickItem | RefQuickPickItem; export type ReferencesQuickPickItem = BranchQuickPickItem | TagQuickPickItem | RefQuickPickItem;
@ -27,7 +26,7 @@ export interface ReferencesQuickPickOptions {
include?: ReferencesQuickPickIncludes; include?: ReferencesQuickPickIncludes;
keys?: Keys[]; keys?: Keys[];
onDidPressKey?(key: Keys, quickpick: QuickPick<ReferencesQuickPickItem>): void | Promise<void>; onDidPressKey?(key: Keys, quickpick: QuickPick<ReferencesQuickPickItem>): void | Promise<void>;
sort?: boolean | { branches?: { current?: boolean; orderBy?: BranchSortingspan> }; tags?: { orderBy?: TagSorting } };
sort?: boolean | { branches?: BranchSortOptions; tags?: TagSortOptions };
} }
export namespace ReferencePicker { export namespace ReferencePicker {

+ 35
- 29
src/views/nodes/branchNode.ts View File

@ -151,7 +151,7 @@ export class BranchNode
this.options.showStatus ? Container.git.getRebaseStatus(this.uri.repoPath!) : undefined, this.options.showStatus ? Container.git.getRebaseStatus(this.uri.repoPath!) : undefined,
this.view.config.pullRequests.enabled && this.view.config.pullRequests.enabled &&
this.view.config.pullRequests.showForBranches && this.view.config.pullRequests.showForBranches &&
(this.branch.upstream || this.branch.remote)
(this.branch.upstream != null || this.branch.remote)
? this.branch.getAssociatedPullRequest( ? this.branch.getAssociatedPullRequest(
this.root ? { include: [PullRequestState.Open, PullRequestState.Merged] } : undefined, this.root ? { include: [PullRequestState.Open, PullRequestState.Merged] } : undefined,
) )
@ -213,10 +213,10 @@ export class BranchNode
ref: this.branch.ref, ref: this.branch.ref,
repoPath: this.branch.repoPath, repoPath: this.branch.repoPath,
state: this.branch.state, state: this.branch.state,
upstream: this.branch.upstream,
upstream: this.branch.upstream?.name,
}; };
if (this.branch.upstream) {
if (this.branch.upstream != null) {
if (this.root && !status.state.behind && !status.state.ahead) { if (this.root && !status.state.behind && !status.state.ahead) {
children.push( children.push(
new BranchTrackingStatusNode(this.view, this, this.branch, status, 'same', this.root), new BranchTrackingStatusNode(this.view, this, this.branch, status, 'same', this.root),
@ -293,7 +293,7 @@ export class BranchNode
if (this.branch.starred) { if (this.branch.starred) {
contextValue += '+starred'; contextValue += '+starred';
} }
if (this.branch.upstream) {
if (this.branch.upstream != null && !this.branch.upstream.missing) {
contextValue += '+tracking'; contextValue += '+tracking';
} }
if (this.options.showAsCommits) { if (this.options.showAsCommits) {
@ -308,28 +308,32 @@ export class BranchNode
let arrows = GlyphChars.Dash; let arrows = GlyphChars.Dash;
const remote = await this.branch.getRemote(); const remote = await this.branch.getRemote();
if (remote != null) {
let left;
let right;
for (const { type } of remote.urls) {
if (type === GitRemoteType.Fetch) {
left = true;
if (right) break;
} else if (type === GitRemoteType.Push) {
right = true;
if (left) break;
if (!this.branch.upstream.missing) {
if (remote != null) {
let left;
let right;
for (const { type } of remote.urls) {
if (type === GitRemoteType.Fetch) {
left = true;
if (right) break;
} else if (type === GitRemoteType.Push) {
right = true;
if (left) break;
}
} }
}
if (left && right) {
arrows = GlyphChars.ArrowsRightLeft;
} else if (right) {
arrows = GlyphChars.ArrowRight;
} else if (left) {
arrows = GlyphChars.ArrowLeft;
if (left && right) {
arrows = GlyphChars.ArrowsRightLeft;
} else if (right) {
arrows = GlyphChars.ArrowRight;
} else if (left) {
arrows = GlyphChars.ArrowLeft;
}
} }
} else {
arrows = GlyphChars.Warning;
} }
description = this.options.showAsCommits description = this.options.showAsCommits
@ -339,19 +343,21 @@ export class BranchNode
arrows, arrows,
2, 2,
2, 2,
)}${this.branch.upstream}`
)}${this.branch.upstream.name}`
: `${this.branch.getTrackingStatus({ suffix: `${GlyphChars.Space} ` })}${arrows}${ : `${this.branch.getTrackingStatus({ suffix: `${GlyphChars.Space} ` })}${arrows}${
GlyphChars.Space GlyphChars.Space
} ${this.branch.upstream}`;
} ${this.branch.upstream.name}`;
tooltip += ` is ${this.branch.getTrackingStatus({ tooltip += ` is ${this.branch.getTrackingStatus({
empty: `up to date with $(git-branch) ${this.branch.upstream}${
remote?.provider?.name ? ` on ${remote.provider.name}` : ''
}`,
empty: this.branch.upstream.missing
? `missing upstream $(git-branch) ${this.branch.upstream.name}`
: `up to date with $(git-branch) ${this.branch.upstream.name}${
remote?.provider?.name ? ` on ${remote.provider.name}` : ''
}`,
expand: true, expand: true,
icons: true, icons: true,
separator: ', ', separator: ', ',
suffix: ` $(git-branch) ${this.branch.upstream}${
suffix: ` $(git-branch) ${this.branch.upstream.name}${
remote?.provider?.name ? ` on ${remote.provider.name}` : '' remote?.provider?.name ? ` on ${remote.provider.name}` : ''
}`, }`,
})}`; })}`;

+ 1
- 1
src/views/nodes/repositoryNode.ts View File

@ -66,7 +66,7 @@ export class RepositoryNode extends SubscribeableViewNode {
true, true,
undefined, undefined,
status.sha, status.sha,
status.upstream,
status.upstream ? { name: status.upstream, missing: false } : undefined,
status.state.ahead, status.state.ahead,
status.state.behind, status.state.behind,
status.detached, status.detached,

+ 9
- 5
src/views/nodes/viewNode.ts View File

@ -409,15 +409,19 @@ export abstract class RepositoryFolderNode<
}${this.repo.formattedName ? `\n${this.uri.repoPath}` : ''}\n\nCurrent branch $(git-branch) ${ }${this.repo.formattedName ? `\n${this.uri.repoPath}` : ''}\n\nCurrent branch $(git-branch) ${
branch.name branch.name
}${ }${
branch.upstream
branch.upstream != null
? ` is ${branch.getTrackingStatus({ ? ` is ${branch.getTrackingStatus({
empty: `up to date with $(git-branch) ${branch.upstream}${
providerName ? ` on ${providerName}` : ''
}`,
empty: branch.upstream.missing
? `missing upstream $(git-branch) ${branch.upstream.name}`
: `up to date with $(git-branch) ${branch.upstream.name}${
providerName ? ` on ${providerName}` : ''
}`,
expand: true, expand: true,
icons: true, icons: true,
separator: ', ', separator: ', ',
suffix: ` $(git-branch) ${branch.upstream}${providerName ? ` on ${providerName}` : ''}`,
suffix: ` $(git-branch) ${branch.upstream.name}${
providerName ? ` on ${providerName}` : ''
}`,
})}` })}`
: `hasn't been published to ${providerName ?? 'a remote'}` : `hasn't been published to ${providerName ?? 'a remote'}`
}${ }${

+ 4
- 4
src/views/viewCommands.ts View File

@ -311,7 +311,7 @@ export class ViewCommands {
: undefined, : undefined,
branch: { branch: {
name: node.branch.name, name: node.branch.name,
upstream: node.branch.upstream,
upstream: node.branch.upstream?.name,
isRemote: node.branch.remote, isRemote: node.branch.remote,
}, },
}); });
@ -516,7 +516,7 @@ export class ViewCommands {
private rebaseToRemote(node: BranchNode | BranchTrackingStatusNode) { private rebaseToRemote(node: BranchNode | BranchTrackingStatusNode) {
if (!(node instanceof BranchNode) && !(node instanceof BranchTrackingStatusNode)) return Promise.resolve(); if (!(node instanceof BranchNode) && !(node instanceof BranchTrackingStatusNode)) return Promise.resolve();
const upstream = node instanceof BranchNode ? node.branch.upstream : node.status.upstream;
const upstream = node instanceof BranchNode ? node.branch.upstream?.name : node.status.upstream;
if (upstream == null) return Promise.resolve(); if (upstream == null) return Promise.resolve();
return GitActions.rebase( return GitActions.rebase(
@ -712,9 +712,9 @@ export class ViewCommands {
@debug() @debug()
private compareWithUpstream(node: BranchNode) { private compareWithUpstream(node: BranchNode) {
if (!(node instanceof BranchNode)) return Promise.resolve(); if (!(node instanceof BranchNode)) return Promise.resolve();
if (!node.branch.upstream) return Promise.resolve();
if (node.branch.upstream == null) return Promise.resolve();
return Container.searchAndCompareView.compare(node.repoPath, node.branch.upstream, node.ref);
return Container.searchAndCompareView.compare(node.repoPath, node.branch.upstream.name, node.ref);
} }
@debug() @debug()

+ 6
- 0
src/views/viewDecorationProvider.ts View File

@ -132,6 +132,12 @@ export class ViewFileDecorationProvider implements FileDecorationProvider, Dispo
color: new ThemeColor('gitlens.decorations.branchDivergedForegroundColor'), color: new ThemeColor('gitlens.decorations.branchDivergedForegroundColor'),
tooltip: 'Diverged', tooltip: 'Diverged',
}; };
case GitBranchStatus.MissingUpstream:
return {
badge: '!',
color: new ThemeColor('gitlens.decorations.branchMissingUpstreamForegroundColor'),
tooltip: 'Missing Upstream',
};
case GitBranchStatus.UpToDate: case GitBranchStatus.UpToDate:
return { return {
badge: '', badge: '',

Loading…
Cancel
Save