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
- 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 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))

+ 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)
- `⮟⮝` + 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
- `!` + 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
- **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

+ 9
- 0
package.json View File

@ -2968,6 +2968,15 @@
"light": "#35b15e",
"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": [

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

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

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

@ -160,13 +160,16 @@ export class OpenFileOnRemoteCommand extends ActiveEditorCommand {
if (pick.refType === 'branch') {
if (pick.remote) {
args.branchOrTag = GitBranch.getNameWithoutRemote(pick.name);
const remoteName = GitBranch.getRemote(pick.name);
const remote = remotes.find(r => r.name === remoteName);
if (remote != null) {
remotes = [remote];
}
} else {
args.branchOrTag = pick.name;
}
args.branchOrTag = pick.remote ? GitBranch.getNameWithoutRemote(pick.name) : pick.name;
sha = undefined;
} else if (pick.refType === 'tag') {
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 { GlyphChars, quickPickTitleMaxChars } from '../constants';
import {
BranchSortOptions,
GitBranch,
GitBranchReference,
GitContributor,
@ -23,6 +24,7 @@ import {
RemoteResourceType,
Repository,
SearchPattern,
TagSortOptions,
} from '../git/git';
import { GitService } from '../git/gitService';
import {
@ -107,16 +109,24 @@ export function appendReposToTitle<
export async function getBranches(
repos: Repository | Repository[],
options: { filterBranches?: (b: GitBranch) => boolean; picked?: string | string[] } = {},
options: { filter?: (b: GitBranch) => boolean; picked?: string | string[]; sort?: BranchSortOptions } = {},
): 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(
repos: Repository | Repository[],
options: { filterTags?: (t: GitTag) => boolean; picked?: string | string[] } = {},
options?: { filter?: (t: GitTag) => boolean; picked?: string | string[]; sort?: TagSortOptions },
): 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(
@ -129,7 +139,7 @@ export async function getBranchesAndOrTags(
}: {
filter?: { branches?: (b: GitBranch) => boolean; tags?: (t: GitTag) => boolean };
picked?: string | string[];
sort?: boolean | { branches?: { current?: boolean; orderBy?: BranchSortingspan> }; tags?: { orderBy?: TagSorting } };
sort?: boolean | { branches?: BranchSortOptions; tags?: TagSortOptions };
} = {},
): Promise<(BranchQuickPickItem | TagQuickPickItem)[]> {
let branches: GitBranch[] | undefined;
@ -384,19 +394,19 @@ export async function* pickBranchStep<
state: State,
context: Context,
{
filterBranches,
filter,
picked,
placeholder,
titleContext,
}: {
filterBranches?: (b: GitBranch) => boolean;
filter?: (b: GitBranch) => boolean;
picked?: string | string[];
placeholder: string;
titleContext?: string;
},
): AsyncStepResultGenerator<GitBranchReference> {
const branches = await getBranches(state.repo, {
filterBranches: filterBranches,
filter: filter,
picked: picked,
});
@ -448,20 +458,23 @@ export async function* pickBranchesStep<
state: State,
context: Context,
{
filterBranches,
filter,
picked,
placeholder,
sort,
titleContext,
}: {
filterBranches?: (b: GitBranch) => boolean;
filter?: (b: GitBranch) => boolean;
picked?: string | string[];
placeholder: string;
sort?: BranchSortOptions;
titleContext?: string;
},
): AsyncStepResultGenerator<GitBranchReference[]> {
const branches = await getBranches(state.repo, {
filterBranches: filterBranches,
filter: filter,
picked: picked,
sort: sort,
});
const step = QuickCommand.createPickStep<BranchQuickPickItem>({
@ -1254,19 +1267,19 @@ export async function* pickTagsStep<
state: State,
context: Context,
{
filterTags,
filter,
picked,
placeholder,
titleContext,
}: {
filterTags?: (b: GitTag) => boolean;
filter?: (b: GitTag) => boolean;
picked?: string | string[];
placeholder: string;
titleContext?: string;
},
): AsyncStepResultGenerator<GitTagReference[]> {
const tags = await getTags(state.repo, {
filterTags: filterTags,
filter: filter,
picked: picked,
});

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

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

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

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

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

@ -15,7 +15,7 @@ import {
} from 'vscode';
import { CreatePullRequestActionContext } from '../../api/gitlens';
import { executeActionCommand } from '../../commands';
import { BranchSorting, configuration, TagSorting } from '../../configuration';
import { configuration } from '../../configuration';
import { BuiltInGitCommands, BuiltInGitConfiguration, Starred, WorkspaceState } from '../../constants';
import { Container } from '../../container';
import {
@ -33,6 +33,7 @@ import { GitUri } from '../gitUri';
import { Logger } from '../../logger';
import { Messages } from '../../messages';
import {
BranchSortOptions,
GitBranchReference,
GitLog,
GitLogCommit,
@ -40,6 +41,7 @@ import {
GitRebaseStatus,
GitReference,
GitTagReference,
TagSortOptions,
} from './models';
import { RemoteProviderFactory, RemoteProviders, RichRemoteProvider } from '../remotes/factory';
import { Arrays, Dates, debug, Functions, gate, Iterables, log, logName } from '../../system';
@ -445,14 +447,16 @@ export class Repository implements Disposable {
if (remote) {
const trackingBranches = localBranches.filter(b => b.upstream != null);
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()) {
this.runTerminalCommand(
'push',
'-d',
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(
options: {
filter?: (b: GitBranch) => boolean;
sort?: boolean | { current?: boolean; orderBy?: BranchSorting };
sort?: boolean | BranchSortOptions;
} = {},
): Promise<GitBranch[]> {
return Container.git.getBranches(this.path, options);
@ -553,9 +557,7 @@ export class Repository implements Disposable {
options: {
filter?: { branches?: (b: GitBranch) => boolean; tags?: (t: GitTag) => boolean };
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);
@ -646,10 +648,7 @@ export class Repository implements Disposable {
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);
}
@ -760,7 +759,7 @@ export class Repository implements Disposable {
branch: {
name: branch.name,
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;
suffix?: 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(
upstream: string | undefined,
upstream: { name: string; missing: boolean } | undefined,
state: { ahead: number; behind: number },
options: {
count?: boolean;
@ -270,25 +274,33 @@ export class GitStatus {
if (expand) {
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}${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 {
static is(tag: any): tag is GitTag {
return tag instanceof GitTag;
@ -22,7 +27,7 @@ export class GitTag implements GitTagReference {
return tag?.refType === 'tag';
}
static sort(tags: GitTag[], options?: { orderBy?: TagSortingspan> }) {
static sort(tags: GitTag[], options?: TagSortOptions) {
options = { orderBy: configuration.get('sortTagsBy'), ...options };
switch (options.orderBy) {

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

@ -2,7 +2,7 @@
import { GitBranch } from '../models/branch';
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
const lb = '%3c'; // `%${'<'.charCodeAt(0).toString(16)}`;
@ -29,6 +29,7 @@ export class GitBranchParser {
let upstream;
let ahead;
let behind;
let missing;
let ref;
let date;
@ -39,7 +40,7 @@ export class GitBranchParser {
match = branchWithTrackingRegex.exec(data);
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/')) {
// 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
ref == null || ref.length === 0 ? undefined : ` ${ref}`.substr(1),
// 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(behind) || 0,
),

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

@ -63,37 +63,41 @@ export namespace BranchQuickPickItem {
description = 'current branch';
}
if (options.status && !branch.remote && branch.upstream !== undefined) {
if (options.status && !branch.remote && branch.upstream != null) {
let arrows = GlyphChars.Dash;
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}${
GlyphChars.Space
} ${branch.upstream}`;
} ${branch.upstream.name}`;
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 { GlyphChars } from '../constants';
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 { BranchQuickPickItem, getQuickPickIgnoreFocusOut, RefQuickPickItem, TagQuickPickItem } from '../quickpicks';
import { getBranchesAndOrTags, getValidateGitReferenceFn } from '../commands/quickCommand';
import { BranchSorting, TagSorting } from '../configuration';
export type ReferencesQuickPickItem = BranchQuickPickItem | TagQuickPickItem | RefQuickPickItem;
@ -27,7 +26,7 @@ export interface ReferencesQuickPickOptions {
include?: ReferencesQuickPickIncludes;
keys?: Keys[];
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 {

+ 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.view.config.pullRequests.enabled &&
this.view.config.pullRequests.showForBranches &&
(this.branch.upstream || this.branch.remote)
(this.branch.upstream != null || this.branch.remote)
? this.branch.getAssociatedPullRequest(
this.root ? { include: [PullRequestState.Open, PullRequestState.Merged] } : undefined,
)
@ -213,10 +213,10 @@ export class BranchNode
ref: this.branch.ref,
repoPath: this.branch.repoPath,
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) {
children.push(
new BranchTrackingStatusNode(this.view, this, this.branch, status, 'same', this.root),
@ -293,7 +293,7 @@ export class BranchNode
if (this.branch.starred) {
contextValue += '+starred';
}
if (this.branch.upstream) {
if (this.branch.upstream != null && !this.branch.upstream.missing) {
contextValue += '+tracking';
}
if (this.options.showAsCommits) {
@ -308,28 +308,32 @@ export class BranchNode
let arrows = GlyphChars.Dash;
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
@ -339,19 +343,21 @@ export class BranchNode
arrows,
2,
2,
)}${this.branch.upstream}`
)}${this.branch.upstream.name}`
: `${this.branch.getTrackingStatus({ suffix: `${GlyphChars.Space} ` })}${arrows}${
GlyphChars.Space
} ${this.branch.upstream}`;
} ${this.branch.upstream.name}`;
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,
icons: true,
separator: ', ',
suffix: ` $(git-branch) ${this.branch.upstream}${
suffix: ` $(git-branch) ${this.branch.upstream.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,
undefined,
status.sha,
status.upstream,
status.upstream ? { name: status.upstream, missing: false } : undefined,
status.state.ahead,
status.state.behind,
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) ${
branch.name
}${
branch.upstream
branch.upstream != null
? ` 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,
icons: true,
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'}`
}${

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

@ -311,7 +311,7 @@ export class ViewCommands {
: undefined,
branch: {
name: node.branch.name,
upstream: node.branch.upstream,
upstream: node.branch.upstream?.name,
isRemote: node.branch.remote,
},
});
@ -516,7 +516,7 @@ export class ViewCommands {
private rebaseToRemote(node: BranchNode | BranchTrackingStatusNode) {
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();
return GitActions.rebase(
@ -712,9 +712,9 @@ export class ViewCommands {
@debug()
private compareWithUpstream(node: BranchNode) {
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()

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

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

Loading…
Cancel
Save