Update to VS Code 1.52.1
This commit is contained in:
@ -181,6 +181,12 @@
|
||||
"category": "Git",
|
||||
"icon": "$(discard)"
|
||||
},
|
||||
{
|
||||
"command": "git.rename",
|
||||
"title": "%command.rename%",
|
||||
"category": "Git",
|
||||
"icon": "$(discard)"
|
||||
},
|
||||
{
|
||||
"command": "git.commit",
|
||||
"title": "%command.commit%",
|
||||
@ -278,6 +284,11 @@
|
||||
"title": "%command.checkout%",
|
||||
"category": "Git"
|
||||
},
|
||||
{
|
||||
"command": "git.checkoutDetached",
|
||||
"title": "%command.checkoutDetached%",
|
||||
"category": "Git"
|
||||
},
|
||||
{
|
||||
"command": "git.branch",
|
||||
"title": "%command.branch%",
|
||||
@ -368,6 +379,11 @@
|
||||
"title": "%command.pushToForce%",
|
||||
"category": "Git"
|
||||
},
|
||||
{
|
||||
"command": "git.pushTags",
|
||||
"title": "%command.pushTags%",
|
||||
"category": "Git"
|
||||
},
|
||||
{
|
||||
"command": "git.pushWithTags",
|
||||
"title": "%command.pushFollowTags%",
|
||||
@ -378,6 +394,11 @@
|
||||
"title": "%command.pushFollowTagsForce%",
|
||||
"category": "Git"
|
||||
},
|
||||
{
|
||||
"command": "git.cherryPick",
|
||||
"title": "%command.cherryPick%",
|
||||
"category": "Git"
|
||||
},
|
||||
{
|
||||
"command": "git.addRemote",
|
||||
"title": "%command.addRemote%",
|
||||
@ -605,6 +626,10 @@
|
||||
"command": "git.cleanAllUntracked",
|
||||
"when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0"
|
||||
},
|
||||
{
|
||||
"command": "git.rename",
|
||||
"when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0 && resourceScheme == file"
|
||||
},
|
||||
{
|
||||
"command": "git.commit",
|
||||
"when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0"
|
||||
@ -705,6 +730,10 @@
|
||||
"command": "git.renameBranch",
|
||||
"when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0"
|
||||
},
|
||||
{
|
||||
"command": "git.cherryPick",
|
||||
"when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0"
|
||||
},
|
||||
{
|
||||
"command": "git.pull",
|
||||
"when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0"
|
||||
@ -769,6 +798,10 @@
|
||||
"command": "git.pushWithTagsForce",
|
||||
"when": "config.git.enabled && !git.missing && config.git.allowForcePush && gitOpenRepositoryCount != 0"
|
||||
},
|
||||
{
|
||||
"command": "git.pushTags",
|
||||
"when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0"
|
||||
},
|
||||
{
|
||||
"command": "git.addRemote",
|
||||
"when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0"
|
||||
@ -1279,17 +1312,17 @@
|
||||
{
|
||||
"command": "git.stageSelectedRanges",
|
||||
"group": "2_git@1",
|
||||
"when": "isInDiffRightEditor && config.git.enabled && !git.missing && gitOpenRepositoryCount != 0 && isInDiffEditor && resourceScheme =~ /^git$|^file$/"
|
||||
"when": "isInDiffRightEditor && !isInEmbeddedDiffEditor && config.git.enabled && !git.missing && gitOpenRepositoryCount != 0 && isInDiffEditor && resourceScheme =~ /^git$|^file$/"
|
||||
},
|
||||
{
|
||||
"command": "git.unstageSelectedRanges",
|
||||
"group": "2_git@2",
|
||||
"when": "isInDiffRightEditor && config.git.enabled && !git.missing && gitOpenRepositoryCount != 0 && isInDiffEditor && resourceScheme =~ /^git$|^file$/"
|
||||
"when": "isInDiffRightEditor && !isInEmbeddedDiffEditor && config.git.enabled && !git.missing && gitOpenRepositoryCount != 0 && isInDiffEditor && resourceScheme =~ /^git$|^file$/"
|
||||
},
|
||||
{
|
||||
"command": "git.revertSelectedRanges",
|
||||
"group": "2_git@3",
|
||||
"when": "isInDiffRightEditor && config.git.enabled && !git.missing && gitOpenRepositoryCount != 0 && isInDiffEditor && resourceScheme =~ /^git$|^file$/"
|
||||
"when": "isInDiffRightEditor && !isInEmbeddedDiffEditor && config.git.enabled && !git.missing && gitOpenRepositoryCount != 0 && isInDiffEditor && resourceScheme =~ /^git$|^file$/"
|
||||
}
|
||||
],
|
||||
"scm/change/title": [
|
||||
@ -1657,21 +1690,27 @@
|
||||
"scope": "resource"
|
||||
},
|
||||
"git.checkoutType": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"all",
|
||||
"local",
|
||||
"tags",
|
||||
"remote"
|
||||
],
|
||||
"enumDescriptions": [
|
||||
"%config.checkoutType.all%",
|
||||
"%config.checkoutType.local%",
|
||||
"%config.checkoutType.tags%",
|
||||
"%config.checkoutType.remote%"
|
||||
],
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"local",
|
||||
"tags",
|
||||
"remote"
|
||||
],
|
||||
"enumDescriptions": [
|
||||
"%config.checkoutType.local%",
|
||||
"%config.checkoutType.tags%",
|
||||
"%config.checkoutType.remote%"
|
||||
]
|
||||
},
|
||||
"uniqueItems": true,
|
||||
"markdownDescription": "%config.checkoutType%",
|
||||
"default": "all"
|
||||
"default": [
|
||||
"local",
|
||||
"remote",
|
||||
"tags"
|
||||
]
|
||||
},
|
||||
"git.ignoreLegacyWarning": {
|
||||
"type": "boolean",
|
||||
@ -1751,6 +1790,28 @@
|
||||
"description": "%config.enableStatusBarSync%",
|
||||
"scope": "resource"
|
||||
},
|
||||
"git.followTagsWhenSync": {
|
||||
"type": "boolean",
|
||||
"scope": "resource",
|
||||
"default": false,
|
||||
"description": "%config.followTagsWhenSync%"
|
||||
},
|
||||
"git.promptToSaveFilesBeforeStash": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"always",
|
||||
"staged",
|
||||
"never"
|
||||
],
|
||||
"enumDescriptions": [
|
||||
"%config.promptToSaveFilesBeforeStash.always%",
|
||||
"%config.promptToSaveFilesBeforeStash.staged%",
|
||||
"%config.promptToSaveFilesBeforeStash.never%"
|
||||
],
|
||||
"scope": "resource",
|
||||
"default": "always",
|
||||
"description": "%config.promptToSaveFilesBeforeStash%"
|
||||
},
|
||||
"git.promptToSaveFilesBeforeCommit": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
@ -1783,6 +1844,23 @@
|
||||
"scope": "resource",
|
||||
"default": "none"
|
||||
},
|
||||
"git.openAfterClone": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"always",
|
||||
"alwaysNewWindow",
|
||||
"whenNoFolderOpen",
|
||||
"prompt"
|
||||
],
|
||||
"enumDescriptions": [
|
||||
"%config.openAfterClone.always%",
|
||||
"%config.openAfterClone.alwaysNewWindow%",
|
||||
"%config.openAfterClone.whenNoFolderOpen%",
|
||||
"%config.openAfterClone.prompt%"
|
||||
],
|
||||
"default": "prompt",
|
||||
"description": "%config.openAfterClone%"
|
||||
},
|
||||
"git.showInlineOpenFileAction": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
@ -1840,6 +1918,12 @@
|
||||
"default": false,
|
||||
"description": "%config.alwaysSignOff%"
|
||||
},
|
||||
"git.ignoreSubmodules": {
|
||||
"type": "boolean",
|
||||
"scope": "resource",
|
||||
"default": false,
|
||||
"description": "%config.ignoreSubmodules%"
|
||||
},
|
||||
"git.ignoredRepositories": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
@ -1876,6 +1960,12 @@
|
||||
"default": false,
|
||||
"description": "%config.fetchOnPull%"
|
||||
},
|
||||
"git.pruneOnFetch": {
|
||||
"type": "boolean",
|
||||
"scope": "resource",
|
||||
"default": false,
|
||||
"description": "%config.pruneOnFetch%"
|
||||
},
|
||||
"git.pullTags": {
|
||||
"type": "boolean",
|
||||
"scope": "resource",
|
||||
@ -1962,6 +2052,12 @@
|
||||
"default": true,
|
||||
"description": "%config.terminalAuthentication%"
|
||||
},
|
||||
"git.useCommitInputAsStashMessage": {
|
||||
"type": "boolean",
|
||||
"scope": "resource",
|
||||
"default": false,
|
||||
"description": "%config.useCommitInputAsStashMessage%"
|
||||
},
|
||||
"git.githubAuthentication": {
|
||||
"deprecationMessage": "This setting is now deprecated, please use `github.gitAuthentication` instead."
|
||||
},
|
||||
@ -2166,31 +2262,36 @@
|
||||
{
|
||||
"view": "scm",
|
||||
"contents": "%view.workbench.scm.empty%",
|
||||
"when": "config.git.enabled && git.state == initialized && workbenchState == empty",
|
||||
"when": "config.git.enabled && workbenchState == empty",
|
||||
"enablement": "git.state == initialized",
|
||||
"group": "2_open@1"
|
||||
},
|
||||
{
|
||||
"view": "scm",
|
||||
"contents": "%view.workbench.scm.folder%",
|
||||
"when": "config.git.enabled && git.state == initialized && workbenchState == folder",
|
||||
"when": "config.git.enabled && workbenchState == folder",
|
||||
"enablement": "git.state == initialized",
|
||||
"group": "5_scm@1"
|
||||
},
|
||||
{
|
||||
"view": "scm",
|
||||
"contents": "%view.workbench.scm.workspace%",
|
||||
"when": "config.git.enabled && git.state == initialized && workbenchState == workspace && workspaceFolderCount != 0",
|
||||
"when": "config.git.enabled && workbenchState == workspace && workspaceFolderCount != 0",
|
||||
"enablement": "git.state == initialized",
|
||||
"group": "5_scm@1"
|
||||
},
|
||||
{
|
||||
"view": "scm",
|
||||
"contents": "%view.workbench.scm.emptyWorkspace%",
|
||||
"when": "config.git.enabled && git.state == initialized && workbenchState == workspace && workspaceFolderCount == 0",
|
||||
"when": "config.git.enabled && workbenchState == workspace && workspaceFolderCount == 0",
|
||||
"enablement": "git.state == initialized",
|
||||
"group": "2_open@1"
|
||||
},
|
||||
{
|
||||
"view": "explorer",
|
||||
"contents": "%view.workbench.cloneRepository%",
|
||||
"when": "config.git.enabled && git.state == initialized",
|
||||
"when": "config.git.enabled",
|
||||
"enablement": "git.state == initialized",
|
||||
"group": "5_scm@1"
|
||||
}
|
||||
]
|
||||
|
@ -23,6 +23,7 @@
|
||||
"command.unstage": "Unstage Changes",
|
||||
"command.unstageAll": "Unstage All Changes",
|
||||
"command.unstageSelectedRanges": "Unstage Selected Ranges",
|
||||
"command.rename": "Rename",
|
||||
"command.clean": "Discard Changes",
|
||||
"command.cleanAll": "Discard All Changes",
|
||||
"command.cleanAllTracked": "Discard All Tracked Changes",
|
||||
@ -46,10 +47,12 @@
|
||||
"command.restoreCommitTemplate": "Restore Commit Template",
|
||||
"command.undoCommit": "Undo Last Commit",
|
||||
"command.checkout": "Checkout to...",
|
||||
"command.checkoutDetached": "Checkout to (Detached)...",
|
||||
"command.branch": "Create Branch...",
|
||||
"command.branchFrom": "Create Branch From...",
|
||||
"command.deleteBranch": "Delete Branch...",
|
||||
"command.renameBranch": "Rename Branch...",
|
||||
"command.cherryPick": "Cherry Pick...",
|
||||
"command.merge": "Merge Branch...",
|
||||
"command.rebase": "Rebase Branch...",
|
||||
"command.createTag": "Create Tag",
|
||||
@ -66,6 +69,7 @@
|
||||
"command.pushToForce": "Push to... (Force)",
|
||||
"command.pushFollowTags": "Push (Follow Tags)",
|
||||
"command.pushFollowTagsForce": "Push (Follow Tags, Force)",
|
||||
"command.pushTags": "Push Tags",
|
||||
"command.addRemote": "Add Remote...",
|
||||
"command.removeRemote": "Remove Remote",
|
||||
"command.sync": "Sync",
|
||||
@ -100,11 +104,10 @@
|
||||
"config.countBadge.all": "Count all changes.",
|
||||
"config.countBadge.tracked": "Count only tracked changes.",
|
||||
"config.countBadge.off": "Turn off counter.",
|
||||
"config.checkoutType": "Controls what type of branches are listed when running `Checkout to...`.",
|
||||
"config.checkoutType.all": "Show all references.",
|
||||
"config.checkoutType.local": "Show only local branches.",
|
||||
"config.checkoutType.tags": "Show only tags.",
|
||||
"config.checkoutType.remote": "Show only remote branches.",
|
||||
"config.checkoutType": "Controls what type of git refs are listed when running `Checkout to...`.",
|
||||
"config.checkoutType.local": "Local branches",
|
||||
"config.checkoutType.tags": "Tags",
|
||||
"config.checkoutType.remote": "Remote branches",
|
||||
"config.branchValidationRegex": "A regular expression to validate new branch names.",
|
||||
"config.branchWhitespaceChar": "The character to replace whitespace in new branch names.",
|
||||
"config.ignoreLegacyWarning": "Ignores the legacy Git warning.",
|
||||
@ -121,6 +124,11 @@
|
||||
"config.discardAllScope": "Controls what changes are discarded by the `Discard all changes` command. `all` discards all changes. `tracked` discards only tracked files. `prompt` shows a prompt dialog every time the action is run.",
|
||||
"config.decorations.enabled": "Controls whether Git contributes colors and badges to the explorer and the open editors view.",
|
||||
"config.enableStatusBarSync": "Controls whether the Git Sync command appears in the status bar.",
|
||||
"config.followTagsWhenSync": "Follow push all tags when running the sync command.",
|
||||
"config.promptToSaveFilesBeforeStash": "Controls whether Git should check for unsaved files before stashing changes.",
|
||||
"config.promptToSaveFilesBeforeStash.always": "Check for any unsaved files.",
|
||||
"config.promptToSaveFilesBeforeStash.staged": "Check only for unsaved staged files.",
|
||||
"config.promptToSaveFilesBeforeStash.never": "Disable this check.",
|
||||
"config.promptToSaveFilesBeforeCommit": "Controls whether Git should check for unsaved files before committing.",
|
||||
"config.promptToSaveFilesBeforeCommit.always": "Check for any unsaved files.",
|
||||
"config.promptToSaveFilesBeforeCommit.staged": "Check only for unsaved staged files.",
|
||||
@ -129,6 +137,11 @@
|
||||
"config.postCommitCommand.none": "Don't run any command after a commit.",
|
||||
"config.postCommitCommand.push": "Run 'Git Push' after a successful commit.",
|
||||
"config.postCommitCommand.sync": "Run 'Git Sync' after a successful commit.",
|
||||
"config.openAfterClone": "Controls whether to open a repository automatically after cloning.",
|
||||
"config.openAfterClone.always": "Always open in current window.",
|
||||
"config.openAfterClone.alwaysNewWindow": "Always open in a new window.",
|
||||
"config.openAfterClone.whenNoFolderOpen": "Only open in current window when no folder is opened.",
|
||||
"config.openAfterClone.prompt": "Always prompt for action.",
|
||||
"config.showInlineOpenFileAction": "Controls whether to show an inline Open File action in the Git changes view.",
|
||||
"config.showPushSuccessNotification": "Controls whether to show a notification when a push is successful.",
|
||||
"config.inputValidation": "Controls when to show commit message input validation.",
|
||||
@ -138,6 +151,7 @@
|
||||
"config.detectSubmodulesLimit": "Controls the limit of git submodules detected.",
|
||||
"config.alwaysShowStagedChangesResourceGroup": "Always show the Staged Changes resource group.",
|
||||
"config.alwaysSignOff": "Controls the signoff flag for all commits.",
|
||||
"config.ignoreSubmodules": "Ignore modifications to submodules in the file tree.",
|
||||
"config.ignoredRepositories": "List of git repositories to ignore.",
|
||||
"config.scanRepositories": "List of paths to search for git repositories in.",
|
||||
"config.showProgress": "Controls whether git actions should show progress.",
|
||||
@ -145,6 +159,7 @@
|
||||
"config.confirmEmptyCommits": "Always confirm the creation of empty commits for the 'Git: Commit Empty' command.",
|
||||
"config.fetchOnPull": "When enabled, fetch all branches when pulling. Otherwise, fetch just the current one.",
|
||||
"config.pullTags": "Fetch all tags when pulling.",
|
||||
"config.pruneOnFetch": "Prune when fetching.",
|
||||
"config.autoStash": "Stash any changes before pulling and restore them after successful pull.",
|
||||
"config.allowForcePush": "Controls whether force push (with or without lease) is enabled.",
|
||||
"config.useForcePushWithLease": "Controls whether force pushing uses the safer force-with-lease variant.",
|
||||
@ -164,6 +179,8 @@
|
||||
"config.timeline.date": "Controls which date to use for items in the Timeline view",
|
||||
"config.timeline.date.committed": "Use the committed date",
|
||||
"config.timeline.date.authored": "Use the authored date",
|
||||
"config.useCommitInputAsStashMessage": "Controls whether to use the message from the commit input box as the default stash message.",
|
||||
"submenu.explorer": "Git",
|
||||
"submenu.commit": "Commit",
|
||||
"submenu.commit.amend": "Amend",
|
||||
"submenu.commit.signoff": "Sign Off",
|
||||
@ -188,5 +205,5 @@
|
||||
"view.workbench.scm.folder": "The folder currently open doesn't have a git repository. You can initialize a repository which will enable source control features powered by git.\n[Initialize Repository](command:git.init?%5Btrue%5D)\nTo learn more about how to use git and source control in VS Code [read our docs](https://aka.ms/vscode-scm).",
|
||||
"view.workbench.scm.workspace": "The workspace currently open doesn't have any folders containing git repositories. You can initialize a repository on a folder which will enable source control features powered by git.\n[Initialize Repository](command:git.init)\nTo learn more about how to use git and source control in VS Code [read our docs](https://aka.ms/vscode-scm).",
|
||||
"view.workbench.scm.emptyWorkspace": "The workspace currently open doesn't have any folders containing git repositories.\n[Add Folder to Workspace](command:workbench.action.addRootFolder)\nTo learn more about how to use git and source control in VS Code [read our docs](https://aka.ms/vscode-scm).",
|
||||
"view.workbench.cloneRepository": "You can also clone a repository from a URL. To learn more about how to use git and source control in VS Code [read our docs](https://aka.ms/vscode-scm).\n[Clone Repository](command:git.clone)"
|
||||
"view.workbench.cloneRepository": "You can also clone a repository from a URL. To learn more about how to use git and source control in VS Code [read our docs](https://aka.ms/vscode-scm).\n[Clone Repository](command:git.clone 'Clone a repository once the git extension has activated')"
|
||||
}
|
||||
|
@ -357,7 +357,7 @@ export function registerAPICommands(extension: GitExtensionImpl): Disposable {
|
||||
return;
|
||||
}
|
||||
|
||||
return pickRemoteSource(extension.model, opts);
|
||||
return pickRemoteSource(extension.model, opts as any);
|
||||
}));
|
||||
|
||||
return Disposable.from(...disposables);
|
||||
|
1
lib/vscode/extensions/git/src/api/git.d.ts
vendored
1
lib/vscode/extensions/git/src/api/git.d.ts
vendored
@ -212,6 +212,7 @@ export interface RemoteSourceProvider {
|
||||
readonly icon?: string; // codicon name
|
||||
readonly supportsQuery?: boolean;
|
||||
getRemoteSources(query?: string): ProviderResult<RemoteSource[]>;
|
||||
getBranches?(url: string): ProviderResult<string[]>;
|
||||
publishRepository?(repository: Repository): Promise<void>;
|
||||
}
|
||||
|
||||
|
@ -3,10 +3,9 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { lstat, Stats } from 'fs';
|
||||
import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
import { commands, Disposable, LineChange, MessageOptions, OutputChannel, Position, ProgressLocation, QuickPickItem, Range, SourceControlResourceState, TextDocumentShowOptions, TextEditor, Uri, ViewColumn, window, workspace, WorkspaceEdit, WorkspaceFolder, TimelineItem, env, Selection } from 'vscode';
|
||||
import { commands, Disposable, LineChange, MessageOptions, OutputChannel, Position, ProgressLocation, QuickPickItem, Range, SourceControlResourceState, TextDocumentShowOptions, TextEditor, Uri, ViewColumn, window, workspace, WorkspaceEdit, WorkspaceFolder, TimelineItem, env, Selection, TextDocumentContentProvider } from 'vscode';
|
||||
import TelemetryReporter from 'vscode-extension-telemetry';
|
||||
import * as nls from 'vscode-nls';
|
||||
import { Branch, GitErrorCodes, Ref, RefType, Status, CommitOptions, RemoteSourceProvider } from './api/git';
|
||||
@ -31,14 +30,14 @@ class CheckoutItem implements QuickPickItem {
|
||||
|
||||
constructor(protected ref: Ref) { }
|
||||
|
||||
async run(repository: Repository): Promise<void> {
|
||||
async run(repository: Repository, opts?: { detached?: boolean }): Promise<void> {
|
||||
const ref = this.ref.name;
|
||||
|
||||
if (!ref) {
|
||||
return;
|
||||
}
|
||||
|
||||
await repository.checkout(ref);
|
||||
await repository.checkout(ref, opts);
|
||||
}
|
||||
}
|
||||
|
||||
@ -55,7 +54,7 @@ class CheckoutRemoteHeadItem extends CheckoutItem {
|
||||
return localize('remote branch at', "Remote branch at {0}", this.shortCommit);
|
||||
}
|
||||
|
||||
async run(repository: Repository): Promise<void> {
|
||||
async run(repository: Repository, opts?: { detached?: boolean }): Promise<void> {
|
||||
if (!this.ref.name) {
|
||||
return;
|
||||
}
|
||||
@ -63,9 +62,9 @@ class CheckoutRemoteHeadItem extends CheckoutItem {
|
||||
const branches = await repository.findTrackingBranches(this.ref.name);
|
||||
|
||||
if (branches.length > 0) {
|
||||
await repository.checkout(branches[0].name!);
|
||||
await repository.checkout(branches[0].name!, opts);
|
||||
} else {
|
||||
await repository.checkoutTracking(this.ref.name);
|
||||
await repository.checkoutTracking(this.ref.name, opts);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -114,31 +113,21 @@ class RebaseItem implements QuickPickItem {
|
||||
}
|
||||
|
||||
class CreateBranchItem implements QuickPickItem {
|
||||
|
||||
constructor(private cc: CommandCenter) { }
|
||||
|
||||
get label(): string { return '$(plus) ' + localize('create branch', 'Create new branch...'); }
|
||||
get description(): string { return ''; }
|
||||
|
||||
get alwaysShow(): boolean { return true; }
|
||||
|
||||
async run(repository: Repository): Promise<void> {
|
||||
await this.cc.branch(repository);
|
||||
}
|
||||
}
|
||||
|
||||
class CreateBranchFromItem implements QuickPickItem {
|
||||
|
||||
constructor(private cc: CommandCenter) { }
|
||||
|
||||
get label(): string { return '$(plus) ' + localize('create branch from', 'Create new branch from...'); }
|
||||
get description(): string { return ''; }
|
||||
|
||||
get alwaysShow(): boolean { return true; }
|
||||
}
|
||||
|
||||
async run(repository: Repository): Promise<void> {
|
||||
await this.cc.branch(repository);
|
||||
}
|
||||
class CheckoutDetachedItem implements QuickPickItem {
|
||||
get label(): string { return '$(debug-disconnect) ' + localize('checkout detached', 'Checkout detached...'); }
|
||||
get description(): string { return ''; }
|
||||
get alwaysShow(): boolean { return true; }
|
||||
}
|
||||
|
||||
class HEADItem implements QuickPickItem {
|
||||
@ -217,18 +206,53 @@ async function categorizeResourceByResolution(resources: Resource[]): Promise<{
|
||||
|
||||
function createCheckoutItems(repository: Repository): CheckoutItem[] {
|
||||
const config = workspace.getConfiguration('git');
|
||||
const checkoutType = config.get<string>('checkoutType') || 'all';
|
||||
const includeTags = checkoutType === 'all' || checkoutType === 'tags';
|
||||
const includeRemotes = checkoutType === 'all' || checkoutType === 'remote';
|
||||
const checkoutTypeConfig = config.get<string | string[]>('checkoutType');
|
||||
let checkoutTypes: string[];
|
||||
|
||||
const heads = repository.refs.filter(ref => ref.type === RefType.Head)
|
||||
.map(ref => new CheckoutItem(ref));
|
||||
const tags = (includeTags ? repository.refs.filter(ref => ref.type === RefType.Tag) : [])
|
||||
.map(ref => new CheckoutTagItem(ref));
|
||||
const remoteHeads = (includeRemotes ? repository.refs.filter(ref => ref.type === RefType.RemoteHead) : [])
|
||||
.map(ref => new CheckoutRemoteHeadItem(ref));
|
||||
if (checkoutTypeConfig === 'all' || !checkoutTypeConfig || checkoutTypeConfig.length === 0) {
|
||||
checkoutTypes = ['local', 'remote', 'tags'];
|
||||
} else if (typeof checkoutTypeConfig === 'string') {
|
||||
checkoutTypes = [checkoutTypeConfig];
|
||||
} else {
|
||||
checkoutTypes = checkoutTypeConfig;
|
||||
}
|
||||
|
||||
return [...heads, ...tags, ...remoteHeads];
|
||||
const processors = checkoutTypes.map(getCheckoutProcessor)
|
||||
.filter(p => !!p) as CheckoutProcessor[];
|
||||
|
||||
for (const ref of repository.refs) {
|
||||
for (const processor of processors) {
|
||||
processor.onRef(ref);
|
||||
}
|
||||
}
|
||||
|
||||
return processors.reduce<CheckoutItem[]>((r, p) => r.concat(...p.items), []);
|
||||
}
|
||||
|
||||
class CheckoutProcessor {
|
||||
|
||||
private refs: Ref[] = [];
|
||||
get items(): CheckoutItem[] { return this.refs.map(r => new this.ctor(r)); }
|
||||
constructor(private type: RefType, private ctor: { new(ref: Ref): CheckoutItem }) { }
|
||||
|
||||
onRef(ref: Ref): void {
|
||||
if (ref.type === this.type) {
|
||||
this.refs.push(ref);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getCheckoutProcessor(type: string): CheckoutProcessor | undefined {
|
||||
switch (type) {
|
||||
case 'local':
|
||||
return new CheckoutProcessor(RefType.Head, CheckoutItem);
|
||||
case 'remote':
|
||||
return new CheckoutProcessor(RefType.RemoteHead, CheckoutRemoteHeadItem);
|
||||
case 'tags':
|
||||
return new CheckoutProcessor(RefType.Tag, CheckoutTagItem);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function sanitizeRemoteName(name: string) {
|
||||
@ -246,6 +270,7 @@ enum PushType {
|
||||
Push,
|
||||
PushTo,
|
||||
PushFollowTags,
|
||||
PushTags
|
||||
}
|
||||
|
||||
interface PushOptions {
|
||||
@ -254,9 +279,27 @@ interface PushOptions {
|
||||
silent?: boolean;
|
||||
}
|
||||
|
||||
class CommandErrorOutputTextDocumentContentProvider implements TextDocumentContentProvider {
|
||||
|
||||
private items = new Map<string, string>();
|
||||
|
||||
set(uri: Uri, contents: string): void {
|
||||
this.items.set(uri.path, contents);
|
||||
}
|
||||
|
||||
delete(uri: Uri): void {
|
||||
this.items.delete(uri.path);
|
||||
}
|
||||
|
||||
provideTextDocumentContent(uri: Uri): string | undefined {
|
||||
return this.items.get(uri.path);
|
||||
}
|
||||
}
|
||||
|
||||
export class CommandCenter {
|
||||
|
||||
private disposables: Disposable[];
|
||||
private commandErrors = new CommandErrorOutputTextDocumentContentProvider();
|
||||
|
||||
constructor(
|
||||
private git: Git,
|
||||
@ -273,6 +316,8 @@ export class CommandCenter {
|
||||
return commands.registerCommand(commandId, command);
|
||||
}
|
||||
});
|
||||
|
||||
this.disposables.push(workspace.registerTextDocumentContentProvider('git-output', this.commandErrors));
|
||||
}
|
||||
|
||||
@command('git.setLogLevel')
|
||||
@ -311,165 +356,14 @@ export class CommandCenter {
|
||||
}
|
||||
|
||||
@command('git.openResource')
|
||||
async openResource(resource: Resource, preserveFocus: boolean): Promise<void> {
|
||||
async openResource(resource: Resource): Promise<void> {
|
||||
const repository = this.model.getRepository(resource.resourceUri);
|
||||
|
||||
if (!repository) {
|
||||
return;
|
||||
}
|
||||
|
||||
const config = workspace.getConfiguration('git', Uri.file(repository.root));
|
||||
const openDiffOnClick = config.get<boolean>('openDiffOnClick');
|
||||
|
||||
if (openDiffOnClick) {
|
||||
await this._openResource(resource, undefined, preserveFocus, false);
|
||||
} else {
|
||||
await this.openFile(resource);
|
||||
}
|
||||
}
|
||||
|
||||
private async _openResource(resource: Resource, preview?: boolean, preserveFocus?: boolean, preserveSelection?: boolean): Promise<void> {
|
||||
let stat: Stats | undefined;
|
||||
|
||||
try {
|
||||
stat = await new Promise<Stats>((c, e) => lstat(resource.resourceUri.fsPath, (err, stat) => err ? e(err) : c(stat)));
|
||||
} catch (err) {
|
||||
// noop
|
||||
}
|
||||
|
||||
let left: Uri | undefined;
|
||||
let right: Uri | undefined;
|
||||
|
||||
if (stat && stat.isDirectory()) {
|
||||
const repository = this.model.getRepositoryForSubmodule(resource.resourceUri);
|
||||
|
||||
if (repository) {
|
||||
right = toGitUri(resource.resourceUri, resource.resourceGroupType === ResourceGroupType.Index ? 'index' : 'wt', { submoduleOf: repository.root });
|
||||
}
|
||||
} else {
|
||||
if (resource.type !== Status.DELETED_BY_THEM) {
|
||||
left = this.getLeftResource(resource);
|
||||
}
|
||||
|
||||
right = this.getRightResource(resource);
|
||||
}
|
||||
|
||||
const title = this.getTitle(resource);
|
||||
|
||||
if (!right) {
|
||||
// TODO
|
||||
console.error('oh no');
|
||||
return;
|
||||
}
|
||||
|
||||
const opts: TextDocumentShowOptions = {
|
||||
preserveFocus,
|
||||
preview,
|
||||
viewColumn: ViewColumn.Active
|
||||
};
|
||||
|
||||
const activeTextEditor = window.activeTextEditor;
|
||||
|
||||
// Check if active text editor has same path as other editor. we cannot compare via
|
||||
// URI.toString() here because the schemas can be different. Instead we just go by path.
|
||||
if (preserveSelection && activeTextEditor && activeTextEditor.document.uri.path === right.path) {
|
||||
opts.selection = activeTextEditor.selection;
|
||||
}
|
||||
|
||||
if (!left) {
|
||||
await commands.executeCommand<void>('vscode.open', right, opts, title);
|
||||
} else {
|
||||
await commands.executeCommand<void>('vscode.diff', left, right, title, opts);
|
||||
}
|
||||
}
|
||||
|
||||
private getLeftResource(resource: Resource): Uri | undefined {
|
||||
switch (resource.type) {
|
||||
case Status.INDEX_MODIFIED:
|
||||
case Status.INDEX_RENAMED:
|
||||
case Status.INDEX_ADDED:
|
||||
return toGitUri(resource.original, 'HEAD');
|
||||
|
||||
case Status.MODIFIED:
|
||||
case Status.UNTRACKED:
|
||||
return toGitUri(resource.resourceUri, '~');
|
||||
|
||||
case Status.DELETED_BY_THEM:
|
||||
return toGitUri(resource.resourceUri, '');
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private getRightResource(resource: Resource): Uri | undefined {
|
||||
switch (resource.type) {
|
||||
case Status.INDEX_MODIFIED:
|
||||
case Status.INDEX_ADDED:
|
||||
case Status.INDEX_COPIED:
|
||||
case Status.INDEX_RENAMED:
|
||||
return toGitUri(resource.resourceUri, '');
|
||||
|
||||
case Status.INDEX_DELETED:
|
||||
case Status.DELETED:
|
||||
return toGitUri(resource.resourceUri, 'HEAD');
|
||||
|
||||
case Status.DELETED_BY_US:
|
||||
return toGitUri(resource.resourceUri, '~3');
|
||||
|
||||
case Status.DELETED_BY_THEM:
|
||||
return toGitUri(resource.resourceUri, '~2');
|
||||
|
||||
case Status.MODIFIED:
|
||||
case Status.UNTRACKED:
|
||||
case Status.IGNORED:
|
||||
case Status.INTENT_TO_ADD:
|
||||
const repository = this.model.getRepository(resource.resourceUri);
|
||||
|
||||
if (!repository) {
|
||||
return;
|
||||
}
|
||||
|
||||
const uriString = resource.resourceUri.toString();
|
||||
const [indexStatus] = repository.indexGroup.resourceStates.filter(r => r.resourceUri.toString() === uriString);
|
||||
|
||||
if (indexStatus && indexStatus.renameResourceUri) {
|
||||
return indexStatus.renameResourceUri;
|
||||
}
|
||||
|
||||
return resource.resourceUri;
|
||||
|
||||
case Status.BOTH_ADDED:
|
||||
case Status.BOTH_MODIFIED:
|
||||
return resource.resourceUri;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private getTitle(resource: Resource): string {
|
||||
const basename = path.basename(resource.resourceUri.fsPath);
|
||||
|
||||
switch (resource.type) {
|
||||
case Status.INDEX_MODIFIED:
|
||||
case Status.INDEX_RENAMED:
|
||||
case Status.INDEX_ADDED:
|
||||
return localize('git.title.index', '{0} (Index)', basename);
|
||||
|
||||
case Status.MODIFIED:
|
||||
case Status.BOTH_ADDED:
|
||||
case Status.BOTH_MODIFIED:
|
||||
return localize('git.title.workingTree', '{0} (Working Tree)', basename);
|
||||
|
||||
case Status.DELETED_BY_US:
|
||||
return localize('git.title.theirs', '{0} (Theirs)', basename);
|
||||
|
||||
case Status.DELETED_BY_THEM:
|
||||
return localize('git.title.ours', '{0} (Ours)', basename);
|
||||
|
||||
case Status.UNTRACKED:
|
||||
return localize('git.title.untracked', '{0} (Untracked)', basename);
|
||||
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
await resource.open();
|
||||
}
|
||||
|
||||
async cloneRepository(url?: string, parentPath?: string, options: { recursive?: boolean } = {}): Promise<void> {
|
||||
@ -531,35 +425,54 @@ export class CommandCenter {
|
||||
(progress, token) => this.git.clone(url!, { parentPath: parentPath!, progress, recursive: options.recursive }, token)
|
||||
);
|
||||
|
||||
let message = localize('proposeopen', "Would you like to open the cloned repository?");
|
||||
const open = localize('openrepo', "Open");
|
||||
const openNewWindow = localize('openreponew', "Open in New Window");
|
||||
const choices = [open, openNewWindow];
|
||||
const config = workspace.getConfiguration('git');
|
||||
const openAfterClone = config.get<'always' | 'alwaysNewWindow' | 'whenNoFolderOpen' | 'prompt'>('openAfterClone');
|
||||
|
||||
const addToWorkspace = localize('add', "Add to Workspace");
|
||||
if (workspace.workspaceFolders) {
|
||||
message = localize('proposeopen2', "Would you like to open the cloned repository, or add it to the current workspace?");
|
||||
choices.push(addToWorkspace);
|
||||
enum PostCloneAction { Open, OpenNewWindow, AddToWorkspace }
|
||||
let action: PostCloneAction | undefined = undefined;
|
||||
|
||||
if (openAfterClone === 'always') {
|
||||
action = PostCloneAction.Open;
|
||||
} else if (openAfterClone === 'alwaysNewWindow') {
|
||||
action = PostCloneAction.OpenNewWindow;
|
||||
} else if (openAfterClone === 'whenNoFolderOpen' && !workspace.workspaceFolders) {
|
||||
action = PostCloneAction.Open;
|
||||
}
|
||||
|
||||
const result = await window.showInformationMessage(message, ...choices);
|
||||
if (action === undefined) {
|
||||
let message = localize('proposeopen', "Would you like to open the cloned repository?");
|
||||
const open = localize('openrepo', "Open");
|
||||
const openNewWindow = localize('openreponew', "Open in New Window");
|
||||
const choices = [open, openNewWindow];
|
||||
|
||||
const addToWorkspace = localize('add', "Add to Workspace");
|
||||
if (workspace.workspaceFolders) {
|
||||
message = localize('proposeopen2', "Would you like to open the cloned repository, or add it to the current workspace?");
|
||||
choices.push(addToWorkspace);
|
||||
}
|
||||
|
||||
const result = await window.showInformationMessage(message, ...choices);
|
||||
|
||||
action = result === open ? PostCloneAction.Open
|
||||
: result === openNewWindow ? PostCloneAction.OpenNewWindow
|
||||
: result === addToWorkspace ? PostCloneAction.AddToWorkspace : undefined;
|
||||
}
|
||||
|
||||
const openFolder = result === open;
|
||||
/* __GDPR__
|
||||
"clone" : {
|
||||
"outcome" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
|
||||
"openFolder": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }
|
||||
}
|
||||
*/
|
||||
this.telemetryReporter.sendTelemetryEvent('clone', { outcome: 'success' }, { openFolder: openFolder ? 1 : 0 });
|
||||
this.telemetryReporter.sendTelemetryEvent('clone', { outcome: 'success' }, { openFolder: action === PostCloneAction.Open || action === PostCloneAction.OpenNewWindow ? 1 : 0 });
|
||||
|
||||
const uri = Uri.file(repositoryPath);
|
||||
|
||||
if (openFolder) {
|
||||
if (action === PostCloneAction.Open) {
|
||||
commands.executeCommand('vscode.openFolder', uri, { forceReuseWindow: true });
|
||||
} else if (result === addToWorkspace) {
|
||||
} else if (action === PostCloneAction.AddToWorkspace) {
|
||||
workspace.updateWorkspaceFolders(workspace.workspaceFolders!.length, 0, { uri });
|
||||
} else if (result === openNewWindow) {
|
||||
} else if (action === PostCloneAction.OpenNewWindow) {
|
||||
commands.executeCommand('vscode.openFolder', uri, { forceNewWindow: true });
|
||||
}
|
||||
} catch (err) {
|
||||
@ -761,7 +674,10 @@ export class CommandCenter {
|
||||
try {
|
||||
document = await workspace.openTextDocument(uri);
|
||||
} catch (error) {
|
||||
await commands.executeCommand('vscode.open', uri, opts);
|
||||
await commands.executeCommand('vscode.open', uri, {
|
||||
...opts,
|
||||
override: arg instanceof Resource && arg.type === Status.BOTH_MODIFIED ? false : undefined
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -801,7 +717,7 @@ export class CommandCenter {
|
||||
return;
|
||||
}
|
||||
|
||||
const HEAD = this.getLeftResource(resource);
|
||||
const HEAD = resource.leftUri;
|
||||
const basename = path.basename(resource.resourceUri.fsPath);
|
||||
const title = `${basename} (HEAD)`;
|
||||
|
||||
@ -819,10 +735,6 @@ export class CommandCenter {
|
||||
|
||||
@command('git.openChange')
|
||||
async openChange(arg?: Resource | Uri, ...resourceStates: SourceControlResourceState[]): Promise<void> {
|
||||
const preserveFocus = arg instanceof Resource;
|
||||
const preview = !(arg instanceof Resource);
|
||||
|
||||
const preserveSelection = arg instanceof Uri || !arg;
|
||||
let resources: Resource[] | undefined = undefined;
|
||||
|
||||
if (arg instanceof Uri) {
|
||||
@ -849,10 +761,33 @@ export class CommandCenter {
|
||||
}
|
||||
|
||||
for (const resource of resources) {
|
||||
await this._openResource(resource, preview, preserveFocus, preserveSelection);
|
||||
await resource.openChange();
|
||||
}
|
||||
}
|
||||
|
||||
@command('git.rename', { repository: true })
|
||||
async rename(repository: Repository, fromUri: Uri | undefined): Promise<void> {
|
||||
fromUri = fromUri ?? window.activeTextEditor?.document.uri;
|
||||
|
||||
if (!fromUri) {
|
||||
return;
|
||||
}
|
||||
|
||||
const from = path.relative(repository.root, fromUri.path);
|
||||
let to = await window.showInputBox({
|
||||
value: from,
|
||||
valueSelection: [from.length - path.basename(from).length, from.length]
|
||||
});
|
||||
|
||||
to = to?.trim();
|
||||
|
||||
if (!to) {
|
||||
return;
|
||||
}
|
||||
|
||||
await repository.move(from, to);
|
||||
}
|
||||
|
||||
@command('git.stage')
|
||||
async stage(...resourceStates: SourceControlResourceState[]): Promise<void> {
|
||||
this.outputChannel.appendLine(`git.stage ${resourceStates.length}`);
|
||||
@ -1018,6 +953,10 @@ export class CommandCenter {
|
||||
|
||||
@command('git.stageChange')
|
||||
async stageChange(uri: Uri, changes: LineChange[], index: number): Promise<void> {
|
||||
if (!uri) {
|
||||
return;
|
||||
}
|
||||
|
||||
const textEditor = window.visibleTextEditors.filter(e => e.document.uri.toString() === uri.toString())[0];
|
||||
|
||||
if (!textEditor) {
|
||||
@ -1068,6 +1007,10 @@ export class CommandCenter {
|
||||
|
||||
@command('git.revertChange')
|
||||
async revertChange(uri: Uri, changes: LineChange[], index: number): Promise<void> {
|
||||
if (!uri) {
|
||||
return;
|
||||
}
|
||||
|
||||
const textEditor = window.visibleTextEditors.filter(e => e.document.uri.toString() === uri.toString())[0];
|
||||
|
||||
if (!textEditor) {
|
||||
@ -1397,7 +1340,7 @@ export class CommandCenter {
|
||||
? localize('unsaved files single', "The following file has unsaved changes which won't be included in the commit if you proceed: {0}.\n\nWould you like to save it before committing?", path.basename(documents[0].uri.fsPath))
|
||||
: localize('unsaved files', "There are {0} unsaved files.\n\nWould you like to save them before committing?", documents.length);
|
||||
const saveAndCommit = localize('save and commit', "Save All & Commit");
|
||||
const commit = localize('commit', "Commit Anyway");
|
||||
const commit = localize('commit', "Commit Staged Changes");
|
||||
const pick = await window.showWarningMessage(message, { modal: true }, saveAndCommit, commit);
|
||||
|
||||
if (pick === saveAndCommit) {
|
||||
@ -1409,8 +1352,14 @@ export class CommandCenter {
|
||||
}
|
||||
}
|
||||
|
||||
if (!opts) {
|
||||
opts = { all: noStagedChanges };
|
||||
} else if (!opts.all && noStagedChanges && !opts.empty) {
|
||||
opts = { ...opts, all: true };
|
||||
}
|
||||
|
||||
// no changes, and the user has not configured to commit all in this case
|
||||
if (!noUnstagedChanges && noStagedChanges && !enableSmartCommit) {
|
||||
if (!noUnstagedChanges && noStagedChanges && !enableSmartCommit && !opts.empty) {
|
||||
const suggestSmartCommit = config.get<boolean>('suggestSmartCommit') === true;
|
||||
|
||||
if (!suggestSmartCommit) {
|
||||
@ -1434,13 +1383,7 @@ export class CommandCenter {
|
||||
}
|
||||
}
|
||||
|
||||
if (!opts) {
|
||||
opts = { all: noStagedChanges };
|
||||
} else if (!opts.all && noStagedChanges) {
|
||||
opts = { ...opts, all: true };
|
||||
}
|
||||
|
||||
// enable signing of commits if configurated
|
||||
// enable signing of commits if configured
|
||||
opts.signCommit = enableCommitSigning;
|
||||
|
||||
if (config.get<boolean>('alwaysSignOff')) {
|
||||
@ -1458,10 +1401,18 @@ export class CommandCenter {
|
||||
// no staged changes and no tracked unstaged changes
|
||||
|| (noStagedChanges && smartCommitChanges === 'tracked' && repository.workingTreeGroup.resourceStates.every(r => r.type === Status.UNTRACKED))
|
||||
)
|
||||
// amend allows changing only the commit message
|
||||
&& !opts.amend
|
||||
&& !opts.empty
|
||||
) {
|
||||
window.showInformationMessage(localize('no changes', "There are no changes to commit."));
|
||||
return false;
|
||||
const commitAnyway = localize('commit anyway', "Create Empty Commit");
|
||||
const answer = await window.showInformationMessage(localize('no changes', "There are no changes to commit."), commitAnyway);
|
||||
|
||||
if (answer !== commitAnyway) {
|
||||
return false;
|
||||
}
|
||||
|
||||
opts.empty = true;
|
||||
}
|
||||
|
||||
if (opts.noVerify) {
|
||||
@ -1484,9 +1435,9 @@ export class CommandCenter {
|
||||
}
|
||||
}
|
||||
|
||||
const message = await getCommitMessage();
|
||||
let message = await getCommitMessage();
|
||||
|
||||
if (!message) {
|
||||
if (!message && !opts.amend) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -1523,7 +1474,7 @@ export class CommandCenter {
|
||||
let value: string | undefined = undefined;
|
||||
|
||||
if (opts && opts.amend && repository.HEAD && repository.HEAD.commit) {
|
||||
value = (await repository.getCommit(repository.HEAD.commit)).message;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const branchName = repository.headShortName;
|
||||
@ -1690,20 +1641,38 @@ export class CommandCenter {
|
||||
}
|
||||
|
||||
@command('git.checkout', { repository: true })
|
||||
async checkout(repository: Repository, treeish: string): Promise<boolean> {
|
||||
if (typeof treeish === 'string') {
|
||||
await repository.checkout(treeish);
|
||||
async checkout(repository: Repository, treeish?: string): Promise<boolean> {
|
||||
return this._checkout(repository, { treeish });
|
||||
}
|
||||
|
||||
@command('git.checkoutDetached', { repository: true })
|
||||
async checkoutDetached(repository: Repository, treeish?: string): Promise<boolean> {
|
||||
return this._checkout(repository, { detached: true, treeish });
|
||||
}
|
||||
|
||||
private async _checkout(repository: Repository, opts?: { detached?: boolean, treeish?: string }): Promise<boolean> {
|
||||
if (typeof opts?.treeish === 'string') {
|
||||
await repository.checkout(opts?.treeish, opts);
|
||||
return true;
|
||||
}
|
||||
|
||||
const createBranch = new CreateBranchItem(this);
|
||||
const createBranchFrom = new CreateBranchFromItem(this);
|
||||
const picks = [createBranch, createBranchFrom, ...createCheckoutItems(repository)];
|
||||
const placeHolder = localize('select a ref to checkout', 'Select a ref to checkout');
|
||||
const createBranch = new CreateBranchItem();
|
||||
const createBranchFrom = new CreateBranchFromItem();
|
||||
const checkoutDetached = new CheckoutDetachedItem();
|
||||
const picks: QuickPickItem[] = [];
|
||||
|
||||
if (!opts?.detached) {
|
||||
picks.push(createBranch, createBranchFrom, checkoutDetached);
|
||||
}
|
||||
|
||||
picks.push(...createCheckoutItems(repository));
|
||||
|
||||
const quickpick = window.createQuickPick();
|
||||
quickpick.items = picks;
|
||||
quickpick.placeholder = placeHolder;
|
||||
quickpick.placeholder = opts?.detached
|
||||
? localize('select a ref to checkout detached', 'Select a ref to checkout in detached mode')
|
||||
: localize('select a ref to checkout', 'Select a ref to checkout');
|
||||
|
||||
quickpick.show();
|
||||
|
||||
const choice = await new Promise<QuickPickItem | undefined>(c => quickpick.onDidAccept(() => c(quickpick.activeItems[0])));
|
||||
@ -1717,8 +1686,31 @@ export class CommandCenter {
|
||||
await this._branch(repository, quickpick.value);
|
||||
} else if (choice === createBranchFrom) {
|
||||
await this._branch(repository, quickpick.value, true);
|
||||
} else if (choice === checkoutDetached) {
|
||||
return this._checkout(repository, { detached: true });
|
||||
} else {
|
||||
await (choice as CheckoutItem).run(repository);
|
||||
const item = choice as CheckoutItem;
|
||||
|
||||
try {
|
||||
await item.run(repository, opts);
|
||||
} catch (err) {
|
||||
if (err.gitErrorCode !== GitErrorCodes.DirtyWorkTree) {
|
||||
throw err;
|
||||
}
|
||||
|
||||
const force = localize('force', "Force Checkout");
|
||||
const stash = localize('stashcheckout', "Stash & Checkout");
|
||||
const choice = await window.showWarningMessage(localize('local changes', "Your local changes would be overwritten by checkout."), { modal: true }, force, stash);
|
||||
|
||||
if (choice === force) {
|
||||
await this.cleanAll(repository);
|
||||
await item.run(repository, opts);
|
||||
} else if (choice === stash) {
|
||||
await this.stash(repository);
|
||||
await item.run(repository, opts);
|
||||
await this.stashPopLatest(repository);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
@ -1849,8 +1841,8 @@ export class CommandCenter {
|
||||
@command('git.merge', { repository: true })
|
||||
async merge(repository: Repository): Promise<void> {
|
||||
const config = workspace.getConfiguration('git');
|
||||
const checkoutType = config.get<string>('checkoutType') || 'all';
|
||||
const includeRemotes = checkoutType === 'all' || checkoutType === 'remote';
|
||||
const checkoutType = config.get<string | string[]>('checkoutType');
|
||||
const includeRemotes = checkoutType === 'all' || checkoutType === 'remote' || checkoutType?.includes('remote');
|
||||
|
||||
const heads = repository.refs.filter(ref => ref.type === RefType.Head)
|
||||
.filter(ref => ref.name || ref.commit)
|
||||
@ -1874,8 +1866,8 @@ export class CommandCenter {
|
||||
@command('git.rebase', { repository: true })
|
||||
async rebase(repository: Repository): Promise<void> {
|
||||
const config = workspace.getConfiguration('git');
|
||||
const checkoutType = config.get<string>('checkoutType') || 'all';
|
||||
const includeRemotes = checkoutType === 'all' || checkoutType === 'remote';
|
||||
const checkoutType = config.get<string | string[]>('checkoutType');
|
||||
const includeRemotes = checkoutType === 'all' || checkoutType === 'remote' || checkoutType?.includes('remote');
|
||||
|
||||
const heads = repository.refs.filter(ref => ref.type === RefType.Head)
|
||||
.filter(ref => ref.name !== repository.HEAD?.name)
|
||||
@ -2068,7 +2060,7 @@ export class CommandCenter {
|
||||
forcePushMode = config.get<boolean>('useForcePushWithLease') === true ? ForcePushMode.ForceWithLease : ForcePushMode.Force;
|
||||
|
||||
if (config.get<boolean>('confirmForcePush')) {
|
||||
const message = localize('confirm force push', "You are about to force push your changes, this can be destructive and could inadvertedly overwrite changes made by others.\n\nAre you sure to continue?");
|
||||
const message = localize('confirm force push', "You are about to force push your changes, this can be destructive and could inadvertently overwrite changes made by others.\n\nAre you sure to continue?");
|
||||
const yes = localize('ok', "OK");
|
||||
const neverAgain = localize('never ask again', "OK, Don't Ask Again");
|
||||
const pick = await window.showWarningMessage(message, { modal: true }, yes, neverAgain);
|
||||
@ -2086,6 +2078,10 @@ export class CommandCenter {
|
||||
return;
|
||||
}
|
||||
|
||||
if (pushOptions.pushType === PushType.PushTags) {
|
||||
await repository.pushTags(undefined, forcePushMode);
|
||||
}
|
||||
|
||||
if (!repository.HEAD || !repository.HEAD.name) {
|
||||
if (!pushOptions.silent) {
|
||||
window.showWarningMessage(localize('nobranch', "Please check out a branch to push to a remote."));
|
||||
@ -2157,6 +2153,21 @@ export class CommandCenter {
|
||||
await this._push(repository, { pushType: PushType.PushFollowTags, forcePush: true });
|
||||
}
|
||||
|
||||
@command('git.cherryPick', { repository: true })
|
||||
async cherryPick(repository: Repository): Promise<void> {
|
||||
const hash = await window.showInputBox({
|
||||
placeHolder: localize('commit hash', "Commit Hash"),
|
||||
prompt: localize('provide commit hash', "Please provide the commit hash"),
|
||||
ignoreFocusOut: true
|
||||
});
|
||||
|
||||
if (!hash) {
|
||||
return;
|
||||
}
|
||||
|
||||
await repository.cherryPick(hash);
|
||||
}
|
||||
|
||||
@command('git.pushTo', { repository: true })
|
||||
async pushTo(repository: Repository): Promise<void> {
|
||||
await this._push(repository, { pushType: PushType.PushTo });
|
||||
@ -2167,6 +2178,11 @@ export class CommandCenter {
|
||||
await this._push(repository, { pushType: PushType.PushTo, forcePush: true });
|
||||
}
|
||||
|
||||
@command('git.pushTags', { repository: true })
|
||||
async pushTags(repository: Repository): Promise<void> {
|
||||
await this._push(repository, { pushType: PushType.PushTags });
|
||||
}
|
||||
|
||||
@command('git.addRemote', { repository: true })
|
||||
async addRemote(repository: Repository): Promise<string | undefined> {
|
||||
const url = await pickRemoteSource(this.model, {
|
||||
@ -2200,6 +2216,7 @@ export class CommandCenter {
|
||||
}
|
||||
|
||||
await repository.addRemote(name, url);
|
||||
await repository.fetch(name);
|
||||
return name;
|
||||
}
|
||||
|
||||
@ -2413,7 +2430,45 @@ export class CommandCenter {
|
||||
return;
|
||||
}
|
||||
|
||||
const message = await this.getStashMessage();
|
||||
const config = workspace.getConfiguration('git', Uri.file(repository.root));
|
||||
const promptToSaveFilesBeforeStashing = config.get<'always' | 'staged' | 'never'>('promptToSaveFilesBeforeStash');
|
||||
|
||||
if (promptToSaveFilesBeforeStashing !== 'never') {
|
||||
let documents = workspace.textDocuments
|
||||
.filter(d => !d.isUntitled && d.isDirty && isDescendant(repository.root, d.uri.fsPath));
|
||||
|
||||
if (promptToSaveFilesBeforeStashing === 'staged' || repository.indexGroup.resourceStates.length > 0) {
|
||||
documents = documents
|
||||
.filter(d => repository.indexGroup.resourceStates.some(s => pathEquals(s.resourceUri.fsPath, d.uri.fsPath)));
|
||||
}
|
||||
|
||||
if (documents.length > 0) {
|
||||
const message = documents.length === 1
|
||||
? localize('unsaved stash files single', "The following file has unsaved changes which won't be included in the stash if you proceed: {0}.\n\nWould you like to save it before stashing?", path.basename(documents[0].uri.fsPath))
|
||||
: localize('unsaved stash files', "There are {0} unsaved files.\n\nWould you like to save them before stashing?", documents.length);
|
||||
const saveAndStash = localize('save and stash', "Save All & Stash");
|
||||
const stash = localize('stash', "Stash Anyway");
|
||||
const pick = await window.showWarningMessage(message, { modal: true }, saveAndStash, stash);
|
||||
|
||||
if (pick === saveAndStash) {
|
||||
await Promise.all(documents.map(d => d.save()));
|
||||
} else if (pick !== stash) {
|
||||
return; // do not stash on cancel
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let message: string | undefined;
|
||||
|
||||
if (config.get<boolean>('useCommitInputAsStashMessage') && (!repository.sourceControl.commitTemplate || repository.inputBox.value !== repository.sourceControl.commitTemplate)) {
|
||||
message = repository.inputBox.value;
|
||||
}
|
||||
|
||||
message = await window.showInputBox({
|
||||
value: message,
|
||||
prompt: localize('provide stash message', "Optionally provide a stash message"),
|
||||
placeHolder: localize('stash message', "Stash message")
|
||||
});
|
||||
|
||||
if (typeof message === 'undefined') {
|
||||
return;
|
||||
@ -2422,13 +2477,6 @@ export class CommandCenter {
|
||||
await repository.createStash(message, includeUntracked);
|
||||
}
|
||||
|
||||
private async getStashMessage(): Promise<string | undefined> {
|
||||
return await window.showInputBox({
|
||||
prompt: localize('provide stash message', "Optionally provide a stash message"),
|
||||
placeHolder: localize('stash message', "Stash message")
|
||||
});
|
||||
}
|
||||
|
||||
@command('git.stash', { repository: true })
|
||||
stash(repository: Repository): Promise<void> {
|
||||
return this._stash(repository);
|
||||
@ -2496,6 +2544,16 @@ export class CommandCenter {
|
||||
return;
|
||||
}
|
||||
|
||||
// request confirmation for the operation
|
||||
const yes = localize('yes', "Yes");
|
||||
const result = await window.showWarningMessage(
|
||||
localize('sure drop', "Are you sure you want to drop the stash: {0}?", stash.description),
|
||||
yes
|
||||
);
|
||||
if (result !== yes) {
|
||||
return;
|
||||
}
|
||||
|
||||
await repository.dropStash(stash.index);
|
||||
}
|
||||
|
||||
@ -2614,6 +2672,31 @@ export class CommandCenter {
|
||||
const outputChannel = this.outputChannel as OutputChannel;
|
||||
choices.set(openOutputChannelChoice, () => outputChannel.show());
|
||||
|
||||
const showCommandOutputChoice = localize('show command output', "Show Command Output");
|
||||
if (err.stderr) {
|
||||
choices.set(showCommandOutputChoice, async () => {
|
||||
const timestamp = new Date().getTime();
|
||||
const uri = Uri.parse(`git-output:/git-error-${timestamp}`);
|
||||
|
||||
let command = 'git';
|
||||
|
||||
if (err.gitArgs) {
|
||||
command = `${command} ${err.gitArgs.join(' ')}`;
|
||||
} else if (err.gitCommand) {
|
||||
command = `${command} ${err.gitCommand}`;
|
||||
}
|
||||
|
||||
this.commandErrors.set(uri, `> ${command}\n${err.stderr}`);
|
||||
|
||||
try {
|
||||
const doc = await workspace.openTextDocument(uri);
|
||||
await window.showTextDocument(doc);
|
||||
} finally {
|
||||
this.commandErrors.delete(uri);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
switch (err.gitErrorCode) {
|
||||
case GitErrorCodes.DirtyWorkTree:
|
||||
message = localize('clean repo', "Please clean your repository working tree before checkout.");
|
||||
|
@ -15,18 +15,18 @@ class GitIgnoreDecorationProvider implements FileDecorationProvider {
|
||||
|
||||
private static Decoration: FileDecoration = { color: new ThemeColor('gitDecoration.ignoredResourceForeground') };
|
||||
|
||||
readonly onDidChange: Event<Uri[]>;
|
||||
readonly onDidChangeFileDecorations: Event<Uri[]>;
|
||||
private queue = new Map<string, { repository: Repository; queue: Map<string, PromiseSource<FileDecoration | undefined>>; }>();
|
||||
private disposables: Disposable[] = [];
|
||||
|
||||
constructor(private model: Model) {
|
||||
this.onDidChange = fireEvent(anyEvent<any>(
|
||||
this.onDidChangeFileDecorations = fireEvent(anyEvent<any>(
|
||||
filterEvent(workspace.onDidSaveTextDocument, e => /\.gitignore$|\.git\/info\/exclude$/.test(e.uri.path)),
|
||||
model.onDidOpenRepository,
|
||||
model.onDidCloseRepository
|
||||
));
|
||||
|
||||
this.disposables.push(window.registerDecorationProvider(this));
|
||||
this.disposables.push(window.registerFileDecorationProvider(this));
|
||||
}
|
||||
|
||||
async provideFileDecoration(uri: Uri): Promise<FileDecoration | undefined> {
|
||||
@ -93,14 +93,14 @@ class GitDecorationProvider implements FileDecorationProvider {
|
||||
};
|
||||
|
||||
private readonly _onDidChangeDecorations = new EventEmitter<Uri[]>();
|
||||
readonly onDidChange: Event<Uri[]> = this._onDidChangeDecorations.event;
|
||||
readonly onDidChangeFileDecorations: Event<Uri[]> = this._onDidChangeDecorations.event;
|
||||
|
||||
private disposables: Disposable[] = [];
|
||||
private decorations = new Map<string, FileDecoration>();
|
||||
|
||||
constructor(private repository: Repository) {
|
||||
this.disposables.push(
|
||||
window.registerDecorationProvider(this),
|
||||
window.registerFileDecorationProvider(this),
|
||||
repository.onDidRunGitStatus(this.onDidRunGitStatus, this)
|
||||
);
|
||||
}
|
||||
|
@ -260,6 +260,7 @@ export interface IGitErrorData {
|
||||
exitCode?: number;
|
||||
gitErrorCode?: string;
|
||||
gitCommand?: string;
|
||||
gitArgs?: string[];
|
||||
}
|
||||
|
||||
export class GitError {
|
||||
@ -271,6 +272,7 @@ export class GitError {
|
||||
exitCode?: number;
|
||||
gitErrorCode?: string;
|
||||
gitCommand?: string;
|
||||
gitArgs?: string[];
|
||||
|
||||
constructor(data: IGitErrorData) {
|
||||
if (data.error) {
|
||||
@ -287,6 +289,7 @@ export class GitError {
|
||||
this.exitCode = data.exitCode;
|
||||
this.gitErrorCode = data.gitErrorCode;
|
||||
this.gitCommand = data.gitCommand;
|
||||
this.gitArgs = data.gitArgs;
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
@ -535,7 +538,8 @@ export class Git {
|
||||
stderr: result.stderr,
|
||||
exitCode: result.exitCode,
|
||||
gitErrorCode: getGitErrorCode(result.stderr),
|
||||
gitCommand: args[0]
|
||||
gitCommand: args[0],
|
||||
gitArgs: args
|
||||
}));
|
||||
}
|
||||
|
||||
@ -1295,13 +1299,17 @@ export class Repository {
|
||||
await this.run(['update-index', add, '--cacheinfo', mode, hash, path]);
|
||||
}
|
||||
|
||||
async checkout(treeish: string, paths: string[], opts: { track?: boolean } = Object.create(null)): Promise<void> {
|
||||
async checkout(treeish: string, paths: string[], opts: { track?: boolean, detached?: boolean } = Object.create(null)): Promise<void> {
|
||||
const args = ['checkout', '-q'];
|
||||
|
||||
if (opts.track) {
|
||||
args.push('--track');
|
||||
}
|
||||
|
||||
if (opts.detached) {
|
||||
args.push('--detach');
|
||||
}
|
||||
|
||||
if (treeish) {
|
||||
args.push(treeish);
|
||||
}
|
||||
@ -1317,23 +1325,30 @@ export class Repository {
|
||||
} catch (err) {
|
||||
if (/Please,? commit your changes or stash them/.test(err.stderr || '')) {
|
||||
err.gitErrorCode = GitErrorCodes.DirtyWorkTree;
|
||||
err.gitTreeish = treeish;
|
||||
}
|
||||
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
async commit(message: string, opts: CommitOptions = Object.create(null)): Promise<void> {
|
||||
const args = ['commit', '--quiet', '--allow-empty-message', '--file', '-'];
|
||||
async commit(message: string | undefined, opts: CommitOptions = Object.create(null)): Promise<void> {
|
||||
const args = ['commit', '--quiet', '--allow-empty-message'];
|
||||
|
||||
if (opts.all) {
|
||||
args.push('--all');
|
||||
}
|
||||
|
||||
if (opts.amend) {
|
||||
if (opts.amend && message) {
|
||||
args.push('--amend');
|
||||
}
|
||||
|
||||
if (opts.amend && !message) {
|
||||
args.push('--amend', '--no-edit');
|
||||
} else {
|
||||
args.push('--file', '-');
|
||||
}
|
||||
|
||||
if (opts.signoff) {
|
||||
args.push('--signoff');
|
||||
}
|
||||
@ -1350,8 +1365,11 @@ export class Repository {
|
||||
args.push('--no-verify');
|
||||
}
|
||||
|
||||
// Stops git from guessing at user/email
|
||||
args.splice(0, 0, '-c', 'user.useConfigOnly=true');
|
||||
|
||||
try {
|
||||
await this.run(args, { input: message || '' });
|
||||
await this.run(args, !opts.amend || message ? { input: message || '' } : {});
|
||||
} catch (commitErr) {
|
||||
await this.handleCommitError(commitErr);
|
||||
}
|
||||
@ -1414,6 +1432,11 @@ export class Repository {
|
||||
await this.run(args);
|
||||
}
|
||||
|
||||
async move(from: string, to: string): Promise<void> {
|
||||
const args = ['mv', from, to];
|
||||
await this.run(args);
|
||||
}
|
||||
|
||||
async setBranchUpstream(name: string, upstream: string): Promise<void> {
|
||||
const args = ['branch', '--set-upstream-to', upstream, name];
|
||||
await this.run(args);
|
||||
@ -1536,9 +1559,11 @@ export class Repository {
|
||||
await this.run(args);
|
||||
}
|
||||
|
||||
async fetch(options: { remote?: string, ref?: string, all?: boolean, prune?: boolean, depth?: number, silent?: boolean } = {}): Promise<void> {
|
||||
async fetch(options: { remote?: string, ref?: string, all?: boolean, prune?: boolean, depth?: number, silent?: boolean, readonly cancellationToken?: CancellationToken } = {}): Promise<void> {
|
||||
const args = ['fetch'];
|
||||
const spawnOptions: SpawnOptions = {};
|
||||
const spawnOptions: SpawnOptions = {
|
||||
cancellationToken: options.cancellationToken,
|
||||
};
|
||||
|
||||
if (options.remote) {
|
||||
args.push(options.remote);
|
||||
@ -1635,7 +1660,7 @@ export class Repository {
|
||||
}
|
||||
}
|
||||
|
||||
async push(remote?: string, name?: string, setUpstream: boolean = false, tags = false, forcePushMode?: ForcePushMode): Promise<void> {
|
||||
async push(remote?: string, name?: string, setUpstream: boolean = false, followTags = false, forcePushMode?: ForcePushMode, tags = false): Promise<void> {
|
||||
const args = ['push'];
|
||||
|
||||
if (forcePushMode === ForcePushMode.ForceWithLease) {
|
||||
@ -1648,10 +1673,14 @@ export class Repository {
|
||||
args.push('-u');
|
||||
}
|
||||
|
||||
if (tags) {
|
||||
if (followTags) {
|
||||
args.push('--follow-tags');
|
||||
}
|
||||
|
||||
if (tags) {
|
||||
args.push('--tags');
|
||||
}
|
||||
|
||||
if (remote) {
|
||||
args.push(remote);
|
||||
}
|
||||
@ -1677,6 +1706,11 @@ export class Repository {
|
||||
}
|
||||
}
|
||||
|
||||
async cherryPick(commitHash: string): Promise<void> {
|
||||
const args = ['cherry-pick', commitHash];
|
||||
await this.run(args);
|
||||
}
|
||||
|
||||
async blame(path: string): Promise<string> {
|
||||
try {
|
||||
const args = ['blame', sanitizePath(path)];
|
||||
@ -1761,11 +1795,17 @@ export class Repository {
|
||||
}
|
||||
}
|
||||
|
||||
getStatus(limit = 5000): Promise<{ status: IFileStatus[]; didHitLimit: boolean; }> {
|
||||
getStatus(opts?: { limit?: number, ignoreSubmodules?: boolean }): Promise<{ status: IFileStatus[]; didHitLimit: boolean; }> {
|
||||
return new Promise<{ status: IFileStatus[]; didHitLimit: boolean; }>((c, e) => {
|
||||
const parser = new GitStatusParser();
|
||||
const env = { GIT_OPTIONAL_LOCKS: '0' };
|
||||
const child = this.stream(['status', '-z', '-u'], { env });
|
||||
const args = ['status', '-z', '-u'];
|
||||
|
||||
if (opts?.ignoreSubmodules) {
|
||||
args.push('--ignore-submodules');
|
||||
}
|
||||
|
||||
const child = this.stream(args, { env });
|
||||
|
||||
const onExit = (exitCode: number) => {
|
||||
if (exitCode !== 0) {
|
||||
@ -1775,13 +1815,15 @@ export class Repository {
|
||||
stderr,
|
||||
exitCode,
|
||||
gitErrorCode: getGitErrorCode(stderr),
|
||||
gitCommand: 'status'
|
||||
gitCommand: 'status',
|
||||
gitArgs: args
|
||||
}));
|
||||
}
|
||||
|
||||
c({ status: parser.status, didHitLimit: false });
|
||||
};
|
||||
|
||||
const limit = opts?.limit ?? 5000;
|
||||
const onStdoutData = (raw: string) => {
|
||||
parser.update(raw);
|
||||
|
||||
@ -1845,7 +1887,7 @@ export class Repository {
|
||||
args.push('--sort', `-${opts.sort}`);
|
||||
}
|
||||
|
||||
args.push('--format', '%(refname) %(objectname)');
|
||||
args.push('--format', '%(refname) %(objectname) %(*objectname)');
|
||||
|
||||
if (opts?.pattern) {
|
||||
args.push(opts.pattern);
|
||||
@ -1860,12 +1902,12 @@ export class Repository {
|
||||
const fn = (line: string): Ref | null => {
|
||||
let match: RegExpExecArray | null;
|
||||
|
||||
if (match = /^refs\/heads\/([^ ]+) ([0-9a-f]{40})$/.exec(line)) {
|
||||
if (match = /^refs\/heads\/([^ ]+) ([0-9a-f]{40}) ([0-9a-f]{40})?$/.exec(line)) {
|
||||
return { name: match[1], commit: match[2], type: RefType.Head };
|
||||
} else if (match = /^refs\/remotes\/([^/]+)\/([^ ]+) ([0-9a-f]{40})$/.exec(line)) {
|
||||
} else if (match = /^refs\/remotes\/([^/]+)\/([^ ]+) ([0-9a-f]{40}) ([0-9a-f]{40})?$/.exec(line)) {
|
||||
return { name: `${match[1]}/${match[2]}`, commit: match[3], type: RefType.RemoteHead, remote: match[1] };
|
||||
} else if (match = /^refs\/tags\/([^ ]+) ([0-9a-f]{40})$/.exec(line)) {
|
||||
return { name: match[1], commit: match[2], type: RefType.Tag };
|
||||
} else if (match = /^refs\/tags\/([^ ]+) ([0-9a-f]{40}) ([0-9a-f]{40})?$/.exec(line)) {
|
||||
return { name: match[1], commit: match[3] ?? match[2], type: RefType.Tag };
|
||||
}
|
||||
|
||||
return null;
|
||||
|
@ -59,7 +59,8 @@ class RemoteSourceProviderQuickPick {
|
||||
this.quickpick.items = remoteSources.map(remoteSource => ({
|
||||
label: remoteSource.name,
|
||||
description: remoteSource.description || (typeof remoteSource.url === 'string' ? remoteSource.url : remoteSource.url[0]),
|
||||
remoteSource
|
||||
remoteSource,
|
||||
alwaysShow: true
|
||||
}));
|
||||
}
|
||||
} catch (err) {
|
||||
@ -80,12 +81,30 @@ class RemoteSourceProviderQuickPick {
|
||||
export interface PickRemoteSourceOptions {
|
||||
readonly providerLabel?: (provider: RemoteSourceProvider) => string;
|
||||
readonly urlLabel?: string;
|
||||
readonly providerName?: string;
|
||||
readonly branch?: boolean; // then result is PickRemoteSourceResult
|
||||
}
|
||||
|
||||
export async function pickRemoteSource(model: Model, options: PickRemoteSourceOptions = {}): Promise<string | undefined> {
|
||||
export interface PickRemoteSourceResult {
|
||||
readonly url: string;
|
||||
readonly branch?: string;
|
||||
}
|
||||
|
||||
export async function pickRemoteSource(model: Model, options: PickRemoteSourceOptions & { branch?: false | undefined }): Promise<string | undefined>;
|
||||
export async function pickRemoteSource(model: Model, options: PickRemoteSourceOptions & { branch: true }): Promise<PickRemoteSourceResult | undefined>;
|
||||
export async function pickRemoteSource(model: Model, options: PickRemoteSourceOptions = {}): Promise<string | PickRemoteSourceResult | undefined> {
|
||||
const quickpick = window.createQuickPick<(QuickPickItem & { provider?: RemoteSourceProvider, url?: string })>();
|
||||
quickpick.ignoreFocusOut = true;
|
||||
|
||||
if (options.providerName) {
|
||||
const provider = model.getRemoteProviders()
|
||||
.filter(provider => provider.name === options.providerName)[0];
|
||||
|
||||
if (provider) {
|
||||
return await pickProviderSource(provider, options);
|
||||
}
|
||||
}
|
||||
|
||||
const providers = model.getRemoteProviders()
|
||||
.map(provider => ({ label: (provider.icon ? `$(${provider.icon}) ` : '') + (options.providerLabel ? options.providerLabel(provider) : provider.name), alwaysShow: true, provider }));
|
||||
|
||||
@ -116,18 +135,48 @@ export async function pickRemoteSource(model: Model, options: PickRemoteSourceOp
|
||||
if (result.url) {
|
||||
return result.url;
|
||||
} else if (result.provider) {
|
||||
const quickpick = new RemoteSourceProviderQuickPick(result.provider);
|
||||
const remote = await quickpick.pick();
|
||||
|
||||
if (remote) {
|
||||
if (typeof remote.url === 'string') {
|
||||
return remote.url;
|
||||
} else if (remote.url.length > 0) {
|
||||
return await window.showQuickPick(remote.url, { ignoreFocusOut: true, placeHolder: localize('pick url', "Choose a URL to clone from.") });
|
||||
}
|
||||
}
|
||||
return await pickProviderSource(result.provider, options);
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
async function pickProviderSource(provider: RemoteSourceProvider, options: PickRemoteSourceOptions = {}): Promise<string | PickRemoteSourceResult | undefined> {
|
||||
const quickpick = new RemoteSourceProviderQuickPick(provider);
|
||||
const remote = await quickpick.pick();
|
||||
|
||||
let url: string | undefined;
|
||||
|
||||
if (remote) {
|
||||
if (typeof remote.url === 'string') {
|
||||
url = remote.url;
|
||||
} else if (remote.url.length > 0) {
|
||||
url = await window.showQuickPick(remote.url, { ignoreFocusOut: true, placeHolder: localize('pick url', "Choose a URL to clone from.") });
|
||||
}
|
||||
}
|
||||
|
||||
if (!url || !options.branch) {
|
||||
return url;
|
||||
}
|
||||
|
||||
if (!provider.getBranches) {
|
||||
return { url };
|
||||
}
|
||||
|
||||
const branches = await provider.getBranches(url);
|
||||
|
||||
if (!branches) {
|
||||
return { url };
|
||||
}
|
||||
|
||||
const branch = await window.showQuickPick(branches, {
|
||||
placeHolder: localize('branch name', "Branch name")
|
||||
});
|
||||
|
||||
if (!branch) {
|
||||
return { url };
|
||||
}
|
||||
|
||||
return { url, branch };
|
||||
}
|
||||
|
@ -75,13 +75,21 @@ export class Resource implements SourceControlResourceState {
|
||||
return this._resourceUri;
|
||||
}
|
||||
|
||||
@memoize
|
||||
get leftUri(): Uri | undefined {
|
||||
return this.resources[0];
|
||||
}
|
||||
|
||||
get rightUri(): Uri {
|
||||
return this.resources[1];
|
||||
}
|
||||
|
||||
get command(): Command {
|
||||
return {
|
||||
command: 'git.openResource',
|
||||
title: localize('open', "Open"),
|
||||
arguments: [this]
|
||||
};
|
||||
return this._commandResolver.resolveDefaultCommand(this);
|
||||
}
|
||||
|
||||
@memoize
|
||||
private get resources(): [Uri | undefined, Uri] {
|
||||
return this._commandResolver.getResources(this);
|
||||
}
|
||||
|
||||
get resourceGroupType(): ResourceGroupType { return this._resourceGroupType; }
|
||||
@ -262,12 +270,28 @@ export class Resource implements SourceControlResourceState {
|
||||
}
|
||||
|
||||
constructor(
|
||||
private _commandResolver: ResourceCommandResolver,
|
||||
private _resourceGroupType: ResourceGroupType,
|
||||
private _resourceUri: Uri,
|
||||
private _type: Status,
|
||||
private _useIcons: boolean,
|
||||
private _renameResourceUri?: Uri
|
||||
private _renameResourceUri?: Uri,
|
||||
) { }
|
||||
|
||||
async open(): Promise<void> {
|
||||
const command = this.command;
|
||||
await commands.executeCommand<void>(command.command, ...(command.arguments || []));
|
||||
}
|
||||
|
||||
async openFile(): Promise<void> {
|
||||
const command = this._commandResolver.resolveFileCommand(this);
|
||||
await commands.executeCommand<void>(command.command, ...(command.arguments || []));
|
||||
}
|
||||
|
||||
async openChange(): Promise<void> {
|
||||
const command = this._commandResolver.resolveChangeCommand(this);
|
||||
await commands.executeCommand<void>(command.command, ...(command.arguments || []));
|
||||
}
|
||||
}
|
||||
|
||||
export const enum Operation {
|
||||
@ -292,6 +316,7 @@ export const enum Operation {
|
||||
Fetch = 'Fetch',
|
||||
Pull = 'Pull',
|
||||
Push = 'Push',
|
||||
CherryPick = 'CherryPick',
|
||||
Sync = 'Sync',
|
||||
Show = 'Show',
|
||||
Stage = 'Stage',
|
||||
@ -315,6 +340,8 @@ export const enum Operation {
|
||||
Blame = 'Blame',
|
||||
Log = 'Log',
|
||||
LogFile = 'LogFile',
|
||||
|
||||
Move = 'Move'
|
||||
}
|
||||
|
||||
function isReadOnly(operation: Operation): boolean {
|
||||
@ -550,6 +577,142 @@ class DotGitWatcher implements IFileWatcher {
|
||||
}
|
||||
}
|
||||
|
||||
class ResourceCommandResolver {
|
||||
|
||||
constructor(private repository: Repository) { }
|
||||
|
||||
resolveDefaultCommand(resource: Resource): Command {
|
||||
const config = workspace.getConfiguration('git', Uri.file(this.repository.root));
|
||||
const openDiffOnClick = config.get<boolean>('openDiffOnClick', true);
|
||||
return openDiffOnClick ? this.resolveChangeCommand(resource) : this.resolveFileCommand(resource);
|
||||
}
|
||||
|
||||
resolveFileCommand(resource: Resource): Command {
|
||||
return {
|
||||
command: 'vscode.open',
|
||||
title: localize('open', "Open"),
|
||||
arguments: [resource.resourceUri]
|
||||
};
|
||||
}
|
||||
|
||||
resolveChangeCommand(resource: Resource): Command {
|
||||
const title = this.getTitle(resource);
|
||||
|
||||
if (!resource.leftUri) {
|
||||
return {
|
||||
command: 'vscode.open',
|
||||
title: localize('open', "Open"),
|
||||
arguments: [resource.rightUri, { override: resource.type === Status.BOTH_MODIFIED ? false : undefined }, title]
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
command: 'vscode.diff',
|
||||
title: localize('open', "Open"),
|
||||
arguments: [resource.leftUri, resource.rightUri, title]
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
getResources(resource: Resource): [Uri | undefined, Uri] {
|
||||
for (const submodule of this.repository.submodules) {
|
||||
if (path.join(this.repository.root, submodule.path) === resource.resourceUri.fsPath) {
|
||||
return [undefined, toGitUri(resource.resourceUri, resource.resourceGroupType === ResourceGroupType.Index ? 'index' : 'wt', { submoduleOf: this.repository.root })];
|
||||
}
|
||||
}
|
||||
|
||||
return [this.getLeftResource(resource), this.getRightResource(resource)];
|
||||
}
|
||||
|
||||
private getLeftResource(resource: Resource): Uri | undefined {
|
||||
switch (resource.type) {
|
||||
case Status.INDEX_MODIFIED:
|
||||
case Status.INDEX_RENAMED:
|
||||
case Status.INDEX_ADDED:
|
||||
return toGitUri(resource.original, 'HEAD');
|
||||
|
||||
case Status.MODIFIED:
|
||||
case Status.UNTRACKED:
|
||||
return toGitUri(resource.resourceUri, '~');
|
||||
|
||||
case Status.DELETED_BY_US:
|
||||
case Status.DELETED_BY_THEM:
|
||||
return toGitUri(resource.resourceUri, '~1');
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private getRightResource(resource: Resource): Uri {
|
||||
switch (resource.type) {
|
||||
case Status.INDEX_MODIFIED:
|
||||
case Status.INDEX_ADDED:
|
||||
case Status.INDEX_COPIED:
|
||||
case Status.INDEX_RENAMED:
|
||||
return toGitUri(resource.resourceUri, '');
|
||||
|
||||
case Status.INDEX_DELETED:
|
||||
case Status.DELETED:
|
||||
return toGitUri(resource.resourceUri, 'HEAD');
|
||||
|
||||
case Status.DELETED_BY_US:
|
||||
return toGitUri(resource.resourceUri, '~3');
|
||||
|
||||
case Status.DELETED_BY_THEM:
|
||||
return toGitUri(resource.resourceUri, '~2');
|
||||
|
||||
case Status.MODIFIED:
|
||||
case Status.UNTRACKED:
|
||||
case Status.IGNORED:
|
||||
case Status.INTENT_TO_ADD:
|
||||
const uriString = resource.resourceUri.toString();
|
||||
const [indexStatus] = this.repository.indexGroup.resourceStates.filter(r => r.resourceUri.toString() === uriString);
|
||||
|
||||
if (indexStatus && indexStatus.renameResourceUri) {
|
||||
return indexStatus.renameResourceUri;
|
||||
}
|
||||
|
||||
return resource.resourceUri;
|
||||
|
||||
case Status.BOTH_ADDED:
|
||||
case Status.BOTH_MODIFIED:
|
||||
return resource.resourceUri;
|
||||
}
|
||||
|
||||
throw new Error('Should never happen');
|
||||
}
|
||||
|
||||
private getTitle(resource: Resource): string {
|
||||
const basename = path.basename(resource.resourceUri.fsPath);
|
||||
|
||||
switch (resource.type) {
|
||||
case Status.INDEX_MODIFIED:
|
||||
case Status.INDEX_RENAMED:
|
||||
case Status.INDEX_ADDED:
|
||||
return localize('git.title.index', '{0} (Index)', basename);
|
||||
|
||||
case Status.MODIFIED:
|
||||
case Status.BOTH_ADDED:
|
||||
case Status.BOTH_MODIFIED:
|
||||
return localize('git.title.workingTree', '{0} (Working Tree)', basename);
|
||||
|
||||
case Status.INDEX_DELETED:
|
||||
case Status.DELETED:
|
||||
return localize('git.title.deleted', '{0} (Deleted)', basename);
|
||||
|
||||
case Status.DELETED_BY_US:
|
||||
return localize('git.title.theirs', '{0} (Theirs)', basename);
|
||||
|
||||
case Status.DELETED_BY_THEM:
|
||||
return localize('git.title.ours', '{0} (Ours)', basename);
|
||||
|
||||
case Status.UNTRACKED:
|
||||
return localize('git.title.untracked', '{0} (Untracked)', basename);
|
||||
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class Repository implements Disposable {
|
||||
|
||||
private _onDidChangeRepository = new EventEmitter<Uri>();
|
||||
@ -680,6 +843,7 @@ export class Repository implements Disposable {
|
||||
private isRepositoryHuge = false;
|
||||
private didWarnAboutLimit = false;
|
||||
|
||||
private resourceCommandResolver = new ResourceCommandResolver(this);
|
||||
private disposables: Disposable[] = [];
|
||||
|
||||
constructor(
|
||||
@ -746,11 +910,12 @@ export class Repository implements Disposable {
|
||||
onConfigListener(updateIndexGroupVisibility, this, this.disposables);
|
||||
updateIndexGroupVisibility();
|
||||
|
||||
const onConfigListenerForBranchSortOrder = filterEvent(workspace.onDidChangeConfiguration, e => e.affectsConfiguration('git.branchSortOrder', root));
|
||||
onConfigListenerForBranchSortOrder(this.updateModelState, this, this.disposables);
|
||||
|
||||
const onConfigListenerForUntracked = filterEvent(workspace.onDidChangeConfiguration, e => e.affectsConfiguration('git.untrackedChanges', root));
|
||||
onConfigListenerForUntracked(this.updateModelState, this, this.disposables);
|
||||
filterEvent(workspace.onDidChangeConfiguration, e =>
|
||||
e.affectsConfiguration('git.branchSortOrder', root)
|
||||
|| e.affectsConfiguration('git.untrackedChanges', root)
|
||||
|| e.affectsConfiguration('git.ignoreSubmodules', root)
|
||||
|| e.affectsConfiguration('git.openDiffOnClick', root)
|
||||
)(this.updateModelState, this, this.disposables);
|
||||
|
||||
const updateInputBoxVisibility = () => {
|
||||
const config = workspace.getConfiguration('git', root);
|
||||
@ -864,6 +1029,12 @@ export class Repository implements Disposable {
|
||||
return;
|
||||
}
|
||||
|
||||
const path = uri.path;
|
||||
|
||||
if (this.mergeGroup.resourceStates.some(r => r.resourceUri.path === path)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return toGitUri(uri, '', { replaceFileExtension: true });
|
||||
}
|
||||
|
||||
@ -976,7 +1147,7 @@ export class Repository implements Disposable {
|
||||
await this.run(Operation.RevertFiles, () => this.repository.revert('HEAD', resources.map(r => r.fsPath)));
|
||||
}
|
||||
|
||||
async commit(message: string, opts: CommitOptions = Object.create(null)): Promise<void> {
|
||||
async commit(message: string | undefined, opts: CommitOptions = Object.create(null)): Promise<void> {
|
||||
if (this.rebaseCommit) {
|
||||
await this.run(Operation.RebaseContinue, async () => {
|
||||
if (opts.all) {
|
||||
@ -1053,6 +1224,14 @@ export class Repository implements Disposable {
|
||||
await this.run(Operation.RenameBranch, () => this.repository.renameBranch(name));
|
||||
}
|
||||
|
||||
async cherryPick(commitHash: string): Promise<void> {
|
||||
await this.run(Operation.CherryPick, () => this.repository.cherryPick(commitHash));
|
||||
}
|
||||
|
||||
async move(from: string, to: string): Promise<void> {
|
||||
await this.run(Operation.Move, () => this.repository.move(from, to));
|
||||
}
|
||||
|
||||
async getBranch(name: string): Promise<Branch> {
|
||||
return await this.run(Operation.GetBranch, () => this.repository.getBranch(name));
|
||||
}
|
||||
@ -1081,12 +1260,12 @@ export class Repository implements Disposable {
|
||||
await this.run(Operation.DeleteTag, () => this.repository.deleteTag(name));
|
||||
}
|
||||
|
||||
async checkout(treeish: string): Promise<void> {
|
||||
await this.run(Operation.Checkout, () => this.repository.checkout(treeish, []));
|
||||
async checkout(treeish: string, opts?: { detached?: boolean }): Promise<void> {
|
||||
await this.run(Operation.Checkout, () => this.repository.checkout(treeish, [], opts));
|
||||
}
|
||||
|
||||
async checkoutTracking(treeish: string): Promise<void> {
|
||||
await this.run(Operation.CheckoutTracking, () => this.repository.checkout(treeish, [], { track: true }));
|
||||
async checkoutTracking(treeish: string, opts: { detached?: boolean } = {}): Promise<void> {
|
||||
await this.run(Operation.CheckoutTracking, () => this.repository.checkout(treeish, [], { ...opts, track: true }));
|
||||
}
|
||||
|
||||
async findTrackingBranches(upstreamRef: string): Promise<Branch[]> {
|
||||
@ -1119,21 +1298,31 @@ export class Repository implements Disposable {
|
||||
|
||||
@throttle
|
||||
async fetchDefault(options: { silent?: boolean } = {}): Promise<void> {
|
||||
await this.run(Operation.Fetch, () => this.repository.fetch(options));
|
||||
await this._fetch({ silent: options.silent });
|
||||
}
|
||||
|
||||
@throttle
|
||||
async fetchPrune(): Promise<void> {
|
||||
await this.run(Operation.Fetch, () => this.repository.fetch({ prune: true }));
|
||||
await this._fetch({ prune: true });
|
||||
}
|
||||
|
||||
@throttle
|
||||
async fetchAll(): Promise<void> {
|
||||
await this.run(Operation.Fetch, () => this.repository.fetch({ all: true }));
|
||||
await this._fetch({ all: true });
|
||||
}
|
||||
|
||||
async fetch(remote?: string, ref?: string, depth?: number): Promise<void> {
|
||||
await this.run(Operation.Fetch, () => this.repository.fetch({ remote, ref, depth }));
|
||||
await this._fetch({ remote, ref, depth });
|
||||
}
|
||||
|
||||
private async _fetch(options: { remote?: string, ref?: string, all?: boolean, prune?: boolean, depth?: number, silent?: boolean } = {}): Promise<void> {
|
||||
if (!options.prune) {
|
||||
const config = workspace.getConfiguration('git', Uri.file(this.root));
|
||||
const prune = config.get<boolean>('pruneOnFetch');
|
||||
options.prune = prune;
|
||||
}
|
||||
|
||||
await this.run(Operation.Fetch, async () => this.repository.fetch(options));
|
||||
}
|
||||
|
||||
@throttle
|
||||
@ -1169,11 +1358,12 @@ export class Repository implements Disposable {
|
||||
const fetchOnPull = config.get<boolean>('fetchOnPull');
|
||||
const tags = config.get<boolean>('pullTags');
|
||||
|
||||
// When fetchOnPull is enabled, fetch all branches when pulling
|
||||
if (fetchOnPull) {
|
||||
await this.repository.pull(rebase, undefined, undefined, { unshallow, tags });
|
||||
} else {
|
||||
await this.repository.pull(rebase, remote, branch, { unshallow, tags });
|
||||
await this.repository.fetch({ all: true });
|
||||
}
|
||||
|
||||
await this.repository.pull(rebase, remote, branch, { unshallow, tags });
|
||||
});
|
||||
});
|
||||
}
|
||||
@ -1199,6 +1389,10 @@ export class Repository implements Disposable {
|
||||
await this.run(Operation.Push, () => this._push(remote, undefined, false, true, forcePushMode));
|
||||
}
|
||||
|
||||
async pushTags(remote?: string, forcePushMode?: ForcePushMode): Promise<void> {
|
||||
await this.run(Operation.Push, () => this._push(remote, undefined, false, false, forcePushMode, true));
|
||||
}
|
||||
|
||||
async blame(path: string): Promise<string> {
|
||||
return await this.run(Operation.Blame, () => this.repository.blame(path));
|
||||
}
|
||||
@ -1229,11 +1423,18 @@ export class Repository implements Disposable {
|
||||
const config = workspace.getConfiguration('git', Uri.file(this.root));
|
||||
const fetchOnPull = config.get<boolean>('fetchOnPull');
|
||||
const tags = config.get<boolean>('pullTags');
|
||||
const followTags = config.get<boolean>('followTagsWhenSync');
|
||||
const supportCancellation = config.get<boolean>('supportCancellation');
|
||||
|
||||
const fn = fetchOnPull
|
||||
? async (cancellationToken?: CancellationToken) => await this.repository.pull(rebase, undefined, undefined, { tags, cancellationToken })
|
||||
: async (cancellationToken?: CancellationToken) => await this.repository.pull(rebase, remoteName, pullBranch, { tags, cancellationToken });
|
||||
const fn = async (cancellationToken?: CancellationToken) => {
|
||||
// When fetchOnPull is enabled, fetch all branches when pulling
|
||||
if (fetchOnPull) {
|
||||
await this.repository.fetch({ all: true, cancellationToken });
|
||||
}
|
||||
|
||||
await this.repository.pull(rebase, remoteName, pullBranch, { tags, cancellationToken });
|
||||
};
|
||||
|
||||
|
||||
if (supportCancellation) {
|
||||
const opts: ProgressOptions = {
|
||||
@ -1256,7 +1457,7 @@ export class Repository implements Disposable {
|
||||
const shouldPush = this.HEAD && (typeof this.HEAD.ahead === 'number' ? this.HEAD.ahead > 0 : true);
|
||||
|
||||
if (shouldPush) {
|
||||
await this._push(remoteName, pushBranch);
|
||||
await this._push(remoteName, pushBranch, false, followTags);
|
||||
}
|
||||
});
|
||||
});
|
||||
@ -1418,9 +1619,9 @@ export class Repository implements Disposable {
|
||||
return ignored;
|
||||
}
|
||||
|
||||
private async _push(remote?: string, refspec?: string, setUpstream: boolean = false, tags = false, forcePushMode?: ForcePushMode): Promise<void> {
|
||||
private async _push(remote?: string, refspec?: string, setUpstream: boolean = false, followTags = false, forcePushMode?: ForcePushMode, tags = false): Promise<void> {
|
||||
try {
|
||||
await this.repository.push(remote, refspec, setUpstream, tags, forcePushMode);
|
||||
await this.repository.push(remote, refspec, setUpstream, followTags, forcePushMode, tags);
|
||||
} catch (err) {
|
||||
if (!remote || !refspec) {
|
||||
throw err;
|
||||
@ -1518,9 +1719,12 @@ export class Repository implements Disposable {
|
||||
|
||||
@throttle
|
||||
private async updateModelState(): Promise<void> {
|
||||
const { status, didHitLimit } = await this.repository.getStatus();
|
||||
const config = workspace.getConfiguration('git');
|
||||
const scopedConfig = workspace.getConfiguration('git', Uri.file(this.repository.root));
|
||||
const ignoreSubmodules = scopedConfig.get<boolean>('ignoreSubmodules');
|
||||
|
||||
const { status, didHitLimit } = await this.repository.getStatus({ ignoreSubmodules });
|
||||
|
||||
const config = workspace.getConfiguration('git');
|
||||
const shouldIgnore = config.get<boolean>('ignoreLimitWarning') === true;
|
||||
const useIcons = !config.get<boolean>('decorations.enabled', true);
|
||||
this.isRepositoryHuge = didHitLimit;
|
||||
@ -1595,36 +1799,36 @@ export class Repository implements Disposable {
|
||||
|
||||
switch (raw.x + raw.y) {
|
||||
case '??': switch (untrackedChanges) {
|
||||
case 'mixed': return workingTree.push(new Resource(ResourceGroupType.WorkingTree, uri, Status.UNTRACKED, useIcons));
|
||||
case 'separate': return untracked.push(new Resource(ResourceGroupType.Untracked, uri, Status.UNTRACKED, useIcons));
|
||||
case 'mixed': return workingTree.push(new Resource(this.resourceCommandResolver, ResourceGroupType.WorkingTree, uri, Status.UNTRACKED, useIcons));
|
||||
case 'separate': return untracked.push(new Resource(this.resourceCommandResolver, ResourceGroupType.Untracked, uri, Status.UNTRACKED, useIcons));
|
||||
default: return undefined;
|
||||
}
|
||||
case '!!': switch (untrackedChanges) {
|
||||
case 'mixed': return workingTree.push(new Resource(ResourceGroupType.WorkingTree, uri, Status.IGNORED, useIcons));
|
||||
case 'separate': return untracked.push(new Resource(ResourceGroupType.Untracked, uri, Status.IGNORED, useIcons));
|
||||
case 'mixed': return workingTree.push(new Resource(this.resourceCommandResolver, ResourceGroupType.WorkingTree, uri, Status.IGNORED, useIcons));
|
||||
case 'separate': return untracked.push(new Resource(this.resourceCommandResolver, ResourceGroupType.Untracked, uri, Status.IGNORED, useIcons));
|
||||
default: return undefined;
|
||||
}
|
||||
case 'DD': return merge.push(new Resource(ResourceGroupType.Merge, uri, Status.BOTH_DELETED, useIcons));
|
||||
case 'AU': return merge.push(new Resource(ResourceGroupType.Merge, uri, Status.ADDED_BY_US, useIcons));
|
||||
case 'UD': return merge.push(new Resource(ResourceGroupType.Merge, uri, Status.DELETED_BY_THEM, useIcons));
|
||||
case 'UA': return merge.push(new Resource(ResourceGroupType.Merge, uri, Status.ADDED_BY_THEM, useIcons));
|
||||
case 'DU': return merge.push(new Resource(ResourceGroupType.Merge, uri, Status.DELETED_BY_US, useIcons));
|
||||
case 'AA': return merge.push(new Resource(ResourceGroupType.Merge, uri, Status.BOTH_ADDED, useIcons));
|
||||
case 'UU': return merge.push(new Resource(ResourceGroupType.Merge, uri, Status.BOTH_MODIFIED, useIcons));
|
||||
case 'DD': return merge.push(new Resource(this.resourceCommandResolver, ResourceGroupType.Merge, uri, Status.BOTH_DELETED, useIcons));
|
||||
case 'AU': return merge.push(new Resource(this.resourceCommandResolver, ResourceGroupType.Merge, uri, Status.ADDED_BY_US, useIcons));
|
||||
case 'UD': return merge.push(new Resource(this.resourceCommandResolver, ResourceGroupType.Merge, uri, Status.DELETED_BY_THEM, useIcons));
|
||||
case 'UA': return merge.push(new Resource(this.resourceCommandResolver, ResourceGroupType.Merge, uri, Status.ADDED_BY_THEM, useIcons));
|
||||
case 'DU': return merge.push(new Resource(this.resourceCommandResolver, ResourceGroupType.Merge, uri, Status.DELETED_BY_US, useIcons));
|
||||
case 'AA': return merge.push(new Resource(this.resourceCommandResolver, ResourceGroupType.Merge, uri, Status.BOTH_ADDED, useIcons));
|
||||
case 'UU': return merge.push(new Resource(this.resourceCommandResolver, ResourceGroupType.Merge, uri, Status.BOTH_MODIFIED, useIcons));
|
||||
}
|
||||
|
||||
switch (raw.x) {
|
||||
case 'M': index.push(new Resource(ResourceGroupType.Index, uri, Status.INDEX_MODIFIED, useIcons)); break;
|
||||
case 'A': index.push(new Resource(ResourceGroupType.Index, uri, Status.INDEX_ADDED, useIcons)); break;
|
||||
case 'D': index.push(new Resource(ResourceGroupType.Index, uri, Status.INDEX_DELETED, useIcons)); break;
|
||||
case 'R': index.push(new Resource(ResourceGroupType.Index, uri, Status.INDEX_RENAMED, useIcons, renameUri)); break;
|
||||
case 'C': index.push(new Resource(ResourceGroupType.Index, uri, Status.INDEX_COPIED, useIcons, renameUri)); break;
|
||||
case 'M': index.push(new Resource(this.resourceCommandResolver, ResourceGroupType.Index, uri, Status.INDEX_MODIFIED, useIcons)); break;
|
||||
case 'A': index.push(new Resource(this.resourceCommandResolver, ResourceGroupType.Index, uri, Status.INDEX_ADDED, useIcons)); break;
|
||||
case 'D': index.push(new Resource(this.resourceCommandResolver, ResourceGroupType.Index, uri, Status.INDEX_DELETED, useIcons)); break;
|
||||
case 'R': index.push(new Resource(this.resourceCommandResolver, ResourceGroupType.Index, uri, Status.INDEX_RENAMED, useIcons, renameUri)); break;
|
||||
case 'C': index.push(new Resource(this.resourceCommandResolver, ResourceGroupType.Index, uri, Status.INDEX_COPIED, useIcons, renameUri)); break;
|
||||
}
|
||||
|
||||
switch (raw.y) {
|
||||
case 'M': workingTree.push(new Resource(ResourceGroupType.WorkingTree, uri, Status.MODIFIED, useIcons, renameUri)); break;
|
||||
case 'D': workingTree.push(new Resource(ResourceGroupType.WorkingTree, uri, Status.DELETED, useIcons, renameUri)); break;
|
||||
case 'A': workingTree.push(new Resource(ResourceGroupType.WorkingTree, uri, Status.INTENT_TO_ADD, useIcons, renameUri)); break;
|
||||
case 'M': workingTree.push(new Resource(this.resourceCommandResolver, ResourceGroupType.WorkingTree, uri, Status.MODIFIED, useIcons, renameUri)); break;
|
||||
case 'D': workingTree.push(new Resource(this.resourceCommandResolver, ResourceGroupType.WorkingTree, uri, Status.DELETED, useIcons, renameUri)); break;
|
||||
case 'A': workingTree.push(new Resource(this.resourceCommandResolver, ResourceGroupType.WorkingTree, uri, Status.INTENT_TO_ADD, useIcons, renameUri)); break;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
|
@ -200,7 +200,7 @@ export class GitTimelineProvider implements TimelineProvider {
|
||||
if (working) {
|
||||
const date = new Date();
|
||||
|
||||
const item = new GitTimelineItem('', index ? '~' : 'HEAD', localize('git.timeline.uncommitedChanges', 'Uncommited Changes'), date.getTime(), 'working', 'git:file:working');
|
||||
const item = new GitTimelineItem('', index ? '~' : 'HEAD', localize('git.timeline.uncommitedChanges', 'Uncommitted Changes'), date.getTime(), 'working', 'git:file:working');
|
||||
// TODO@eamodio: Replace with a better icon -- reflecting its status maybe?
|
||||
item.iconPath = new (ThemeIcon as any)('git-commit');
|
||||
item.description = '';
|
||||
|
Reference in New Issue
Block a user