Skip to content

Commit baa3e22

Browse files
authored
Merge branch 'main' into tyriar/183236
2 parents d1be423 + 6c63b9e commit baa3e22

File tree

101 files changed

+1559
-388
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

101 files changed

+1559
-388
lines changed

extensions/git-base/src/api/api1.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@
55

66
import { Disposable, commands } from 'vscode';
77
import { Model } from '../model';
8-
import { pickRemoteSource } from '../remoteSource';
8+
import { getRemoteSourceActions, pickRemoteSource } from '../remoteSource';
99
import { GitBaseExtensionImpl } from './extension';
10-
import { API, PickRemoteSourceOptions, PickRemoteSourceResult, RemoteSourceProvider } from './git-base';
10+
import { API, PickRemoteSourceOptions, PickRemoteSourceResult, RemoteSourceAction, RemoteSourceProvider } from './git-base';
1111

1212
export class ApiImpl implements API {
1313

@@ -17,6 +17,10 @@ export class ApiImpl implements API {
1717
return pickRemoteSource(this._model, options as any);
1818
}
1919

20+
getRemoteSourceActions(url: string): Promise<RemoteSourceAction[]> {
21+
return getRemoteSourceActions(this._model, url);
22+
}
23+
2024
registerRemoteSourceProvider(provider: RemoteSourceProvider): Disposable {
2125
return this._model.registerRemoteSourceProvider(provider);
2226
}

extensions/git-base/src/api/git-base.d.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,15 @@ export interface PickRemoteSourceResult {
4444
readonly branch?: string;
4545
}
4646

47+
export interface RemoteSourceAction {
48+
readonly label: string;
49+
/**
50+
* Codicon name
51+
*/
52+
readonly icon: string;
53+
run(branch: string): void;
54+
}
55+
4756
export interface RemoteSource {
4857
readonly name: string;
4958
readonly description?: string;
@@ -70,6 +79,7 @@ export interface RemoteSourceProvider {
7079
readonly supportsQuery?: boolean;
7180

7281
getBranches?(url: string): ProviderResult<string[]>;
82+
getRemoteSourceActions?(url: string): ProviderResult<RemoteSourceAction[]>;
7383
getRecentRemoteSources?(query?: string): ProviderResult<RecentRemoteSource[]>;
7484
getRemoteSources(query?: string): ProviderResult<RemoteSource[]>;
7585
}

extensions/git-base/src/remoteSource.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
*--------------------------------------------------------------------------------------------*/
55

66
import { QuickPickItem, window, QuickPick, QuickPickItemKind, l10n } from 'vscode';
7-
import { RemoteSourceProvider, RemoteSource, PickRemoteSourceOptions, PickRemoteSourceResult } from './api/git-base';
7+
import { RemoteSourceProvider, RemoteSource, PickRemoteSourceOptions, PickRemoteSourceResult, RemoteSourceAction } from './api/git-base';
88
import { Model } from './model';
99
import { throttle, debounce } from './decorators';
1010

@@ -81,6 +81,20 @@ class RemoteSourceProviderQuickPick {
8181
}
8282
}
8383

84+
export async function getRemoteSourceActions(model: Model, url: string): Promise<RemoteSourceAction[]> {
85+
const providers = model.getRemoteProviders();
86+
87+
const remoteSourceActions = [];
88+
for (const provider of providers) {
89+
const providerActions = await provider.getRemoteSourceActions?.(url);
90+
if (providerActions?.length) {
91+
remoteSourceActions.push(...providerActions);
92+
}
93+
}
94+
95+
return remoteSourceActions;
96+
}
97+
8498
export async function pickRemoteSource(model: Model, options: PickRemoteSourceOptions & { branch?: false | undefined }): Promise<string | undefined>;
8599
export async function pickRemoteSource(model: Model, options: PickRemoteSourceOptions & { branch: true }): Promise<PickRemoteSourceResult | undefined>;
86100
export async function pickRemoteSource(model: Model, options: PickRemoteSourceOptions = {}): Promise<string | PickRemoteSourceResult | undefined> {

extensions/git/src/api/api1.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
import { Model } from '../model';
77
import { Repository as BaseRepository, Resource } from '../repository';
8-
import { InputBox, Git, API, Repository, Remote, RepositoryState, Branch, ForcePushMode, Ref, Submodule, Commit, Change, RepositoryUIState, Status, LogOptions, APIState, CommitOptions, RefType, CredentialsProvider, BranchQuery, PushErrorHandler, PublishEvent, FetchOptions, RemoteSourceProvider, RemoteSourcePublisher, PostCommitCommandsProvider, RefQuery, BranchProtectionProvider } from './git';
8+
import { InputBox, Git, API, Repository, Remote, RepositoryState, Branch, ForcePushMode, Ref, Submodule, Commit, Change, RepositoryUIState, Status, LogOptions, APIState, CommitOptions, RefType, CredentialsProvider, BranchQuery, PushErrorHandler, PublishEvent, FetchOptions, RemoteSourceProvider, RemoteSourcePublisher, PostCommitCommandsProvider, RefQuery, BranchProtectionProvider, InitOptions } from './git';
99
import { Event, SourceControlInputBox, Uri, SourceControl, Disposable, commands, CancellationToken } from 'vscode';
1010
import { combinedDisposable, mapEvent } from '../util';
1111
import { toGitUri } from '../uri';
@@ -294,9 +294,9 @@ export class ApiImpl implements API {
294294
return result ? new ApiRepository(result) : null;
295295
}
296296

297-
async init(root: Uri): Promise<Repository | null> {
297+
async init(root: Uri, options?: InitOptions): Promise<Repository | null> {
298298
const path = root.fsPath;
299-
await this._model.git.init(path);
299+
await this._model.git.init(path, options);
300300
await this._model.openRepository(path);
301301
return this.getRepository(root) || null;
302302
}
@@ -362,6 +362,7 @@ function getStatus(status: Status): string {
362362
case Status.UNTRACKED: return 'UNTRACKED';
363363
case Status.IGNORED: return 'IGNORED';
364364
case Status.INTENT_TO_ADD: return 'INTENT_TO_ADD';
365+
case Status.INTENT_TO_RENAME: return 'INTENT_TO_RENAME';
365366
case Status.ADDED_BY_US: return 'ADDED_BY_US';
366367
case Status.ADDED_BY_THEM: return 'ADDED_BY_THEM';
367368
case Status.DELETED_BY_US: return 'DELETED_BY_US';

extensions/git/src/api/git-base.d.ts

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ export { ProviderResult } from 'vscode';
88

99
export interface API {
1010
pickRemoteSource(options: PickRemoteSourceOptions): Promise<string | PickRemoteSourceResult | undefined>;
11+
getRemoteSourceActions(url: string): Promise<RemoteSourceAction[]>;
1112
registerRemoteSourceProvider(provider: RemoteSourceProvider): Disposable;
1213
}
1314

@@ -31,30 +32,55 @@ export interface GitBaseExtension {
3132

3233
export interface PickRemoteSourceOptions {
3334
readonly providerLabel?: (provider: RemoteSourceProvider) => string;
34-
readonly urlLabel?: string;
35+
readonly urlLabel?: string | ((url: string) => string);
3536
readonly providerName?: string;
37+
readonly title?: string;
38+
readonly placeholder?: string;
3639
readonly branch?: boolean; // then result is PickRemoteSourceResult
40+
readonly showRecentSources?: boolean;
3741
}
3842

3943
export interface PickRemoteSourceResult {
4044
readonly url: string;
4145
readonly branch?: string;
4246
}
4347

48+
export interface RemoteSourceAction {
49+
readonly label: string;
50+
/**
51+
* Codicon name
52+
*/
53+
readonly icon: string;
54+
run(branch: string): void;
55+
}
56+
4457
export interface RemoteSource {
4558
readonly name: string;
4659
readonly description?: string;
60+
readonly detail?: string;
61+
/**
62+
* Codicon name
63+
*/
64+
readonly icon?: string;
4765
readonly url: string | string[];
4866
}
4967

68+
export interface RecentRemoteSource extends RemoteSource {
69+
readonly timestamp: number;
70+
}
71+
5072
export interface RemoteSourceProvider {
5173
readonly name: string;
5274
/**
5375
* Codicon name
5476
*/
5577
readonly icon?: string;
78+
readonly label?: string;
79+
readonly placeholder?: string;
5680
readonly supportsQuery?: boolean;
5781

5882
getBranches?(url: string): ProviderResult<string[]>;
83+
getRemoteSourceActions?(url: string): ProviderResult<RemoteSourceAction[]>;
84+
getRecentRemoteSources?(query?: string): ProviderResult<RecentRemoteSource[]>;
5985
getRemoteSources(query?: string): ProviderResult<RemoteSource[]>;
6086
}

extensions/git/src/api/git.d.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ export const enum Status {
7878
UNTRACKED,
7979
IGNORED,
8080
INTENT_TO_ADD,
81+
INTENT_TO_RENAME,
8182

8283
ADDED_BY_US,
8384
ADDED_BY_THEM,
@@ -156,6 +157,10 @@ export interface FetchOptions {
156157
depth?: number;
157158
}
158159

160+
export interface InitOptions {
161+
defaultBranch?: string;
162+
}
163+
159164
export interface RefQuery {
160165
readonly contains?: string;
161166
readonly count?: number;
@@ -307,7 +312,7 @@ export interface API {
307312

308313
toGitUri(uri: Uri, ref: string): Uri;
309314
getRepository(uri: Uri): Repository | null;
310-
init(root: Uri): Promise<Repository | null>;
315+
init(root: Uri, options?: InitOptions): Promise<Repository | null>;
311316
openRepository(root: Uri): Promise<Repository | null>
312317

313318
registerRemoteSourcePublisher(publisher: RemoteSourcePublisher): Disposable;

extensions/git/src/commands.ts

Lines changed: 66 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
import * as os from 'os';
77
import * as path from 'path';
8-
import { Command, commands, Disposable, LineChange, MessageOptions, Position, ProgressLocation, QuickPickItem, Range, SourceControlResourceState, TextDocumentShowOptions, TextEditor, Uri, ViewColumn, window, workspace, WorkspaceEdit, WorkspaceFolder, TimelineItem, env, Selection, TextDocumentContentProvider, InputBoxValidationSeverity, TabInputText, TabInputTextMerge, QuickPickItemKind, TextDocument, LogOutputChannel, l10n, Memento, UIKind } from 'vscode';
8+
import { Command, commands, Disposable, LineChange, MessageOptions, Position, ProgressLocation, QuickPickItem, Range, SourceControlResourceState, TextDocumentShowOptions, TextEditor, Uri, ViewColumn, window, workspace, WorkspaceEdit, WorkspaceFolder, TimelineItem, env, Selection, TextDocumentContentProvider, InputBoxValidationSeverity, TabInputText, TabInputTextMerge, QuickPickItemKind, TextDocument, LogOutputChannel, l10n, Memento, UIKind, QuickInputButton, ThemeIcon } from 'vscode';
99
import TelemetryReporter from '@vscode/extension-telemetry';
1010
import { uniqueNamesGenerator, adjectives, animals, colors, NumberDictionary } from '@joaomoreno/unique-names-generator';
1111
import { Branch, ForcePushMode, GitErrorCodes, Ref, RefType, Status, CommitOptions, RemoteSourcePublisher, Remote } from './api/git';
@@ -17,16 +17,20 @@ import { fromGitUri, toGitUri, isGitUri, toMergeUris } from './uri';
1717
import { grep, isDescendant, pathEquals, relativePath } from './util';
1818
import { GitTimelineItem } from './timelineProvider';
1919
import { ApiRepository } from './api/api1';
20-
import { pickRemoteSource } from './remoteSource';
20+
import { getRemoteSourceActions, pickRemoteSource } from './remoteSource';
21+
import { RemoteSourceAction } from './api/git-base';
2122

2223
class CheckoutItem implements QuickPickItem {
2324

2425
protected get shortCommit(): string { return (this.ref.commit || '').substr(0, 8); }
2526
get label(): string { return `${this.repository.isBranchProtected(this.ref) ? '$(lock)' : '$(git-branch)'} ${this.ref.name || this.shortCommit}`; }
2627
get description(): string { return this.shortCommit; }
2728
get refName(): string | undefined { return this.ref.name; }
29+
get refRemote(): string | undefined { return this.ref.remote; }
30+
get buttons(): QuickInputButton[] | undefined { return this._buttons; }
31+
set buttons(newButtons: QuickInputButton[] | undefined) { this._buttons = newButtons; }
2832

29-
constructor(protected repository: Repository, protected ref: Ref) { }
33+
constructor(protected repository: Repository, protected ref: Ref, protected _buttons?: QuickInputButton[]) { }
3034

3135
async run(opts?: { detached?: boolean }): Promise<void> {
3236
if (!this.ref.name) {
@@ -278,7 +282,54 @@ async function createCheckoutItems(repository: Repository, detached = false): Pr
278282
}
279283
}
280284

281-
return processors.reduce<CheckoutItem[]>((r, p) => r.concat(...p.items), []);
285+
const buttons = await getRemoteRefItemButtons(repository);
286+
let fallbackRemoteButtons: RemoteSourceActionButton[] | undefined = [];
287+
const remote = repository.remotes.find(r => r.pushUrl === repository.HEAD?.remote || r.fetchUrl === repository.HEAD?.remote) ?? repository.remotes[0];
288+
const remoteUrl = remote.pushUrl ?? remote.fetchUrl;
289+
if (remoteUrl) {
290+
fallbackRemoteButtons = buttons.get(remoteUrl);
291+
}
292+
293+
return processors.reduce<CheckoutItem[]>((r, p) => r.concat(...p.items.map((item) => {
294+
if (item.refRemote) {
295+
const matchingRemote = repository.remotes.find((remote) => remote.name === item.refRemote);
296+
const remoteUrl = matchingRemote?.pushUrl ?? matchingRemote?.fetchUrl;
297+
if (remoteUrl) {
298+
item.buttons = buttons.get(item.refRemote);
299+
}
300+
}
301+
302+
item.buttons = fallbackRemoteButtons;
303+
return item;
304+
})), []);
305+
}
306+
307+
type RemoteSourceActionButton = {
308+
iconPath: ThemeIcon;
309+
tooltip: string;
310+
actual: RemoteSourceAction;
311+
};
312+
313+
async function getRemoteRefItemButtons(repository: Repository) {
314+
// Compute actions for all known remotes
315+
const remoteUrlsToActions = new Map<string, RemoteSourceActionButton[]>();
316+
317+
const getButtons = async (remoteUrl: string) => (await getRemoteSourceActions(remoteUrl)).map((action) => ({ iconPath: new ThemeIcon(action.icon), tooltip: action.label, actual: action }));
318+
319+
for (const remote of repository.remotes) {
320+
if (remote.fetchUrl) {
321+
const actions = remoteUrlsToActions.get(remote.fetchUrl) ?? [];
322+
actions.push(...await getButtons(remote.fetchUrl));
323+
remoteUrlsToActions.set(remote.fetchUrl, actions);
324+
}
325+
if (remote.pushUrl && remote.pushUrl !== remote.fetchUrl) {
326+
const actions = remoteUrlsToActions.get(remote.pushUrl) ?? [];
327+
actions.push(...await getButtons(remote.pushUrl));
328+
remoteUrlsToActions.set(remote.pushUrl, actions);
329+
}
330+
}
331+
332+
return remoteUrlsToActions;
282333
}
283334

284335
class CheckoutProcessor {
@@ -2084,7 +2135,17 @@ export class CommandCenter {
20842135
quickpick.items = picks;
20852136
quickpick.busy = false;
20862137

2087-
const choice = await new Promise<QuickPickItem | undefined>(c => quickpick.onDidAccept(() => c(quickpick.activeItems[0])));
2138+
const choice = await new Promise<QuickPickItem | undefined>(c => {
2139+
quickpick.onDidAccept(() => c(quickpick.activeItems[0]));
2140+
quickpick.onDidTriggerItemButton((e) => {
2141+
quickpick.hide();
2142+
const button = e.button as QuickInputButton & { actual: RemoteSourceAction };
2143+
const item = e.item as CheckoutItem;
2144+
if (button.actual && item.refName) {
2145+
button.actual.run(item.refRemote ? item.refName.substring(item.refRemote.length + 1) : item.refName);
2146+
}
2147+
});
2148+
});
20882149
quickpick.hide();
20892150

20902151
if (!choice) {

extensions/git/src/decorationProvider.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ class GitDecorationProvider implements FileDecorationProvider {
131131
bucket.set(r.rightUri.toString(), decoration);
132132
}
133133

134-
if (r.type === Status.INDEX_RENAMED) {
134+
if (r.type === Status.INDEX_RENAMED || r.type === Status.INTENT_TO_RENAME) {
135135
bucket.set(r.resourceUri.toString(), decoration);
136136
}
137137
}

extensions/git/src/git.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import * as filetype from 'file-type';
1515
import { assign, groupBy, IDisposable, toDisposable, dispose, mkdirp, readBytes, detectUnicodeEncoding, Encoding, onceEvent, splitInChunks, Limiter, Versions, isWindows, pathEquals } from './util';
1616
import { CancellationError, CancellationToken, ConfigurationChangeEvent, LogOutputChannel, Progress, Uri, workspace } from 'vscode';
1717
import { detectEncoding } from './encoding';
18-
import { Ref, RefType, Branch, Remote, ForcePushMode, GitErrorCodes, LogOptions, Change, Status, CommitOptions, RefQuery } from './api/git';
18+
import { Ref, RefType, Branch, Remote, ForcePushMode, GitErrorCodes, LogOptions, Change, Status, CommitOptions, RefQuery, InitOptions } from './api/git';
1919
import * as byline from 'byline';
2020
import { StringDecoder } from 'string_decoder';
2121

@@ -401,7 +401,7 @@ export class Git {
401401
return new Repository(this, repository, dotGit, logger);
402402
}
403403

404-
async init(repository: string, options: { defaultBranch?: string } = {}): Promise<void> {
404+
async init(repository: string, options: InitOptions = {}): Promise<void> {
405405
const args = ['init'];
406406

407407
if (options.defaultBranch && options.defaultBranch !== '') {
@@ -793,7 +793,7 @@ export class GitStatusParser {
793793
// space
794794
i++;
795795

796-
if (entry.x === 'R' || entry.x === 'C') {
796+
if (entry.x === 'R' || entry.y === 'R' || entry.x === 'C') {
797797
lastIndex = raw.indexOf('\0', i);
798798

799799
if (lastIndex === -1) {

extensions/git/src/remoteSource.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,7 @@ export async function pickRemoteSource(options: PickRemoteSourceOptions & { bran
1111
export async function pickRemoteSource(options: PickRemoteSourceOptions = {}): Promise<string | PickRemoteSourceResult | undefined> {
1212
return GitBaseApi.getAPI().pickRemoteSource(options);
1313
}
14+
15+
export async function getRemoteSourceActions(url: string) {
16+
return GitBaseApi.getAPI().getRemoteSourceActions(url);
17+
}

0 commit comments

Comments
 (0)