Merge VS Code 1.30.1 (#4092)

This commit is contained in:
Matt Irvine
2019-02-21 17:17:23 -08:00
committed by GitHub
parent a764a481f3
commit 826856c390
11465 changed files with 119542 additions and 255338 deletions

View File

@@ -1,5 +1,8 @@
src/**
test/**
out/test/**
out/**
tsconfig.json
build/**
build/**
extension.webpack.config.js
cgmanifest.json
yarn.lock

View File

@@ -1,52 +0,0 @@
// ATTENTION - THIS DIRECTORY CONTAINS THIRD PARTY OPEN SOURCE MATERIALS:
[
{
"name": "textmate/git.tmbundle",
"version": "0.0.0",
"license": "MIT",
"repositoryURL": "https://github.com/textmate/git.tmbundle",
"licenseDetail": [
"Copyright (c) 2008 Tim Harper",
"",
"Permission is hereby granted, free of charge, to any person obtaining",
"a copy of this software and associated documentation files (the\"",
"Software\"), to deal in the Software without restriction, including",
"without limitation the rights to use, copy, modify, merge, publish,",
"distribute, sublicense, and/or sell copies of the Software, and to",
"permit persons to whom the Software is furnished to do so, subject to",
"the following conditions:",
"",
"The above copyright notice and this permission notice shall be",
"included in all copies or substantial portions of the Software.",
"",
"THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,",
"EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF",
"MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND",
"NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE",
"LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION",
"OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION",
"WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE."
]
},
{
"name": "textmate/diff.tmbundle",
"version": "0.0.0",
"license": "TextMate Bundle License",
"repositoryURL": "https://github.com/textmate/diff.tmbundle",
"licenseDetail": [
"Copyright (c) textmate-diff.tmbundle project authors",
"",
"If not otherwise specified (see below), files in this repository fall under the following license:",
"",
"Permission to copy, use, modify, sell and distribute this",
"software is granted. This software is provided \"as is\" without",
"express or implied warranty, and with no claim as to its",
"suitability for any purpose.",
"",
"An exception is made for files in readable text which contain their own license information,",
"or files where an accompanying file exists (in the same directory) with a \"-license\" suffix added",
"to the base-name name of the original file, and an extension of txt, html, or similar. For example",
"\"tidy\" is accompanied by \"tidy-license.txt\"."
]
}
]

View File

@@ -4,4 +4,17 @@
## Features
See [Git support in VS Code](https://code.visualstudio.com/docs/editor/versioncontrol#_git-support) to learn about the features of this extension.
See [Git support in VS Code](https://code.visualstudio.com/docs/editor/versioncontrol#_git-support) to learn about the features of this extension.
## API
The Git extension exposes an API, reachable by any other extension.
1. Copy `src/api/git.d.ts` to your extension's sources;
2. Include `git.d.ts` in your extension's compilation.
3. Get a hold of the API with the following snippet:
```ts
const gitExtension = vscode.extensions.getExtension<GitExtension>('vscode.git').exports;
const git = gitExtension.getAPI(1);
```

View File

@@ -0,0 +1,66 @@
{
"registrations": [
{
"component": {
"type": "git",
"git": {
"name": "textmate/git.tmbundle",
"repositoryUrl": "https://github.com/textmate/git.tmbundle",
"commitHash": "93897a78c6e52bef13dadc0d4091d203c5facb40"
}
},
"licenseDetail": [
"Copyright (c) 2008 Tim Harper",
"",
"Permission is hereby granted, free of charge, to any person obtaining",
"a copy of this software and associated documentation files (the\"",
"Software\"), to deal in the Software without restriction, including",
"without limitation the rights to use, copy, modify, merge, publish,",
"distribute, sublicense, and/or sell copies of the Software, and to",
"permit persons to whom the Software is furnished to do so, subject to",
"the following conditions:",
"",
"The above copyright notice and this permission notice shall be",
"included in all copies or substantial portions of the Software.",
"",
"THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,",
"EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF",
"MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND",
"NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE",
"LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION",
"OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION",
"WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE."
],
"license": "MIT",
"version": "0.0.0"
},
{
"component": {
"type": "git",
"git": {
"name": "textmate/diff.tmbundle",
"repositoryUrl": "https://github.com/textmate/diff.tmbundle",
"commitHash": "0593bb775eab1824af97ef2172fd38822abd97d7"
}
},
"licenseDetail": [
"Copyright (c) textmate-diff.tmbundle project authors",
"",
"If not otherwise specified (see below), files in this repository fall under the following license:",
"",
"Permission to copy, use, modify, sell and distribute this",
"software is granted. This software is provided \"as is\" without",
"express or implied warranty, and with no claim as to its",
"suitability for any purpose.",
"",
"An exception is made for files in readable text which contain their own license information,",
"or files where an accompanying file exists (in the same directory) with a \"-license\" suffix added",
"to the base-name name of the original file, and an extension of txt, html, or similar. For example",
"\"tidy\" is accompanied by \"tidy-license.txt\"."
],
"license": "TextMate Bundle License",
"version": "0.0.0"
}
],
"version": 1
}

View File

@@ -0,0 +1,18 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
//@ts-check
'use strict';
const withDefaults = require('../shared.webpack.config');
module.exports = withDefaults({
context: __dirname,
entry: {
main: './src/main.ts',
['askpass-main']: './src/askpass-main.ts'
}
});

View File

@@ -190,6 +190,11 @@
"title": "%command.commitStaged%",
"category": "Git"
},
{
"command": "git.commitEmpty",
"title": "%command.commitEmpty%",
"category": "Git"
},
{
"command": "git.commitStagedSigned",
"title": "%command.commitStagedSigned%",
@@ -215,6 +220,11 @@
"title": "%command.commitAllAmend%",
"category": "Git"
},
{
"command": "git.restoreCommitTemplate",
"title": "%command.restoreCommitTemplate%",
"category": "Git"
},
{
"command": "git.undoCommit",
"title": "%command.undoCommit%",
@@ -255,6 +265,16 @@
"title": "%command.fetch%",
"category": "Git"
},
{
"command": "git.fetchPrune",
"title": "%command.fetchPrune%",
"category": "Git"
},
{
"command": "git.fetchAll",
"title": "%command.fetchAll%",
"category": "Git"
},
{
"command": "git.pull",
"title": "%command.pull%",
@@ -275,16 +295,31 @@
"title": "%command.push%",
"category": "Git"
},
{
"command": "git.pushForce",
"title": "%command.pushForce%",
"category": "Git"
},
{
"command": "git.pushTo",
"title": "%command.pushTo%",
"category": "Git"
},
{
"command": "git.pushToForce",
"title": "%command.pushToForce%",
"category": "Git"
},
{
"command": "git.pushWithTags",
"title": "%command.pushWithTags%",
"category": "Git"
},
{
"command": "git.pushWithTagsForce",
"title": "%command.pushWithTagsForce%",
"category": "Git"
},
{
"command": "git.sync",
"title": "%command.sync%",
@@ -329,6 +364,16 @@
"command": "git.stashPopLatest",
"title": "%command.stashPopLatest%",
"category": "Git"
},
{
"command": "git.stashApply",
"title": "%command.stashApply%",
"category": "Git"
},
{
"command": "git.stashApplyLatest",
"title": "%command.stashApplyLatest%",
"category": "Git"
}
],
"menus": {
@@ -441,6 +486,10 @@
"command": "git.commitAllAmend",
"when": "config.git.enabled && gitOpenRepositoryCount != 0"
},
{
"command": "git.restoreCommitTemplate",
"when": "false"
},
{
"command": "git.undoCommit",
"when": "config.git.enabled && gitOpenRepositoryCount != 0"
@@ -489,18 +538,38 @@
"command": "git.fetch",
"when": "config.git.enabled && gitOpenRepositoryCount != 0"
},
{
"command": "git.fetchPrune",
"when": "config.git.enabled && gitOpenRepositoryCount != 0"
},
{
"command": "git.fetchAll",
"when": "config.git.enabled && gitOpenRepositoryCount != 0"
},
{
"command": "git.push",
"when": "config.git.enabled && gitOpenRepositoryCount != 0"
},
{
"command": "git.pushForce",
"when": "config.git.enabled && config.git.allowForcePush && gitOpenRepositoryCount != 0"
},
{
"command": "git.pushTo",
"when": "config.git.enabled && gitOpenRepositoryCount != 0"
},
{
"command": "git.pushToForce",
"when": "config.git.enabled && config.git.allowForcePush && gitOpenRepositoryCount != 0"
},
{
"command": "git.pushWithTags",
"when": "config.git.enabled && gitOpenRepositoryCount != 0"
},
{
"command": "git.pushWithTagsForce",
"when": "config.git.enabled && config.git.allowForcePush && gitOpenRepositoryCount != 0"
},
{
"command": "git.sync",
"when": "config.git.enabled && gitOpenRepositoryCount != 0"
@@ -536,6 +605,14 @@
{
"command": "git.stashPopLatest",
"when": "config.git.enabled && gitOpenRepositoryCount != 0"
},
{
"command": "git.stashApply",
"when": "config.git.enabled && gitOpenRepositoryCount != 0"
},
{
"command": "git.stashApplyLatest",
"when": "config.git.enabled && gitOpenRepositoryCount != 0"
}
],
"scm/title": [
@@ -664,6 +741,16 @@
"group": "5_stash",
"when": "scmProvider == git"
},
{
"command": "git.stashApply",
"group": "5_stash",
"when": "scmProvider == git"
},
{
"command": "git.stashApplyLatest",
"group": "5_stash",
"when": "scmProvider == git"
},
{
"command": "git.showOutput",
"group": "7_repository",
@@ -737,7 +824,12 @@
},
{
"command": "git.openFile2",
"when": "scmProvider == git && scmResourceGroup == merge && config.git.showInlineOpenFileAction",
"when": "scmProvider == git && scmResourceGroup == merge && config.git.showInlineOpenFileAction && config.git.openDiffOnClick",
"group": "inline0"
},
{
"command": "git.openChange",
"when": "scmProvider == git && scmResourceGroup == merge && config.git.showInlineOpenFileAction && !config.git.openDiffOnClick",
"group": "inline0"
},
{
@@ -767,7 +859,12 @@
},
{
"command": "git.openFile2",
"when": "scmProvider == git && scmResourceGroup == index && config.git.showInlineOpenFileAction",
"when": "scmProvider == git && scmResourceGroup == index && config.git.showInlineOpenFileAction && config.git.openDiffOnClick",
"group": "inline0"
},
{
"command": "git.openChange",
"when": "scmProvider == git && scmResourceGroup == index && config.git.showInlineOpenFileAction && !config.git.openDiffOnClick",
"group": "inline0"
},
{
@@ -807,7 +904,12 @@
},
{
"command": "git.openFile2",
"when": "scmProvider == git && scmResourceGroup == workingTree && config.git.showInlineOpenFileAction",
"when": "scmProvider == git && scmResourceGroup == workingTree && config.git.showInlineOpenFileAction && config.git.openDiffOnClick",
"group": "inline0"
},
{
"command": "git.openChange",
"when": "scmProvider == git && scmResourceGroup == workingTree && config.git.showInlineOpenFileAction && !config.git.openDiffOnClick",
"group": "inline0"
},
{
@@ -885,7 +987,7 @@
"string",
"null"
],
"description": "%config.path%",
"markdownDescription": "%config.path%",
"default": null,
"scope": "application"
},
@@ -900,6 +1002,12 @@
"subFolders",
"openEditors"
],
"enumDescriptions": [
"%config.autoRepositoryDetection.true%",
"%config.autoRepositoryDetection.false%",
"%config.autoRepositoryDetection.subFolders%",
"%config.autoRepositoryDetection.openEditors%"
],
"description": "%config.autoRepositoryDetection%",
"default": true
},
@@ -912,7 +1020,19 @@
"type": "boolean",
"description": "%config.autofetch%",
"default": false,
"tags": ["usesOnlineServices"]
"tags": [
"usesOnlineServices"
]
},
"git.branchValidationRegex": {
"type": "string",
"description": "%config.branchValidationRegex%",
"default": ""
},
"git.branchWhitespaceChar": {
"type": "string",
"description": "%config.branchWhitespaceChar%",
"default": "-"
},
"git.confirmSync": {
"type": "boolean",
@@ -948,7 +1068,7 @@
"%config.checkoutType.tags%",
"%config.checkoutType.remote%"
],
"description": "%config.checkoutType%",
"markdownDescription": "%config.checkoutType%",
"default": "all"
},
"git.ignoreLegacyWarning": {
@@ -983,6 +1103,12 @@
"description": "%config.enableCommitSigning%",
"default": false
},
"git.confirmEmptyCommits": {
"type": "boolean",
"scope": "resource",
"description": "%config.confirmEmptyCommits%",
"default": true
},
"git.decorations.enabled": {
"type": "boolean",
"default": true,
@@ -991,9 +1117,24 @@
"git.promptToSaveFilesBeforeCommit": {
"type": "boolean",
"scope": "resource",
"default": false,
"default": true,
"description": "%config.promptToSaveFilesBeforeCommit%"
},
"git.postCommitCommand": {
"type": "string",
"enum": [
"none",
"push",
"sync"
],
"enumDescriptions": [
"%config.postCommitCommand.none%",
"%config.postCommitCommand.push%",
"%config.postCommitCommand.sync%"
],
"markdownDescription": "%config.postCommitCommand%",
"default": "none"
},
"git.showInlineOpenFileAction": {
"type": "boolean",
"default": true,
@@ -1014,6 +1155,11 @@
"default": "warn",
"description": "%config.inputValidation%"
},
"git.inputValidationLength": {
"type": "number",
"default": 72,
"description": "%config.inputValidationLength%"
},
"git.detectSubmodules": {
"type": "boolean",
"scope": "resource",
@@ -1026,6 +1172,12 @@
"default": 10,
"description": "%config.detectSubmodulesLimit%"
},
"git.alwaysShowStagedChangesResourceGroup": {
"type": "boolean",
"scope": "resource",
"default": false,
"description": "%config.alwaysShowStagedChangesResourceGroup%"
},
"git.alwaysSignOff": {
"type": "boolean",
"scope": "resource",
@@ -1037,6 +1189,50 @@
"default": [],
"scope": "window",
"description": "%config.ignoredRepositories%"
},
"git.scanRepositories": {
"type": "array",
"default": [],
"scope": "resource",
"description": "%config.scanRepositories%"
},
"git.showProgress": {
"type": "boolean",
"description": "%config.showProgress%",
"default": true,
"scope": "resource"
},
"git.rebaseWhenSync": {
"type": "boolean",
"scope": "resource",
"default": false,
"description": "%config.rebaseWhenSync%"
},
"git.fetchOnPull": {
"type": "boolean",
"scope": "resource",
"default": false,
"description": "%config.fetchOnPull%"
},
"git.allowForcePush": {
"type": "boolean",
"default": false,
"description": "%config.allowForcePush%"
},
"git.useForcePushWithLease": {
"type": "boolean",
"default": true,
"description": "%config.useForcePushWithLease%"
},
"git.confirmForcePush": {
"type": "boolean",
"default": true,
"description": "%config.confirmForcePush%"
},
"git.openDiffOnClick": {
"type": "boolean",
"default": true,
"description": "%config.openDiffOnClick%"
}
}
},
@@ -1072,7 +1268,7 @@
"id": "gitDecoration.untrackedResourceForeground",
"description": "%colors.untracked%",
"defaults": {
"light": "#018101",
"light": "#007100",
"dark": "#73C991",
"highContrast": "#73C991"
}
@@ -1082,7 +1278,7 @@
"description": "%colors.ignored%",
"defaults": {
"light": "#8E8E90",
"dark": "#A7A8A9",
"dark": "#8C8C8C",
"highContrast": "#A7A8A9"
}
},
@@ -1187,17 +1383,17 @@
"dependencies": {
"byline": "^5.0.0",
"file-type": "^7.2.0",
"iconv-lite": "0.4.19",
"iconv-lite": "^0.4.24",
"jschardet": "^1.6.0",
"vscode-extension-telemetry": "0.0.18",
"vscode-nls": "^3.2.4",
"vscode-extension-telemetry": "0.1.0",
"vscode-nls": "^4.0.0",
"which": "^1.3.0"
},
"devDependencies": {
"@types/byline": "4.2.31",
"@types/file-type": "^5.2.1",
"@types/mocha": "2.2.43",
"@types/node": "7.0.43",
"@types/node": "^8.10.25",
"@types/which": "^1.0.28",
"mocha": "^3.2.0"
}

View File

@@ -22,11 +22,13 @@
"command.cleanAll": "Discard All Changes",
"command.commit": "Commit",
"command.commitStaged": "Commit Staged",
"command.commitEmpty": "Commit Empty",
"command.commitStagedSigned": "Commit Staged (Signed Off)",
"command.commitStagedAmend": "Commit Staged (Amend)",
"command.commitAll": "Commit All",
"command.commitAllSigned": "Commit All (Signed Off)",
"command.commitAllAmend": "Commit All (Amend)",
"command.restoreCommitTemplate": "Restore Commit Template",
"command.undoCommit": "Undo Last Commit",
"command.checkout": "Checkout to...",
"command.branch": "Create Branch...",
@@ -35,12 +37,17 @@
"command.merge": "Merge Branch...",
"command.createTag": "Create Tag",
"command.fetch": "Fetch",
"command.fetchPrune": "Fetch (Prune)",
"command.fetchAll": "Fetch From All Remotes",
"command.pull": "Pull",
"command.pullRebase": "Pull (Rebase)",
"command.pullFrom": "Pull from...",
"command.push": "Push",
"command.pushForce": "Push (Force)",
"command.pushTo": "Push to...",
"command.pushToForce": "Push to... (Force)",
"command.pushWithTags": "Push With Tags",
"command.pushWithTagsForce": "Push With Tags (Force)",
"command.sync": "Sync",
"command.syncRebase": "Sync (Rebase)",
"command.publish": "Publish Branch",
@@ -50,12 +57,17 @@
"command.stash": "Stash",
"command.stashPop": "Pop Stash...",
"command.stashPopLatest": "Pop Latest Stash",
"command.stashApply": "Apply Stash...",
"command.stashApplyLatest": "Apply Latest Stash",
"config.enabled": "Whether git is enabled.",
"config.path": "Path to the git executable.",
"config.path": "Path and filename of the git executable, e.g. `C:\\Program Files\\Git\\bin\\git.exe` (Windows).",
"config.autoRepositoryDetection": "Configures when repositories should be automatically detected.",
"config.autoRepositoryDetection.true": "Scan for both subfolders of the current opened folder and parent folders of open files.",
"config.autoRepositoryDetection.false": "Disable automatic repository scanning.",
"config.autoRepositoryDetection.subFolders": "Scan for subfolders of the currently opened folder.",
"config.autoRepositoryDetection.openEditors": "Scan for parent folders of open files.",
"config.autorefresh": "Whether auto refreshing is enabled.",
"config.autofetch": "Whether auto fetching is enabled.",
"config.enableLongCommitWarning": "Whether long commit messages should be warned about.",
"config.autofetch": "When enabled, commits will automatically be fetched from the default remote of the current Git repository.",
"config.confirmSync": "Confirm before synchronizing git repositories.",
"config.countBadge": "Controls the git badge counter.",
"config.countBadge.all": "Count all changes.",
@@ -66,6 +78,8 @@
"config.checkoutType.local": "Show only local branches.",
"config.checkoutType.tags": "Show only tags.",
"config.checkoutType.remote": "Show only 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.",
"config.ignoreMissingGitWarning": "Ignores the warning when Git is missing.",
"config.ignoreLimitWarning": "Ignores the warning when there are too many changes in a repository.",
@@ -75,14 +89,29 @@
"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.promptToSaveFilesBeforeCommit": "Controls whether Git should check for unsaved files before committing.",
"config.postCommitCommand": "Runs a git command after a successful commit.",
"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.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.",
"config.inputValidationLength": "Controls the commit message length threshold for showing a warning.",
"config.detectSubmodules": "Controls whether to automatically detect git submodules.",
"colors.added": "Color for added resources.",
"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.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.",
"config.rebaseWhenSync": "Force git to use rebase when running the sync command.",
"config.confirmEmptyCommits": "Always confirm the creation of empty commits.",
"config.fetchOnPull": "Fetch all branches when pulling or just the current one.",
"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.",
"config.confirmForcePush": "Controls whether to ask for confirmation before force-pushing.",
"config.openDiffOnClick": "Controls whether the diff editor should be opened when clicking a change. Otherwise the regular editor will be opened.",
"colors.added": "Color for added resources.",
"colors.modified": "Color for modified resources.",
"colors.deleted": "Color for deleted resources.",
"colors.untracked": "Color for untracked resources.",

View File

@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><polygon points="5.382,13 2.382,7 6.618,7 7,7.764 9.382,3 13.618,3 8.618,13" fill="#F6F6F6"/><path d="M12 4l-4 8h-2l-2-4h2l1 2 3-6h2z" fill="#424242"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="-2 -2 16 16" enable-background="new -2 -2 16 16"><polygon fill="#424242" points="9,0 4.5,9 3,6 0,6 3,12 6,12 12,0"/></svg>

Before

Width:  |  Height:  |  Size: 220 B

After

Width:  |  Height:  |  Size: 194 B

View File

@@ -1,65 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { Model } from './model';
import { Repository as ModelRepository } from './repository';
import { Uri, SourceControlInputBox } from 'vscode';
export interface InputBox {
value: string;
}
export class InputBoxImpl implements InputBox {
set value(value: string) { this.inputBox.value = value; }
get value(): string { return this.inputBox.value; }
constructor(private inputBox: SourceControlInputBox) { }
}
export interface Repository {
readonly rootUri: Uri;
readonly inputBox: InputBox;
}
export class RepositoryImpl implements Repository {
readonly rootUri: Uri;
readonly inputBox: InputBox;
constructor(repository: ModelRepository) {
this.rootUri = Uri.file(repository.root);
this.inputBox = new InputBoxImpl(repository.inputBox);
}
}
export interface API {
getRepositories(): Promise<Repository[]>;
getGitPath(): Promise<string>;
}
export class APIImpl implements API {
constructor(private model: Model) { }
async getGitPath(): Promise<string> {
return this.model.git.path;
}
async getRepositories(): Promise<Repository[]> {
return this.model.repositories.map(repository => new RepositoryImpl(repository));
}
}
export class NoopAPIImpl implements API {
async getGitPath(): Promise<string> {
throw new Error('Git model not found');
}
async getRepositories(): Promise<Repository[]> {
throw new Error('Git model not found');
}
}

View File

@@ -0,0 +1,208 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Model } from '../model';
import { Repository as BaseRepository, Resource } from '../repository';
import { InputBox, Git, API, Repository, Remote, RepositoryState, Branch, Ref, Submodule, Commit, Change, RepositoryUIState, Status } from './git';
import { Event, SourceControlInputBox, Uri, SourceControl } from 'vscode';
import { mapEvent } from '../util';
class ApiInputBox implements InputBox {
set value(value: string) { this._inputBox.value = value; }
get value(): string { return this._inputBox.value; }
constructor(private _inputBox: SourceControlInputBox) { }
}
export class ApiChange implements Change {
get uri(): Uri { return this.resource.resourceUri; }
get originalUri(): Uri { return this.resource.original; }
get renameUri(): Uri | undefined { return this.resource.renameResourceUri; }
get status(): Status { return this.resource.type; }
constructor(private readonly resource: Resource) { }
}
export class ApiRepositoryState implements RepositoryState {
get HEAD(): Branch | undefined { return this._repository.HEAD; }
get refs(): Ref[] { return [...this._repository.refs]; }
get remotes(): Remote[] { return [...this._repository.remotes]; }
get submodules(): Submodule[] { return [...this._repository.submodules]; }
get rebaseCommit(): Commit | undefined { return this._repository.rebaseCommit; }
get mergeChanges(): Change[] { return this._repository.mergeGroup.resourceStates.map(r => new ApiChange(r)); }
get indexChanges(): Change[] { return this._repository.indexGroup.resourceStates.map(r => new ApiChange(r)); }
get workingTreeChanges(): Change[] { return this._repository.workingTreeGroup.resourceStates.map(r => new ApiChange(r)); }
readonly onDidChange: Event<void> = this._repository.onDidRunGitStatus;
constructor(private _repository: BaseRepository) { }
}
export class ApiRepositoryUIState implements RepositoryUIState {
get selected(): boolean { return this._sourceControl.selected; }
readonly onDidChange: Event<void> = mapEvent<boolean, void>(this._sourceControl.onDidChangeSelection, () => null);
constructor(private _sourceControl: SourceControl) { }
}
export class ApiRepository implements Repository {
readonly rootUri: Uri = Uri.file(this._repository.root);
readonly inputBox: InputBox = new ApiInputBox(this._repository.inputBox);
readonly state: RepositoryState = new ApiRepositoryState(this._repository);
readonly ui: RepositoryUIState = new ApiRepositoryUIState(this._repository.sourceControl);
constructor(private _repository: BaseRepository) { }
apply(patch: string, reverse?: boolean): Promise<void> {
return this._repository.apply(patch, reverse);
}
getConfigs(): Promise<{ key: string; value: string; }[]> {
return this._repository.getConfigs();
}
getConfig(key: string): Promise<string> {
return this._repository.getConfig(key);
}
setConfig(key: string, value: string): Promise<string> {
return this._repository.setConfig(key, value);
}
getObjectDetails(treeish: string, path: string): Promise<{ mode: string; object: string; size: number; }> {
return this._repository.getObjectDetails(treeish, path);
}
detectObjectType(object: string): Promise<{ mimetype: string, encoding?: string }> {
return this._repository.detectObjectType(object);
}
buffer(ref: string, filePath: string): Promise<Buffer> {
return this._repository.buffer(ref, filePath);
}
show(ref: string, path: string): Promise<string> {
return this._repository.show(ref, path);
}
getCommit(ref: string): Promise<Commit> {
return this._repository.getCommit(ref);
}
clean(paths: string[]) {
return this._repository.clean(paths.map(p => Uri.file(p)));
}
diff(cached?: boolean) {
return this._repository.diff(cached);
}
diffWithHEAD(path: string): Promise<string> {
return this._repository.diffWithHEAD(path);
}
diffWith(ref: string, path: string): Promise<string> {
return this._repository.diffWith(ref, path);
}
diffIndexWithHEAD(path: string): Promise<string> {
return this._repository.diffIndexWithHEAD(path);
}
diffIndexWith(ref: string, path: string): Promise<string> {
return this._repository.diffIndexWith(ref, path);
}
diffBlobs(object1: string, object2: string): Promise<string> {
return this._repository.diffBlobs(object1, object2);
}
diffBetween(ref1: string, ref2: string, path: string): Promise<string> {
return this._repository.diffBetween(ref1, ref2, path);
}
hashObject(data: string): Promise<string> {
return this._repository.hashObject(data);
}
createBranch(name: string, checkout: boolean, ref?: string | undefined): Promise<void> {
return this._repository.branch(name, checkout, ref);
}
deleteBranch(name: string, force?: boolean): Promise<void> {
return this._repository.deleteBranch(name, force);
}
getBranch(name: string): Promise<Branch> {
return this._repository.getBranch(name);
}
setBranchUpstream(name: string, upstream: string): Promise<void> {
return this._repository.setBranchUpstream(name, upstream);
}
getMergeBase(ref1: string, ref2: string): Promise<string> {
return this._repository.getMergeBase(ref1, ref2);
}
status(): Promise<void> {
return this._repository.status();
}
checkout(treeish: string): Promise<void> {
return this._repository.checkout(treeish);
}
addRemote(name: string, url: string): Promise<void> {
return this._repository.addRemote(name, url);
}
removeRemote(name: string): Promise<void> {
return this._repository.removeRemote(name);
}
fetch(remote?: string | undefined, ref?: string | undefined): Promise<void> {
return this._repository.fetch(remote, ref);
}
pull(): Promise<void> {
return this._repository.pull();
}
push(remoteName?: string, branchName?: string, setUpstream: boolean = false): Promise<void> {
return this._repository.pushTo(remoteName, branchName, setUpstream);
}
}
export class ApiGit implements Git {
get path(): string { return this._model.git.path; }
constructor(private _model: Model) { }
}
export class ApiImpl implements API {
readonly git = new ApiGit(this._model);
get onDidOpenRepository(): Event<Repository> {
return mapEvent(this._model.onDidOpenRepository, r => new ApiRepository(r));
}
get onDidCloseRepository(): Event<Repository> {
return mapEvent(this._model.onDidCloseRepository, r => new ApiRepository(r));
}
get repositories(): Repository[] {
return this._model.repositories.map(r => new ApiRepository(r));
}
constructor(private _model: Model) { }
}

View File

@@ -0,0 +1,76 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Model } from '../model';
import { GitExtension, Repository, API } from './git';
import { ApiRepository, ApiImpl } from './api1';
import { Event, EventEmitter } from 'vscode';
import { latchEvent } from '../util';
export function deprecated(_target: any, key: string, descriptor: any): void {
if (typeof descriptor.value !== 'function') {
throw new Error('not supported');
}
const fn = descriptor.value;
descriptor.value = function () {
console.warn(`Git extension API method '${key}' is deprecated.`);
return fn.apply(this, arguments);
};
}
export class GitExtensionImpl implements GitExtension {
enabled: boolean = false;
private _onDidChangeEnablement = new EventEmitter<boolean>();
readonly onDidChangeEnablement: Event<boolean> = latchEvent(this._onDidChangeEnablement.event);
private _model: Model | undefined = undefined;
set model(model: Model | undefined) {
this._model = model;
this.enabled = !!model;
this._onDidChangeEnablement.fire(this.enabled);
}
constructor(model?: Model) {
if (model) {
this.enabled = true;
this._model = model;
}
}
@deprecated
async getGitPath(): Promise<string> {
if (!this._model) {
throw new Error('Git model not found');
}
return this._model.git.path;
}
@deprecated
async getRepositories(): Promise<Repository[]> {
if (!this._model) {
throw new Error('Git model not found');
}
return this._model.repositories.map(repository => new ApiRepository(repository));
}
getAPI(version: number): API {
if (!this._model) {
throw new Error('Git model not found');
}
if (version !== 1) {
throw new Error(`No API version ${version} found.`);
}
return new ApiImpl(this._model);
}
}

217
extensions/git/src/api/git.d.ts vendored Normal file
View File

@@ -0,0 +1,217 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Uri, SourceControlInputBox, Event, CancellationToken } from 'vscode';
export interface Git {
readonly path: string;
}
export interface InputBox {
value: string;
}
export const enum RefType {
Head,
RemoteHead,
Tag
}
export interface Ref {
readonly type: RefType;
readonly name?: string;
readonly commit?: string;
readonly remote?: string;
}
export interface UpstreamRef {
readonly remote: string;
readonly name: string;
}
export interface Branch extends Ref {
readonly upstream?: UpstreamRef;
readonly ahead?: number;
readonly behind?: number;
}
export interface Commit {
readonly hash: string;
readonly message: string;
readonly parents: string[];
}
export interface Submodule {
readonly name: string;
readonly path: string;
readonly url: string;
}
export interface Remote {
readonly name: string;
readonly fetchUrl?: string;
readonly pushUrl?: string;
readonly isReadOnly: boolean;
}
export const enum Status {
INDEX_MODIFIED,
INDEX_ADDED,
INDEX_DELETED,
INDEX_RENAMED,
INDEX_COPIED,
MODIFIED,
DELETED,
UNTRACKED,
IGNORED,
ADDED_BY_US,
ADDED_BY_THEM,
DELETED_BY_US,
DELETED_BY_THEM,
BOTH_ADDED,
BOTH_DELETED,
BOTH_MODIFIED
}
export interface Change {
/**
* Returns either `originalUri` or `renameUri`, depending
* on whether this change is a rename change. When
* in doubt always use `uri` over the other two alternatives.
*/
readonly uri: Uri;
readonly originalUri: Uri;
readonly renameUri: Uri | undefined;
readonly status: Status;
}
export interface RepositoryState {
readonly HEAD: Branch | undefined;
readonly refs: Ref[];
readonly remotes: Remote[];
readonly submodules: Submodule[];
readonly rebaseCommit: Commit | undefined;
readonly mergeChanges: Change[];
readonly indexChanges: Change[];
readonly workingTreeChanges: Change[];
readonly onDidChange: Event<void>;
}
export interface RepositoryUIState {
readonly selected: boolean;
readonly onDidChange: Event<void>;
}
export interface Repository {
readonly rootUri: Uri;
readonly inputBox: InputBox;
readonly state: RepositoryState;
readonly ui: RepositoryUIState;
getConfigs(): Promise<{ key: string; value: string; }[]>;
getConfig(key: string): Promise<string>;
setConfig(key: string, value: string): Promise<string>;
getObjectDetails(treeish: string, path: string): Promise<{ mode: string, object: string, size: number }>;
detectObjectType(object: string): Promise<{ mimetype: string, encoding?: string }>;
buffer(ref: string, path: string): Promise<Buffer>;
show(ref: string, path: string): Promise<string>;
getCommit(ref: string): Promise<Commit>;
clean(paths: string[]): Promise<void>;
apply(patch: string, reverse?: boolean): Promise<void>;
diff(cached?: boolean): Promise<string>;
diffWithHEAD(path: string): Promise<string>;
diffWith(ref: string, path: string): Promise<string>;
diffIndexWithHEAD(path: string): Promise<string>;
diffIndexWith(ref: string, path: string): Promise<string>;
diffBlobs(object1: string, object2: string): Promise<string>;
diffBetween(ref1: string, ref2: string, path: string): Promise<string>;
hashObject(data: string): Promise<string>;
createBranch(name: string, checkout: boolean, ref?: string): Promise<void>;
deleteBranch(name: string, force?: boolean): Promise<void>;
getBranch(name: string): Promise<Branch>;
setBranchUpstream(name: string, upstream: string): Promise<void>;
getMergeBase(ref1: string, ref2: string): Promise<string>;
status(): Promise<void>;
checkout(treeish: string): Promise<void>;
addRemote(name: string, url: string): Promise<void>;
removeRemote(name: string): Promise<void>;
fetch(remote?: string, ref?: string): Promise<void>;
pull(): Promise<void>;
push(remoteName?: string, branchName?: string, setUpstream?: boolean): Promise<void>;
}
export interface API {
readonly git: Git;
readonly repositories: Repository[];
readonly onDidOpenRepository: Event<Repository>;
readonly onDidCloseRepository: Event<Repository>;
}
export interface GitExtension {
readonly enabled: boolean;
readonly onDidChangeEnablement: Event<boolean>;
/**
* Returns a specific API version.
*
* Throws error if git extension is disabled. You can listed to the
* [GitExtension.onDidChangeEnablement](#GitExtension.onDidChangeEnablement) event
* to know when the extension becomes enabled/disabled.
*
* @param version Version number.
* @returns API instance
*/
getAPI(version: 1): API;
}
export const enum GitErrorCodes {
BadConfigFile = 'BadConfigFile',
AuthenticationFailed = 'AuthenticationFailed',
NoUserNameConfigured = 'NoUserNameConfigured',
NoUserEmailConfigured = 'NoUserEmailConfigured',
NoRemoteRepositorySpecified = 'NoRemoteRepositorySpecified',
NotAGitRepository = 'NotAGitRepository',
NotAtRepositoryRoot = 'NotAtRepositoryRoot',
Conflict = 'Conflict',
StashConflict = 'StashConflict',
UnmergedChanges = 'UnmergedChanges',
PushRejected = 'PushRejected',
RemoteConnectionError = 'RemoteConnectionError',
DirtyWorkTree = 'DirtyWorkTree',
CantOpenResource = 'CantOpenResource',
GitNotFound = 'GitNotFound',
CantCreatePipe = 'CantCreatePipe',
CantAccessRemote = 'CantAccessRemote',
RepositoryNotFound = 'RepositoryNotFound',
RepositoryIsLocked = 'RepositoryIsLocked',
BranchNotFullyMerged = 'BranchNotFullyMerged',
NoRemoteReference = 'NoRemoteReference',
InvalidBranchName = 'InvalidBranchName',
BranchAlreadyExists = 'BranchAlreadyExists',
NoLocalChanges = 'NoLocalChanges',
NoStashFound = 'NoStashFound',
LocalChangesOverwritten = 'LocalChangesOverwritten',
NoUpstreamBranch = 'NoUpstreamBranch',
IsInSubmodule = 'IsInSubmodule',
WrongCase = 'WrongCase',
CantLockRef = 'CantLockRef',
CantRebaseMultipleBranches = 'CantRebaseMultipleBranches',
}

View File

@@ -3,8 +3,6 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as http from 'http';
import * as fs from 'fs';
import * as nls from 'vscode-nls';

View File

@@ -3,8 +3,6 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { Disposable, window, InputBoxOptions } from 'vscode';
import { denodeify } from './util';
import * as path from 'path';

View File

@@ -3,13 +3,11 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { workspace, Disposable, EventEmitter, Memento, window, MessageItem, ConfigurationTarget } from 'vscode';
import { GitErrorCodes } from './git';
import { Repository, Operation } from './repository';
import { eventToPromise, filterEvent, onceEvent } from './util';
import * as nls from 'vscode-nls';
import { GitErrorCodes } from './api/git';
const localize = nls.loadMessageBundle();
@@ -103,7 +101,7 @@ export class AutoFetcher {
}
try {
await this.repository.fetch();
await this.repository.fetchDefault();
} catch (err) {
if (err.gitErrorCode === GitErrorCodes.AuthenticationFailed) {
this.disable();

569
extensions/git/src/commands.ts Normal file → Executable file
View File

@@ -3,11 +3,9 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { Uri, commands, Disposable, window, workspace, QuickPickItem, OutputChannel, Range, WorkspaceEdit, Position, LineChange, SourceControlResourceState, TextDocumentShowOptions, ViewColumn, ProgressLocation, TextEditor, MessageOptions } from 'vscode';
import { Ref, RefType, Git, GitErrorCodes, Branch } from './git';
import { Repository, Resource, Status, CommitOptions, ResourceGroupType } from './repository';
import { Uri, commands, Disposable, window, workspace, QuickPickItem, OutputChannel, Range, WorkspaceEdit, Position, LineChange, SourceControlResourceState, TextDocumentShowOptions, ViewColumn, ProgressLocation, TextEditor, MessageOptions, WorkspaceFolder } from 'vscode';
import { Git, CommitOptions, Stash, ForcePushMode } from './git';
import { Repository, Resource, ResourceGroupType } from './repository';
import { Model } from './model';
import { toGitUri, fromGitUri } from './uri';
import { grep, isDescendant, pathEquals } from './util';
@@ -17,20 +15,20 @@ import { lstat, Stats } from 'fs';
import * as os from 'os';
import TelemetryReporter from 'vscode-extension-telemetry';
import * as nls from 'vscode-nls';
import { Ref, RefType, Branch, GitErrorCodes, Status } from './api/git';
const localize = nls.loadMessageBundle();
class CheckoutItem implements QuickPickItem {
protected get shortCommit(): string { return (this.ref.commit || '').substr(0, 8); }
protected get treeish(): string | undefined { return this.ref.name; }
get label(): string { return this.ref.name || this.shortCommit; }
get description(): string { return this.shortCommit; }
constructor(protected ref: Ref) { }
async run(repository: Repository): Promise<void> {
const ref = this.treeish;
const ref = this.ref.name;
if (!ref) {
return;
@@ -53,13 +51,12 @@ class CheckoutRemoteHeadItem extends CheckoutItem {
return localize('remote branch at', "Remote branch at {0}", this.shortCommit);
}
protected get treeish(): string | undefined {
async run(repository: Repository): Promise<void> {
if (!this.ref.name) {
return;
}
const match = /^[^/]+\/(.*)$/.exec(this.ref.name);
return match ? match[1] : this.ref.name;
await repository.checkoutTracking(this.ref.name);
}
}
@@ -99,6 +96,8 @@ class CreateBranchItem implements QuickPickItem {
get label(): string { return localize('create branch', '$(plus) Create new branch'); }
get description(): string { return ''; }
get alwaysShow(): boolean { return true; }
async run(repository: Repository): Promise<void> {
await this.cc.branch(repository);
}
@@ -119,7 +118,7 @@ interface Command {
const Commands: Command[] = [];
function command(commandId: string, options: CommandOptions = {}): Function {
return (target: any, key: string, descriptor: any) => {
return (_target: any, key: string, descriptor: any) => {
if (!(typeof descriptor.value === 'function')) {
throw new Error('not supported');
}
@@ -137,20 +136,34 @@ const ImageMimetypes = [
'image/bmp'
];
async function categorizeResourceByResolution(resources: Resource[]): Promise<{ merge: Resource[], resolved: Resource[], unresolved: Resource[] }> {
async function categorizeResourceByResolution(resources: Resource[]): Promise<{ merge: Resource[], resolved: Resource[], unresolved: Resource[], deletionConflicts: Resource[] }> {
const selection = resources.filter(s => s instanceof Resource) as Resource[];
const merge = selection.filter(s => s.resourceGroupType === ResourceGroupType.Merge);
const isBothAddedOrModified = (s: Resource) => s.type === Status.BOTH_MODIFIED || s.type === Status.BOTH_ADDED;
const isAnyDeleted = (s: Resource) => s.type === Status.DELETED_BY_THEM || s.type === Status.DELETED_BY_US;
const possibleUnresolved = merge.filter(isBothAddedOrModified);
const promises = possibleUnresolved.map(s => grep(s.resourceUri.fsPath, /^<{7}|^={7}|^>{7}/));
const unresolvedBothModified = await Promise.all<boolean>(promises);
const resolved = possibleUnresolved.filter((s, i) => !unresolvedBothModified[i]);
const resolved = possibleUnresolved.filter((_s, i) => !unresolvedBothModified[i]);
const deletionConflicts = merge.filter(s => isAnyDeleted(s));
const unresolved = [
...merge.filter(s => !isBothAddedOrModified(s)),
...possibleUnresolved.filter((s, i) => unresolvedBothModified[i])
...merge.filter(s => !isBothAddedOrModified(s) && !isAnyDeleted(s)),
...possibleUnresolved.filter((_s, i) => unresolvedBothModified[i])
];
return { merge, resolved, unresolved };
return { merge, resolved, unresolved, deletionConflicts };
}
enum PushType {
Push,
PushTo,
PushTags,
}
interface PushOptions {
pushType: PushType;
forcePush?: boolean;
silent?: boolean;
}
export class CommandCenter {
@@ -181,7 +194,20 @@ export class CommandCenter {
@command('git.openResource')
async openResource(resource: Resource): Promise<void> {
await this._openResource(resource, undefined, true, false);
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, true, false);
} else {
await this.openFile(resource);
}
}
private async _openResource(resource: Resource, preview?: boolean, preserveFocus?: boolean, preserveSelection?: boolean): Promise<void> {
@@ -203,7 +229,10 @@ export class CommandCenter {
right = toGitUri(resource.resourceUri, resource.resourceGroupType === ResourceGroupType.Index ? 'index' : 'wt', { submoduleOf: repository.root });
}
} else {
left = await this.getLeftResource(resource);
if (resource.type !== Status.DELETED_BY_THEM) {
left = await this.getLeftResource(resource);
}
right = await this.getRightResource(resource);
}
@@ -230,7 +259,7 @@ export class CommandCenter {
}
if (!left) {
await commands.executeCommand<void>('vscode.open', right, opts);
await commands.executeCommand<void>('vscode.open', right, opts, title);
} else {
await commands.executeCommand<void>('vscode.diff', left, right, title, opts);
}
@@ -287,6 +316,7 @@ export class CommandCenter {
case Status.DELETED_BY_THEM:
return this.getURI(resource.resourceUri, '');
}
return undefined;
}
private async getRightResource(resource: Resource): Promise<Uri | undefined> {
@@ -298,10 +328,15 @@ export class CommandCenter {
return this.getURI(resource.resourceUri, '');
case Status.INDEX_DELETED:
case Status.DELETED_BY_THEM:
case Status.DELETED:
return this.getURI(resource.resourceUri, 'HEAD');
case Status.DELETED_BY_US:
return this.getURI(resource.resourceUri, '~3');
case Status.DELETED_BY_THEM:
return this.getURI(resource.resourceUri, '~2');
case Status.MODIFIED:
case Status.UNTRACKED:
case Status.IGNORED:
@@ -324,6 +359,7 @@ export class CommandCenter {
case Status.BOTH_MODIFIED:
return resource.resourceUri;
}
return undefined;
}
private getTitle(resource: Resource): string {
@@ -332,13 +368,18 @@ export class CommandCenter {
switch (resource.type) {
case Status.INDEX_MODIFIED:
case Status.INDEX_RENAMED:
case Status.DELETED_BY_THEM:
return `${basename} (Index)`;
case Status.MODIFIED:
case Status.BOTH_ADDED:
case Status.BOTH_MODIFIED:
return `${basename} (Working Tree)`;
case Status.DELETED_BY_US:
return `${basename} (Theirs)`;
case Status.DELETED_BY_THEM:
return `${basename} (Ours)`;
}
return '';
@@ -454,21 +495,27 @@ export class CommandCenter {
@command('git.init')
async init(): Promise<void> {
let path: string | undefined;
let repositoryPath: string | undefined = undefined;
let askToOpen = true;
if (workspace.workspaceFolders && workspace.workspaceFolders.length > 1) {
if (workspace.workspaceFolders) {
const placeHolder = localize('init', "Pick workspace folder to initialize git repo in");
const items = workspace.workspaceFolders.map(folder => ({ label: folder.name, description: folder.uri.fsPath, folder }));
const pick = { label: localize('choose', "Choose Folder...") };
const items: { label: string, folder?: WorkspaceFolder }[] = [
...workspace.workspaceFolders.map(folder => ({ label: folder.name, description: folder.uri.fsPath, folder })),
pick
];
const item = await window.showQuickPick(items, { placeHolder, ignoreFocusOut: true });
if (!item) {
return;
} else if (item.folder) {
repositoryPath = item.folder.uri.fsPath;
askToOpen = false;
}
path = item.folder.uri.fsPath;
}
if (!path) {
if (!repositoryPath) {
const homeUri = Uri.file(os.homedir());
const defaultUri = workspace.workspaceFolders && workspace.workspaceFolders.length > 0
? Uri.file(workspace.workspaceFolders[0].uri.fsPath)
@@ -497,11 +544,40 @@ export class CommandCenter {
}
}
path = uri.fsPath;
repositoryPath = uri.fsPath;
if (workspace.workspaceFolders && workspace.workspaceFolders.some(w => w.uri.toString() === uri.toString())) {
askToOpen = false;
}
}
await this.git.init(path);
await this.model.openRepository(path);
await this.git.init(repositoryPath);
const choices = [];
let message = localize('proposeopen init', "Would you like to open the initialized repository?");
const open = localize('openrepo', "Open Repository");
choices.push(open);
if (!askToOpen) {
return;
}
const addToWorkspace = localize('add', "Add to Workspace");
if (workspace.workspaceFolders) {
message = localize('proposeopen2 init', "Would you like to open the initialized repository, or add it to the current workspace?");
choices.push(addToWorkspace);
}
const result = await window.showInformationMessage(message, ...choices);
const uri = Uri.file(repositoryPath);
if (result === open) {
commands.executeCommand('vscode.openFolder', uri);
} else if (result === addToWorkspace) {
workspace.updateWorkspaceFolders(workspace.workspaceFolders!.length, 0, { uri });
} else {
await this.model.openRepository(repositoryPath);
}
}
@command('git.openRepository', { repository: false })
@@ -562,22 +638,27 @@ export class CommandCenter {
return;
}
const preview = uris.length === 1 ? true : false;
const activeTextEditor = window.activeTextEditor;
for (const uri of uris) {
const opts: TextDocumentShowOptions = {
preserveFocus,
preview,
preview: false,
viewColumn: ViewColumn.Active
};
const document = await workspace.openTextDocument(uri);
// 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 (activeTextEditor && activeTextEditor.document.uri.path === uri.path) {
// preserve not only selection but also visible range
opts.selection = activeTextEditor.selection;
const previousVisibleRanges = activeTextEditor.visibleRanges;
const editor = await window.showTextDocument(document, opts);
editor.revealRange(previousVisibleRanges[0]);
} else {
await window.showTextDocument(document, opts);
}
await commands.executeCommand<void>('vscode.open', uri, opts);
}
}
@@ -589,6 +670,7 @@ export class CommandCenter {
@command('git.openHEADFile')
async openHEADFile(arg?: Resource | Uri): Promise<void> {
let resource: Resource | undefined = undefined;
const preview = !(arg instanceof Resource);
if (arg instanceof Resource) {
resource = arg;
@@ -609,12 +691,18 @@ export class CommandCenter {
return;
}
return await commands.executeCommand<void>('vscode.open', HEAD);
const opts: TextDocumentShowOptions = {
preview
};
return await commands.executeCommand<void>('vscode.open', HEAD, opts);
}
@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;
@@ -641,7 +729,6 @@ export class CommandCenter {
return;
}
const preview = resources.length === 1 ? undefined : false;
for (const resource of resources) {
await this._openResource(resource, preview, preserveFocus, preserveSelection);
}
@@ -666,7 +753,7 @@ export class CommandCenter {
}
const selection = resourceStates.filter(s => s instanceof Resource) as Resource[];
const { resolved, unresolved } = await categorizeResourceByResolution(selection);
const { resolved, unresolved, deletionConflicts } = await categorizeResourceByResolution(selection);
if (unresolved.length > 0) {
const message = unresolved.length > 1
@@ -681,6 +768,20 @@ export class CommandCenter {
}
}
try {
await this.runByRepository(deletionConflicts.map(r => r.resourceUri), async (repository, resources) => {
for (const resource of resources) {
await this._stageDeletionConflict(repository, resource);
}
});
} catch (err) {
if (/Cancelled/.test(err.message)) {
return;
}
throw err;
}
const workingTree = selection.filter(s => s.resourceGroupType === ResourceGroupType.WorkingTree);
const scmResources = [...workingTree, ...resolved, ...unresolved];
@@ -696,7 +797,19 @@ export class CommandCenter {
@command('git.stageAll', { repository: true })
async stageAll(repository: Repository): Promise<void> {
const resources = repository.mergeGroup.resourceStates.filter(s => s instanceof Resource) as Resource[];
const { merge, unresolved } = await categorizeResourceByResolution(resources);
const { merge, unresolved, deletionConflicts } = await categorizeResourceByResolution(resources);
try {
for (const deletionConflict of deletionConflicts) {
await this._stageDeletionConflict(repository, deletionConflict.resourceUri);
}
} catch (err) {
if (/Cancelled/.test(err.message)) {
return;
}
throw err;
}
if (unresolved.length > 0) {
const message = unresolved.length > 1
@@ -714,6 +827,41 @@ export class CommandCenter {
await repository.add([]);
}
private async _stageDeletionConflict(repository: Repository, uri: Uri): Promise<void> {
const uriString = uri.toString();
const resource = repository.mergeGroup.resourceStates.filter(r => r.resourceUri.toString() === uriString)[0];
if (!resource) {
return;
}
if (resource.type === Status.DELETED_BY_THEM) {
const keepIt = localize('keep ours', "Keep Our Version");
const deleteIt = localize('delete', "Delete File");
const result = await window.showInformationMessage(localize('deleted by them', "File '{0}' was deleted by them and modified by us.\n\nWhat would you like to do?", path.basename(uri.fsPath)), { modal: true }, keepIt, deleteIt);
if (result === keepIt) {
await repository.add([uri]);
} else if (result === deleteIt) {
await repository.rm([uri]);
} else {
throw new Error('Cancelled');
}
} else if (resource.type === Status.DELETED_BY_US) {
const keepIt = localize('keep theirs', "Keep Their Version");
const deleteIt = localize('delete', "Delete File");
const result = await window.showInformationMessage(localize('deleted by us', "File '{0}' was deleted by us and modified by them.\n\nWhat would you like to do?", path.basename(uri.fsPath)), { modal: true }, keepIt, deleteIt);
if (result === keepIt) {
await repository.add([uri]);
} else if (result === deleteIt) {
await repository.rm([uri]);
} else {
throw new Error('Cancelled');
}
}
}
@command('git.stageChange')
async stageChange(uri: Uri, changes: LineChange[], index: number): Promise<void> {
const textEditor = window.visibleTextEditors.filter(e => e.document.uri.toString() === uri.toString())[0];
@@ -916,10 +1064,20 @@ export class CommandCenter {
message = localize('confirm delete', "Are you sure you want to DELETE {0}?", path.basename(scmResources[0].resourceUri.fsPath));
yes = localize('delete file', "Delete file");
} else {
message = localize('confirm discard', "Are you sure you want to discard changes in {0}?", path.basename(scmResources[0].resourceUri.fsPath));
if (scmResources[0].type === Status.DELETED) {
yes = localize('restore file', "Restore file");
message = localize('confirm restore', "Are you sure you want to restore {0}?", path.basename(scmResources[0].resourceUri.fsPath));
} else {
message = localize('confirm discard', "Are you sure you want to discard changes in {0}?", path.basename(scmResources[0].resourceUri.fsPath));
}
}
} else {
message = localize('confirm discard multiple', "Are you sure you want to discard changes in {0} files?", scmResources.length);
if (scmResources.every(resource => resource.type === Status.DELETED)) {
yes = localize('restore files', "Restore files");
message = localize('confirm restore multiple', "Are you sure you want to restore {0} files?", scmResources.length);
} else {
message = localize('confirm discard multiple', "Are you sure you want to discard changes in {0} files?", scmResources.length);
}
if (untrackedCount > 0) {
message = `${message}\n\n${localize('warn untracked', "This will DELETE {0} untracked files!", untrackedCount)}`;
@@ -1071,10 +1229,13 @@ export class CommandCenter {
}
if (
// no changes
(noStagedChanges && noUnstagedChanges)
// or no staged changes and not `all`
|| (!opts.all && noStagedChanges)
(
// no changes
(noStagedChanges && noUnstagedChanges)
// or no staged changes and not `all`
|| (!opts.all && noStagedChanges)
)
&& !opts.empty
) {
window.showInformationMessage(localize('no changes', "There are no changes to commit."));
return false;
@@ -1088,6 +1249,17 @@ export class CommandCenter {
await repository.commit(message, opts);
const postCommitCommand = config.get<'none' | 'push' | 'sync'>('postCommitCommand');
switch (postCommitCommand) {
case 'push':
await this._push(repository, { pushType: PushType.Push, silent: true });
break;
case 'sync':
await this.sync(repository);
break;
}
return true;
}
@@ -1167,6 +1339,33 @@ export class CommandCenter {
await this.commitWithAnyInput(repository, { all: true, amend: true });
}
@command('git.commitEmpty', { repository: true })
async commitEmpty(repository: Repository): Promise<void> {
const root = Uri.file(repository.root);
const config = workspace.getConfiguration('git', root);
const shouldPrompt = config.get<boolean>('confirmEmptyCommits') === true;
if (shouldPrompt) {
const message = localize('confirm emtpy commit', "Are you sure you want to create an empty commit?");
const yes = localize('yes', "Yes");
const neverAgain = localize('yes never again', "Yes, Don't Show Again");
const pick = await window.showWarningMessage(message, { modal: true }, yes, neverAgain);
if (pick === neverAgain) {
await config.update('confirmEmptyCommits', false, true);
} else if (pick !== yes) {
return;
}
}
await this.commitWithAnyInput(repository, { empty: true });
}
@command('git.restoreCommitTemplate', { repository: true })
async restoreCommitTemplate(repository: Repository): Promise<void> {
repository.inputBox.value = await repository.getCommitTemplate();
}
@command('git.undoCommit', { repository: true })
async undoCommit(repository: Repository): Promise<void> {
const HEAD = repository.HEAD;
@@ -1189,9 +1388,10 @@ export class CommandCenter {
}
@command('git.checkout', { repository: true })
async checkout(repository: Repository, treeish: string): Promise<void> {
async checkout(repository: Repository, treeish: string): Promise<boolean> {
if (typeof treeish === 'string') {
return await repository.checkout(treeish);
await repository.checkout(treeish);
return true;
}
const config = workspace.getConfiguration('git');
@@ -1215,26 +1415,49 @@ export class CommandCenter {
const choice = await window.showQuickPick(picks, { placeHolder });
if (!choice) {
return;
return false;
}
await choice.run(repository);
return true;
}
@command('git.branch', { repository: true })
async branch(repository: Repository): Promise<void> {
const config = workspace.getConfiguration('git');
const branchValidationRegex = config.get<string>('branchValidationRegex')!;
const branchWhitespaceChar = config.get<string>('branchWhitespaceChar')!;
const validateName = new RegExp(branchValidationRegex);
const sanitize = (name: string) => {
name = name.trim();
if (!name) {
return name;
}
return name.replace(/^\.|\/\.|\.\.|~|\^|:|\/$|\.lock$|\.lock\/|\\|\*|\s|^\s*$|\.$|\[|\]$/g, branchWhitespaceChar);
};
const result = await window.showInputBox({
placeHolder: localize('branch name', "Branch name"),
prompt: localize('provide branch name', "Please provide a branch name"),
ignoreFocusOut: true
ignoreFocusOut: true,
validateInput: (name: string) => {
if (validateName.test(sanitize(name))) {
return null;
}
return localize('branch name format invalid', "Branch name needs to match regex: {0}", branchValidationRegex);
}
});
if (!result) {
const name = sanitize(result || '');
if (!name) {
return;
}
const name = result.replace(/^\.|\/\.|\.\.|~|\^|:|\/$|\.lock$|\.lock\/|\\|\*|\s|^\s*$|\.$/g, '-');
await repository.branch(name);
await repository.branch(name, true);
}
@command('git.deleteBranch', { repository: true })
@@ -1354,7 +1577,28 @@ export class CommandCenter {
return;
}
await repository.fetch();
await repository.fetchDefault();
}
@command('git.fetchPrune', { repository: true })
async fetchPrune(repository: Repository): Promise<void> {
if (repository.remotes.length === 0) {
window.showWarningMessage(localize('no remotes to fetch', "This repository has no remotes configured to fetch from."));
return;
}
await repository.fetchPrune();
}
@command('git.fetchAll', { repository: true })
async fetchAll(repository: Repository): Promise<void> {
if (repository.remotes.length === 0) {
window.showWarningMessage(localize('no remotes to fetch', "This repository has no remotes configured to fetch from."));
return;
}
await repository.fetchAll();
}
@command('git.pullFrom', { repository: true })
@@ -1377,7 +1621,8 @@ export class CommandCenter {
const remoteRefs = repository.refs;
const remoteRefsFiltered = remoteRefs.filter(r => (r.remote === remotePick.label));
const branchPicks = remoteRefsFiltered.map(r => ({ label: r.name })) as { label: string; description: string }[];
const branchPick = await window.showQuickPick(branchPicks, { placeHolder });
const branchPlaceHolder = localize('pick branch pull', "Pick a branch to pull from");
const branchPick = await window.showQuickPick(branchPicks, { placeHolder: branchPlaceHolder });
if (!branchPick) {
return;
@@ -1412,76 +1657,118 @@ export class CommandCenter {
await repository.pullWithRebase(repository.HEAD);
}
@command('git.push', { repository: true })
async push(repository: Repository): Promise<void> {
private async _push(repository: Repository, pushOptions: PushOptions) {
const remotes = repository.remotes;
if (remotes.length === 0) {
window.showWarningMessage(localize('no remotes to push', "Your repository has no remotes configured to push to."));
if (!pushOptions.silent) {
window.showWarningMessage(localize('no remotes to push', "Your repository has no remotes configured to push to."));
}
return;
}
const config = workspace.getConfiguration('git', Uri.file(repository.root));
let forcePushMode: ForcePushMode | undefined = undefined;
if (pushOptions.forcePush) {
if (!config.get<boolean>('allowForcePush')) {
await window.showErrorMessage(localize('force push not allowed', "Force push is not allowed, please enable it with the 'git.allowForcePush' setting."));
return;
}
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 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);
if (pick === neverAgain) {
config.update('confirmForcePush', false, true);
} else if (pick !== yes) {
return;
}
}
}
if (pushOptions.pushType === PushType.PushTags) {
await repository.pushTags(undefined, forcePushMode);
window.showInformationMessage(localize('push with tags success', "Successfully pushed with tags."));
return;
}
if (!repository.HEAD || !repository.HEAD.name) {
window.showWarningMessage(localize('nobranch', "Please check out a branch to push to a remote."));
if (!pushOptions.silent) {
window.showWarningMessage(localize('nobranch', "Please check out a branch to push to a remote."));
}
return;
}
try {
await repository.push(repository.HEAD);
} catch (err) {
if (err.gitErrorCode !== GitErrorCodes.NoUpstreamBranch) {
throw err;
}
if (pushOptions.pushType === PushType.Push) {
try {
await repository.push(repository.HEAD, forcePushMode);
} catch (err) {
if (err.gitErrorCode !== GitErrorCodes.NoUpstreamBranch) {
throw err;
}
if (pushOptions.silent) {
return;
}
const branchName = repository.HEAD.name;
const message = localize('confirm publish branch', "The branch '{0}' has no upstream branch. Would you like to publish this branch?", branchName);
const yes = localize('ok', "OK");
const pick = await window.showWarningMessage(message, { modal: true }, yes);
if (pick === yes) {
await this.publish(repository);
}
}
} else {
const branchName = repository.HEAD.name;
const message = localize('confirm publish branch', "The branch '{0}' has no upstream branch. Would you like to publish this branch?", branchName);
const yes = localize('ok', "OK");
const pick = await window.showWarningMessage(message, { modal: true }, yes);
const picks = remotes.filter(r => r.pushUrl !== undefined).map(r => ({ label: r.name, description: r.pushUrl! }));
const placeHolder = localize('pick remote', "Pick a remote to publish the branch '{0}' to:", branchName);
const pick = await window.showQuickPick(picks, { placeHolder });
if (pick === yes) {
await this.publish(repository);
if (!pick) {
return;
}
await repository.pushTo(pick.label, branchName, undefined, forcePushMode);
}
}
@command('git.push', { repository: true })
async push(repository: Repository): Promise<void> {
await this._push(repository, { pushType: PushType.Push });
}
@command('git.pushForce', { repository: true })
async pushForce(repository: Repository): Promise<void> {
await this._push(repository, { pushType: PushType.Push, forcePush: true });
}
@command('git.pushWithTags', { repository: true })
async pushWithTags(repository: Repository): Promise<void> {
const remotes = repository.remotes;
await this._push(repository, { pushType: PushType.PushTags });
}
if (remotes.length === 0) {
window.showWarningMessage(localize('no remotes to push', "Your repository has no remotes configured to push to."));
return;
}
await repository.pushTags();
window.showInformationMessage(localize('push with tags success', "Successfully pushed with tags."));
@command('git.pushWithTagsForce', { repository: true })
async pushWithTagsForce(repository: Repository): Promise<void> {
await this._push(repository, { pushType: PushType.PushTags, forcePush: true });
}
@command('git.pushTo', { repository: true })
async pushTo(repository: Repository): Promise<void> {
const remotes = repository.remotes;
await this._push(repository, { pushType: PushType.PushTo });
}
if (remotes.length === 0) {
window.showWarningMessage(localize('no remotes to push', "Your repository has no remotes configured to push to."));
return;
}
if (!repository.HEAD || !repository.HEAD.name) {
window.showWarningMessage(localize('nobranch', "Please check out a branch to push to a remote."));
return;
}
const branchName = repository.HEAD.name;
const picks = remotes.filter(r => r.pushUrl !== undefined).map(r => ({ label: r.name, description: r.pushUrl! }));
const placeHolder = localize('pick remote', "Pick a remote to publish the branch '{0}' to:", branchName);
const pick = await window.showQuickPick(picks, { placeHolder });
if (!pick) {
return;
}
await repository.pushTo(pick.label, branchName);
@command('git.pushToForce', { repository: true })
async pushToForce(repository: Repository): Promise<void> {
await this._push(repository, { pushType: PushType.PushTo, forcePush: true });
}
private async _sync(repository: Repository, rebase: boolean): Promise<void> {
@@ -1627,22 +1914,14 @@ export class CommandCenter {
@command('git.stashPop', { repository: true })
async stashPop(repository: Repository): Promise<void> {
const stashes = await repository.getStashes();
if (stashes.length === 0) {
window.showInformationMessage(localize('no stashes', "There are no stashes to restore."));
return;
}
const picks = stashes.map(r => ({ label: `#${r.index}: ${r.description}`, description: '', details: '', id: r.index }));
const placeHolder = localize('pick stash to pop', "Pick a stash to pop");
const choice = await window.showQuickPick(picks, { placeHolder });
const stash = await this.pickStash(repository, placeHolder);
if (!choice) {
if (!stash) {
return;
}
await repository.popStash(choice.id);
await repository.popStash(stash.index);
}
@command('git.stashPopLatest', { repository: true })
@@ -1650,13 +1929,50 @@ export class CommandCenter {
const stashes = await repository.getStashes();
if (stashes.length === 0) {
window.showInformationMessage(localize('no stashes', "There are no stashes to restore."));
window.showInformationMessage(localize('no stashes', "There are no stashes in the repository."));
return;
}
await repository.popStash();
}
@command('git.stashApply', { repository: true })
async stashApply(repository: Repository): Promise<void> {
const placeHolder = localize('pick stash to apply', "Pick a stash to apply");
const stash = await this.pickStash(repository, placeHolder);
if (!stash) {
return;
}
await repository.applyStash(stash.index);
}
@command('git.stashApplyLatest', { repository: true })
async stashApplyLatest(repository: Repository): Promise<void> {
const stashes = await repository.getStashes();
if (stashes.length === 0) {
window.showInformationMessage(localize('no stashes', "There are no stashes in the repository."));
return;
}
await repository.applyStash();
}
private async pickStash(repository: Repository, placeHolder: string): Promise<Stash | undefined> {
const stashes = await repository.getStashes();
if (stashes.length === 0) {
window.showInformationMessage(localize('no stashes', "There are no stashes in the repository."));
return;
}
const picks = stashes.map(stash => ({ label: `#${stash.index}: ${stash.description}`, description: '', details: '', stash }));
const result = await window.showQuickPick(picks, { placeHolder });
return result && result.stash;
}
private createCommand(id: string, key: string, method: Function, options: CommandOptions): (...args: any[]) => any {
const result = (...args: any[]) => {
let result: Promise<any>;
@@ -1700,6 +2016,11 @@ export class CommandCenter {
let message: string;
let type: 'error' | 'warning' = 'error';
const choices = new Map<string, () => void>();
const openOutputChannelChoice = localize('open git log', "Open Git Log");
const outputChannel = this.outputChannel as OutputChannel;
choices.set(openOutputChannelChoice, () => outputChannel.show());
switch (err.gitErrorCode) {
case GitErrorCodes.DirtyWorkTree:
message = localize('clean repo', "Please clean your repository working tree before checkout.");
@@ -1712,6 +2033,16 @@ export class CommandCenter {
type = 'warning';
options.modal = false;
break;
case GitErrorCodes.StashConflict:
message = localize('stash merge conflicts', "There were merge conflicts while applying the stash.");
type = 'warning';
options.modal = false;
break;
case GitErrorCodes.NoUserNameConfigured:
case GitErrorCodes.NoUserEmailConfigured:
message = localize('missing user info', "Make sure you configure your 'user.name' and 'user.email' in git.");
choices.set(localize('learn more', "Learn More"), () => commands.executeCommand('vscode.open', Uri.parse('https://git-scm.com/book/en/v2/Getting-Started-First-Time-Git-Setup')));
break;
default:
const hint = (err.stderr || err.message || String(err))
.replace(/^error: /mi, '')
@@ -1732,14 +2063,17 @@ export class CommandCenter {
return;
}
const outputChannel = this.outputChannel as OutputChannel;
const openOutputChannelChoice = localize('open git log', "Open Git Log");
const choice = type === 'error'
? await window.showErrorMessage(message, options, openOutputChannelChoice)
: await window.showWarningMessage(message, options, openOutputChannelChoice);
const allChoices = Array.from(choices.keys());
const result = type === 'error'
? await window.showErrorMessage(message, options, ...allChoices)
: await window.showWarningMessage(message, options, ...allChoices);
if (choice === openOutputChannelChoice) {
outputChannel.show();
if (result) {
const resultFn = choices.get(result);
if (resultFn) {
resultFn();
}
}
});
};
@@ -1778,6 +2112,7 @@ export class CommandCenter {
return repository.workingTreeGroup.resourceStates.filter(r => r.resourceUri.toString() === uriString)[0]
|| repository.indexGroup.resourceStates.filter(r => r.resourceUri.toString() === uriString)[0];
}
return undefined;
}
private runByRepository<T>(resource: Uri, fn: (repository: Repository, resource: Uri) => Promise<T>): Promise<T[]>;

View File

@@ -3,8 +3,6 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { workspace, Uri, Disposable, Event, EventEmitter, window } from 'vscode';
import { debounce, throttle } from './decorators';
import { fromGitUri, toGitUri } from './uri';
@@ -92,7 +90,11 @@ export class GitContentProvider {
return '';
}
return await repository.diff(path, { cached: ref === 'index' });
if (ref === 'index') {
return await repository.diffIndexWithHEAD(path);
} else {
return await repository.diffWithHEAD(path);
}
}
const repository = this.model.getRepository(uri);
@@ -112,6 +114,8 @@ export class GitContentProvider {
const uriString = fileUri.toString();
const [indexStatus] = repository.indexGroup.resourceStates.filter(r => r.resourceUri.toString() === uriString);
ref = indexStatus ? '' : 'HEAD';
} else if (/^~\d$/.test(ref)) {
ref = `:${ref[1]}`;
}
try {

View File

@@ -3,15 +3,13 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { window, workspace, Uri, Disposable, Event, EventEmitter, DecorationData, DecorationProvider, ThemeColor } from 'vscode';
import * as path from 'path';
import { Repository, GitResourceGroup, Status } from './repository';
import { Repository, GitResourceGroup } from './repository';
import { Model } from './model';
import { debounce } from './decorators';
import { filterEvent, dispose, anyEvent, fireEvent } from './util';
import { GitErrorCodes } from './git';
import { GitErrorCodes, Status } from './api/git';
type Callback = { resolve: (status: boolean) => void, reject: (err: any) => void };
@@ -56,6 +54,7 @@ class GitIgnoreDecorationProvider implements DecorationProvider {
color: new ThemeColor('gitDecoration.ignoredResourceForeground')
};
}
return undefined;
});
}
@@ -93,7 +92,7 @@ class GitDecorationProvider implements DecorationProvider {
private static SubmoduleDecorationData: DecorationData = {
title: 'Submodule',
abbreviation: 'S',
letter: 'S',
color: new ThemeColor('gitDecoration.submoduleResourceForeground')
};

View File

@@ -3,12 +3,10 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { done } from './util';
function decorate(decorator: (fn: Function, key: string) => Function): Function {
return (target: any, key: string, descriptor: any) => {
return (_target: any, key: string, descriptor: any) => {
let fnKey: string | null = null;
let fn: Function | null = null;

View File

@@ -3,13 +3,11 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as jschardet from 'jschardet';
jschardet.Constants.MINIMUM_THRESHOLD = 0.2;
function detectEncodingByBOM(buffer: NodeBuffer): string | null {
function detectEncodingByBOM(buffer: Buffer): string | null {
if (!buffer || buffer.length < 2) {
return null;
}

View File

@@ -3,8 +3,6 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as fs from 'fs';
import * as path from 'path';
import * as os from 'os';
@@ -16,6 +14,7 @@ import * as filetype from 'file-type';
import { assign, groupBy, denodeify, IDisposable, toDisposable, dispose, mkdirp, readBytes, detectUnicodeEncoding, Encoding, onceEvent } from './util';
import { CancellationToken } from 'vscode';
import { detectEncoding } from './encoding';
import { Ref, RefType, Branch, Remote, GitErrorCodes } from './api/git';
const readfile = denodeify<string, string | null, string>(fs.readFile);
@@ -31,40 +30,15 @@ export interface IFileStatus {
rename?: string;
}
export interface Remote {
name: string;
fetchUrl?: string;
pushUrl?: string;
isReadOnly: boolean;
}
export interface Stash {
index: number;
description: string;
}
export enum RefType {
Head,
RemoteHead,
Tag
}
export interface Ref {
type: RefType;
name?: string;
commit?: string;
remote?: string;
}
export interface UpstreamRef {
remote: string;
name: string;
}
export interface Branch extends Ref {
upstream?: UpstreamRef;
ahead?: number;
behind?: number;
interface MutableRemote extends Remote {
fetchUrl?: string;
pushUrl?: string;
isReadOnly: boolean;
}
function parseVersion(raw: string): string {
@@ -309,37 +283,6 @@ export interface IGitOptions {
env?: any;
}
export const GitErrorCodes = {
BadConfigFile: 'BadConfigFile',
AuthenticationFailed: 'AuthenticationFailed',
NoUserNameConfigured: 'NoUserNameConfigured',
NoUserEmailConfigured: 'NoUserEmailConfigured',
NoRemoteRepositorySpecified: 'NoRemoteRepositorySpecified',
NotAGitRepository: 'NotAGitRepository',
NotAtRepositoryRoot: 'NotAtRepositoryRoot',
Conflict: 'Conflict',
UnmergedChanges: 'UnmergedChanges',
PushRejected: 'PushRejected',
RemoteConnectionError: 'RemoteConnectionError',
DirtyWorkTree: 'DirtyWorkTree',
CantOpenResource: 'CantOpenResource',
GitNotFound: 'GitNotFound',
CantCreatePipe: 'CantCreatePipe',
CantAccessRemote: 'CantAccessRemote',
RepositoryNotFound: 'RepositoryNotFound',
RepositoryIsLocked: 'RepositoryIsLocked',
BranchNotFullyMerged: 'BranchNotFullyMerged',
NoRemoteReference: 'NoRemoteReference',
InvalidBranchName: 'InvalidBranchName',
BranchAlreadyExists: 'BranchAlreadyExists',
NoLocalChanges: 'NoLocalChanges',
NoStashFound: 'NoStashFound',
LocalChangesOverwritten: 'LocalChangesOverwritten',
NoUpstreamBranch: 'NoUpstreamBranch',
IsInSubmodule: 'IsInSubmodule',
WrongCase: 'WrongCase',
};
function getGitErrorCode(stderr: string): string | undefined {
if (/Another git process seems to be running in this repository|If no other git process is currently running/.test(stderr)) {
return GitErrorCodes.RepositoryIsLocked;
@@ -427,6 +370,10 @@ export class Git {
return await this._exec(args, options);
}
async exec2(args: string[], options: SpawnOptions = {}): Promise<IExecutionResult<string>> {
return await this._exec(args, options);
}
stream(cwd: string, args: string[], options: SpawnOptions = {}): cp.ChildProcess {
options = assign({ cwd }, options || {});
return this.spawn(args, options);
@@ -674,8 +621,17 @@ export function parseLsFiles(raw: string): LsFilesElement[] {
.map(([, mode, object, stage, file]) => ({ mode, object, stage, file }));
}
export interface DiffOptions {
cached?: boolean;
export interface CommitOptions {
all?: boolean;
amend?: boolean;
signoff?: boolean;
signCommit?: boolean;
empty?: boolean;
}
export enum ForcePushMode {
Force,
ForceWithLease
}
export class Repository {
@@ -706,7 +662,7 @@ export class Repository {
return this.git.spawn(args, options);
}
async config(scope: string, key: string, value: any, options: SpawnOptions): Promise<string> {
async config(scope: string, key: string, value: any = null, options: SpawnOptions = {}): Promise<string> {
const args = ['config'];
if (scope) {
@@ -720,7 +676,25 @@ export class Repository {
}
const result = await this.run(args, options);
return result.stdout;
return result.stdout.trim();
}
async getConfigs(scope: string): Promise<{ key: string; value: string; }[]> {
const args = ['config'];
if (scope) {
args.push('--' + scope);
}
args.push('-l');
const result = await this.run(args);
const lines = result.stdout.trim().split(/\r|\r\n|\n/);
return lines.map(entry => {
const equalsIndex = entry.indexOf('=');
return { key: entry.substr(0, equalsIndex), value: entry.substr(equalsIndex + 1) };
});
}
async bufferString(object: string, encoding: string = 'utf8', autoGuessEncoding = false): Promise<string> {
@@ -848,19 +822,78 @@ export class Repository {
}
}
async diff(path: string, options: DiffOptions = {}): Promise<string> {
async apply(patch: string, reverse?: boolean): Promise<void> {
const args = ['apply', patch];
if (reverse) {
args.push('-R');
}
await this.run(args);
}
async diff(cached = false): Promise<string> {
const args = ['diff'];
if (options.cached) {
if (cached) {
args.push('--cached');
}
args.push('--', path);
const result = await this.run(args);
return result.stdout;
}
async diffWithHEAD(path: string): Promise<string> {
const args = ['diff', '--', path];
const result = await this.run(args);
return result.stdout;
}
async diffWith(ref: string, path: string): Promise<string> {
const args = ['diff', ref, '--', path];
const result = await this.run(args);
return result.stdout;
}
async diffIndexWithHEAD(path: string): Promise<string> {
const args = ['diff', '--cached', '--', path];
const result = await this.run(args);
return result.stdout;
}
async diffIndexWith(ref: string, path: string): Promise<string> {
const args = ['diff', '--cached', ref, '--', path];
const result = await this.run(args);
return result.stdout;
}
async diffBlobs(object1: string, object2: string): Promise<string> {
const args = ['diff', object1, object2];
const result = await this.run(args);
return result.stdout;
}
async diffBetween(ref1: string, ref2: string, path: string): Promise<string> {
const args = ['diff', `${ref1}...${ref2}`, '--', path];
const result = await this.run(args);
return result.stdout.trim();
}
async getMergeBase(ref1: string, ref2: string): Promise<string> {
const args = ['merge-base', ref1, ref2];
const result = await this.run(args);
return result.stdout.trim();
}
async hashObject(data: string): Promise<string> {
const args = ['hash-object', '-w', '--stdin'];
const result = await this.run(args, { input: data });
return result.stdout.trim();
}
async add(paths: string[]): Promise<void> {
const args = ['add', '-A', '--'];
@@ -873,6 +906,18 @@ export class Repository {
await this.run(args);
}
async rm(paths: string[]): Promise<void> {
const args = ['rm', '--'];
if (!paths || !paths.length) {
return;
}
args.push(...paths);
await this.run(args);
}
async stage(path: string, data: string): Promise<void> {
const child = this.stream(['hash-object', '--stdin', '-w', '--path', path], { stdio: [null, null, null] });
child.stdin.end(data, 'utf8');
@@ -899,9 +944,13 @@ export class Repository {
await this.run(['update-index', '--cacheinfo', mode, hash, path]);
}
async checkout(treeish: string, paths: string[]): Promise<void> {
async checkout(treeish: string, paths: string[], opts: { track?: boolean } = Object.create(null)): Promise<void> {
const args = ['checkout', '-q'];
if (opts.track) {
args.push('--track');
}
if (treeish) {
args.push(treeish);
}
@@ -914,7 +963,7 @@ export class Repository {
try {
await this.run(args);
} catch (err) {
if (/Please, commit your changes or stash them/.test(err.stderr || '')) {
if (/Please,? commit your changes or stash them/.test(err.stderr || '')) {
err.gitErrorCode = GitErrorCodes.DirtyWorkTree;
}
@@ -922,7 +971,7 @@ export class Repository {
}
}
async commit(message: string, opts: { all?: boolean, amend?: boolean, signoff?: boolean, signCommit?: boolean } = Object.create(null)): Promise<void> {
async commit(message: string, opts: CommitOptions = Object.create(null)): Promise<void> {
const args = ['commit', '--quiet', '--allow-empty-message', '--file', '-'];
if (opts.all) {
@@ -940,6 +989,9 @@ export class Repository {
if (opts.signCommit) {
args.push('-S');
}
if (opts.empty) {
args.push('--allow-empty');
}
try {
await this.run(args, { input: message || '' });
@@ -981,8 +1033,13 @@ export class Repository {
throw commitErr;
}
async branch(name: string, checkout: boolean): Promise<void> {
async branch(name: string, checkout: boolean, ref?: string): Promise<void> {
const args = checkout ? ['checkout', '-q', '-b', name] : ['branch', '-q', name];
if (ref) {
args.push(ref);
}
await this.run(args);
}
@@ -996,6 +1053,11 @@ export class Repository {
await this.run(args);
}
async setBranchUpstream(name: string, upstream: string): Promise<void> {
const args = ['branch', '--set-upstream-to', upstream, name];
await this.run(args);
}
async deleteRef(ref: string): Promise<void> {
const args = ['update-ref', '-d', ref];
await this.run(args);
@@ -1052,14 +1114,7 @@ export class Repository {
}
async reset(treeish: string, hard: boolean = false): Promise<void> {
const args = ['reset'];
if (hard) {
args.push('--hard');
}
args.push(treeish);
const args = ['reset', hard ? '--hard' : '--soft', treeish];
await this.run(args);
}
@@ -1093,9 +1148,36 @@ export class Repository {
}
}
async fetch(): Promise<void> {
async addRemote(name: string, url: string): Promise<void> {
const args = ['remote', 'add', name, url];
await this.run(args);
}
async removeRemote(name: string): Promise<void> {
const args = ['remote', 'rm', name];
await this.run(args);
}
async fetch(options: { remote?: string, ref?: string, all?: boolean, prune?: boolean } = {}): Promise<void> {
const args = ['fetch'];
if (options.remote) {
args.push(options.remote);
if (options.ref) {
args.push(options.ref);
}
} else if (options.all) {
args.push('--all');
}
if (options.prune) {
args.push('--prune');
}
try {
await this.run(['fetch']);
await this.run(args);
} catch (err) {
if (/No remote repository specified\./.test(err.stderr || '')) {
err.gitErrorCode = GitErrorCodes.NoRemoteRepositorySpecified;
@@ -1131,15 +1213,25 @@ export class Repository {
} else if (/Pull is not possible because you have unmerged files|Cannot pull with rebase: You have unstaged changes|Your local changes to the following files would be overwritten|Please, commit your changes before you can merge/i.test(err.stderr)) {
err.stderr = err.stderr.replace(/Cannot pull with rebase: You have unstaged changes/i, 'Cannot pull with rebase, you have unstaged changes');
err.gitErrorCode = GitErrorCodes.DirtyWorkTree;
} else if (/cannot lock ref|unable to update local ref/i.test(err.stderr || '')) {
err.gitErrorCode = GitErrorCodes.CantLockRef;
} else if (/cannot rebase onto multiple branches/i.test(err.stderr || '')) {
err.gitErrorCode = GitErrorCodes.CantRebaseMultipleBranches;
}
throw err;
}
}
async push(remote?: string, name?: string, setUpstream: boolean = false, tags = false): Promise<void> {
async push(remote?: string, name?: string, setUpstream: boolean = false, tags = false, forcePushMode?: ForcePushMode): Promise<void> {
const args = ['push'];
if (forcePushMode === ForcePushMode.ForceWithLease) {
args.push('--force-with-lease');
} else if (forcePushMode === ForcePushMode.Force) {
args.push('--force');
}
if (setUpstream) {
args.push('-u');
}
@@ -1194,9 +1286,17 @@ export class Repository {
}
async popStash(index?: number): Promise<void> {
try {
const args = ['stash', 'pop'];
const args = ['stash', 'pop'];
await this.popOrApplyStash(args, index);
}
async applyStash(index?: number): Promise<void> {
const args = ['stash', 'apply'];
await this.popOrApplyStash(args, index);
}
private async popOrApplyStash(args: string[], index?: number): Promise<void> {
try {
if (typeof index === 'number') {
args.push(`stash@{${index}}`);
}
@@ -1207,6 +1307,8 @@ export class Repository {
err.gitErrorCode = GitErrorCodes.NoStashFound;
} else if (/error: Your local changes to the following files would be overwritten/.test(err.stderr || '')) {
err.gitErrorCode = GitErrorCodes.LocalChangesOverwritten;
} else if (/^CONFLICT/m.test(err.stdout || '')) {
err.gitErrorCode = GitErrorCodes.StashConflict;
}
throw err;
@@ -1316,7 +1418,7 @@ export class Repository {
async getRemotes(): Promise<Remote[]> {
const result = await this.run(['remote', '--verbose']);
const lines = result.stdout.trim().split('\n').filter(l => !!l);
const remotes: Remote[] = [];
const remotes: MutableRemote[] = [];
for (const line of lines) {
const parts = line.split(/\s/);
@@ -1348,6 +1450,10 @@ export class Repository {
async getBranch(name: string): Promise<Branch> {
if (name === 'HEAD') {
return this.getHEAD();
} else if (/^@/.test(name)) {
const symbolicFullNameResult = await this.run(['rev-parse', '--symbolic-full-name', name]);
const symbolicFullName = symbolicFullNameResult.stdout.trim();
name = symbolicFullName || name;
}
const result = await this.run(['rev-parse', name]);

View File

@@ -1,57 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
function* filter<T>(it: IterableIterator<T>, condition: (t: T, i: number) => boolean): IterableIterator<T> {
let i = 0;
for (let t of it) {
if (condition(t, i++)) {
yield t;
}
}
}
function* map<T, R>(it: IterableIterator<T>, fn: (t: T, i: number) => R): IterableIterator<R> {
let i = 0;
for (let t of it) {
yield fn(t, i++);
}
}
export interface FunctionalIterator<T> extends Iterable<T> {
filter(condition: (t: T, i: number) => boolean): FunctionalIterator<T>;
map<R>(fn: (t: T, i: number) => R): FunctionalIterator<R>;
toArray(): T[];
}
class FunctionalIteratorImpl<T> implements FunctionalIterator<T> {
constructor(private iterator: IterableIterator<T>) { }
filter(condition: (t: T, i: number) => boolean): FunctionalIterator<T> {
return new FunctionalIteratorImpl(filter(this.iterator, condition));
}
map<R>(fn: (t: T, i: number) => R): FunctionalIterator<R> {
return new FunctionalIteratorImpl(map<T, R>(this.iterator, fn));
}
toArray(): T[] {
return Array.from(this.iterator);
}
[Symbol.iterator](): IterableIterator<T> {
return this.iterator;
}
}
export function iterate<T>(obj: T[] | IterableIterator<T>): FunctionalIterator<T> {
if (Array.isArray(obj)) {
return new FunctionalIteratorImpl(obj[Symbol.iterator]());
}
return new FunctionalIteratorImpl(obj);
}

View File

@@ -3,11 +3,10 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as nls from 'vscode-nls';
const localize = nls.loadMessageBundle();
import { ExtensionContext, workspace, window, Disposable, commands, OutputChannel } from 'vscode';
import { ExtensionContext, workspace, window, Disposable, commands, OutputChannel } from 'vscode'; // {{SQL CARBON EDIT}} - remove unused imports
import { findGit, Git, IGit } from './git';
import { Model } from './model';
import { CommandCenter } from './commands';
@@ -16,8 +15,12 @@ import { GitDecorations } from './decorationProvider';
import { Askpass } from './askpass';
import { toDisposable, filterEvent, eventToPromise } from './util';
import TelemetryReporter from 'vscode-extension-telemetry';
import { API, NoopAPIImpl, APIImpl } from './api';
import { GitExtension } from './api/git';
import { GitProtocolHandler } from './protocolHandler';
import { GitExtensionImpl } from './api/extension';
// {{SQL CARBON EDIT}} - remove unused imports
// import * as path from 'path';
// import * as fs from 'fs';
const deactivateTasks: { (): Promise<any>; }[] = [];
@@ -69,7 +72,57 @@ async function createModel(context: ExtensionContext, outputChannel: OutputChann
return model;
}
export async function activate(context: ExtensionContext): Promise<API> {
// {{SQL CARBON EDIT}} - Comment out function that is unused due to our edit below
// async function isGitRepository(folder: WorkspaceFolder): Promise<boolean> {
// if (folder.uri.scheme !== 'file') {
// return false;
// }
// const dotGit = path.join(folder.uri.fsPath, '.git');
// try {
// const dotGitStat = await new Promise<fs.Stats>((c, e) => fs.stat(dotGit, (err, stat) => err ? e(err) : c(stat)));
// return dotGitStat.isDirectory();
// } catch (err) {
// return false;
// }
// }
// {{SQL CARBON EDIT}} - Comment out function that is unused due to our edit below
// async function warnAboutMissingGit(): Promise<void> {
// const config = workspace.getConfiguration('git');
// const shouldIgnore = config.get<boolean>('ignoreMissingGitWarning') === true;
// if (shouldIgnore) {
// return;
// }
// if (!workspace.workspaceFolders) {
// return;
// }
// const areGitRepositories = await Promise.all(workspace.workspaceFolders.map(isGitRepository));
// if (areGitRepositories.every(isGitRepository => !isGitRepository)) {
// return;
// }
// const download = localize('downloadgit', "Download Git");
// const neverShowAgain = localize('neverShowAgain', "Don't Show Again");
// const choice = await window.showWarningMessage(
// localize('notfound', "Git not found. Install it or configure it using the 'git.path' setting."),
// download,
// neverShowAgain
// );
// if (choice === download) {
// commands.executeCommand('vscode.open', Uri.parse('https://git-scm.com/'));
// } else if (choice === neverShowAgain) {
// await config.update('ignoreMissingGitWarning', true, true);
// }
// }
export async function activate(context: ExtensionContext): Promise<GitExtension> {
const disposables: Disposable[] = [];
context.subscriptions.push(new Disposable(() => Disposable.from(...disposables).dispose()));
@@ -77,7 +130,7 @@ export async function activate(context: ExtensionContext): Promise<API> {
commands.registerCommand('git.showOutput', () => outputChannel.show());
disposables.push(outputChannel);
const { name, version, aiKey } = require(context.asAbsolutePath('./package.json')) as { name: string, version: string, aiKey: string };
const { name, version, aiKey } = require('../package.json') as { name: string, version: string, aiKey: string };
const telemetryReporter = new TelemetryReporter(name, version, aiKey);
deactivateTasks.push(() => telemetryReporter.dispose());
@@ -87,46 +140,32 @@ export async function activate(context: ExtensionContext): Promise<API> {
if (!enabled) {
const onConfigChange = filterEvent(workspace.onDidChangeConfiguration, e => e.affectsConfiguration('git'));
const onEnabled = filterEvent(onConfigChange, () => workspace.getConfiguration('git', null).get<boolean>('enabled') === true);
await eventToPromise(onEnabled);
const result = new GitExtensionImpl();
eventToPromise(onEnabled).then(async () => result.model = await createModel(context, outputChannel, telemetryReporter, disposables));
return result;
}
try {
const model = await createModel(context, outputChannel, telemetryReporter, disposables);
return new APIImpl(model);
return new GitExtensionImpl(model);
} catch (err) {
if (!/Git installation not found/.test(err.message || '')) {
throw err;
}
// {{SQL CARBON EDIT}} turn-off Git missing prompt
//const config = workspace.getConfiguration('git');
//const shouldIgnore = config.get<boolean>('ignoreMissingGitWarning') === true;
// console.warn(err.message);
// outputChannel.appendLine(err.message);
// if (!shouldIgnore) {
// console.warn(err.message);
// outputChannel.appendLine(err.message);
// outputChannel.show();
// warnAboutMissingGit();
// const download = localize('downloadgit', "Download Git");
// const neverShowAgain = localize('neverShowAgain', "Don't Show Again");
// const choice = await window.showWarningMessage(
// localize('notfound', "Git not found. Install it or configure it using the 'git.path' setting."),
// download,
// neverShowAgain
// );
// if (choice === download) {
// commands.executeCommand('vscode.open', Uri.parse('https://git-scm.com/'));
// } else if (choice === neverShowAgain) {
// await config.update('ignoreMissingGitWarning', true, true);
// }
// }
return new NoopAPIImpl();
return new GitExtensionImpl();
}
}
async function checkGitVersion(info: IGit): Promise<void> {
// {{SQL CARBON EDIT}} - Rename info to _info to prevent error due to unused variable
async function checkGitVersion(_info: IGit): Promise<void> {
// {{SQL CARBON EDIT}}
// remove Git version check for azuredatastudio

View File

@@ -3,17 +3,16 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { workspace, WorkspaceFoldersChangeEvent, Uri, window, Event, EventEmitter, QuickPickItem, Disposable, SourceControl, SourceControlResourceGroup, TextEditor, Memento, OutputChannel } from 'vscode';
import { Repository, RepositoryState } from './repository';
import { memoize, sequentialize, debounce } from './decorators';
import { dispose, anyEvent, filterEvent, isDescendant, firstIndex } from './util';
import { Git, GitErrorCodes } from './git';
import { Git } from './git';
import * as path from 'path';
import * as fs from 'fs';
import * as nls from 'vscode-nls';
import { fromGitUri } from './uri';
import { GitErrorCodes } from './api/git';
const localize = nls.loadMessageBundle();
@@ -107,6 +106,18 @@ export class Model {
children
.filter(child => child !== '.git')
.forEach(child => this.openRepository(path.join(root, child)));
const folderConfig = workspace.getConfiguration('git', folder.uri);
const paths = folderConfig.get<string[]>('scanRepositories') || [];
for (const possibleRepositoryPath of paths) {
if (path.isAbsolute(possibleRepositoryPath)) {
console.warn(localize('not supported', "Absolute paths not supported in 'git.scanRepositories' setting."));
continue;
}
this.openRepository(path.join(root, possibleRepositoryPath));
}
} catch (err) {
// noop
}
@@ -241,17 +252,28 @@ export class Model {
const changeListener = repository.onDidChangeRepository(uri => this._onDidChangeRepository.fire({ repository, uri }));
const originalResourceChangeListener = repository.onDidChangeOriginalResource(uri => this._onDidChangeOriginalResource.fire({ repository, uri }));
const shouldDetectSubmodules = workspace
.getConfiguration('git', Uri.file(repository.root))
.get<boolean>('detectSubmodules') as boolean;
const submodulesLimit = workspace
.getConfiguration('git', Uri.file(repository.root))
.get<number>('detectSubmodulesLimit') as number;
const checkForSubmodules = () => {
if (!shouldDetectSubmodules) {
return;
}
if (repository.submodules.length > submodulesLimit) {
window.showWarningMessage(localize('too many submodules', "The '{0}' repository has {1} submodules which won't be opened automatically. You can still open each one individually by opening a file within.", path.basename(repository.root), repository.submodules.length));
statusListener.dispose();
}
this.scanSubmodules(repository, submodulesLimit);
repository.submodules
.slice(0, submodulesLimit)
.map(r => path.join(repository.root, r.path))
.forEach(p => this.eventuallyScanPossibleGitRepository(p));
};
const statusListener = repository.onDidRunGitStatus(checkForSubmodules);
@@ -273,21 +295,6 @@ export class Model {
this._onDidOpenRepository.fire(repository);
}
private scanSubmodules(repository: Repository, limit: number): void {
const shouldScanSubmodules = workspace
.getConfiguration('git', Uri.file(repository.root))
.get<boolean>('detectSubmodules') === true;
if (!shouldScanSubmodules) {
return;
}
repository.submodules
.slice(0, limit)
.map(r => path.join(repository.root, r.path))
.forEach(p => this.eventuallyScanPossibleGitRepository(p));
}
close(repository: Repository): void {
const openRepository = this.getOpenRepository(repository);

View File

@@ -3,8 +3,6 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { UriHandler, Uri, window, Disposable, commands } from 'vscode';
import { dispose } from './util';
import * as querystring from 'querystring';

View File

@@ -3,10 +3,8 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { commands, Uri, Command, EventEmitter, Event, scm, SourceControl, SourceControlInputBox, SourceControlResourceGroup, SourceControlResourceState, SourceControlResourceDecorations, SourceControlInputBoxValidation, Disposable, ProgressLocation, window, workspace, WorkspaceEdit, ThemeColor, DecorationData, Memento, SourceControlInputBoxValidationType } from 'vscode';
import { Repository as BaseRepository, Ref, Branch, Remote, Commit, GitErrorCodes, Stash, RefType, GitError, Submodule, DiffOptions } from './git';
import { Repository as BaseRepository, Commit, Stash, GitError, Submodule, CommitOptions, ForcePushMode } from './git';
import { anyEvent, filterEvent, eventToPromise, dispose, find, isDescendant, IDisposable, onceEvent, EmptyDisposable, debounceEvent } from './util';
import { memoize, throttle, debounce } from './decorators';
import { toGitUri } from './uri';
@@ -15,6 +13,7 @@ import * as path from 'path';
import * as nls from 'vscode-nls';
import * as fs from 'fs';
import { StatusBarCommands } from './statusbar';
import { Branch, Ref, Remote, RefType, GitErrorCodes, Status } from './api/git';
const timeout = (millis: number) => new Promise(c => setTimeout(c, millis));
@@ -25,33 +24,12 @@ function getIconUri(iconName: string, theme: string): Uri {
return Uri.file(path.join(iconsRootPath, theme, `${iconName}.svg`));
}
export enum RepositoryState {
export const enum RepositoryState {
Idle,
Disposed
}
export enum Status {
INDEX_MODIFIED,
INDEX_ADDED,
INDEX_DELETED,
INDEX_RENAMED,
INDEX_COPIED,
MODIFIED,
DELETED,
UNTRACKED,
IGNORED,
ADDED_BY_US,
ADDED_BY_THEM,
DELETED_BY_US,
DELETED_BY_THEM,
BOTH_ADDED,
BOTH_DELETED,
BOTH_MODIFIED
}
export enum ResourceGroupType {
export const enum ResourceGroupType {
Merge,
Index,
WorkingTree
@@ -123,6 +101,7 @@ export class Resource implements SourceControlResourceState {
case Status.DELETED_BY_US: return Resource.Icons[theme].Conflict;
case Status.BOTH_ADDED: return Resource.Icons[theme].Conflict;
case Status.BOTH_MODIFIED: return Resource.Icons[theme].Conflict;
default: throw new Error('Unknown git status: ' + this.type);
}
}
@@ -197,15 +176,19 @@ export class Resource implements SourceControlResourceState {
return 'U';
case Status.IGNORED:
return 'I';
case Status.DELETED_BY_THEM:
return 'D';
case Status.DELETED_BY_US:
return 'D';
case Status.INDEX_COPIED:
case Status.BOTH_DELETED:
case Status.ADDED_BY_US:
case Status.DELETED_BY_THEM:
case Status.ADDED_BY_THEM:
case Status.DELETED_BY_US:
case Status.BOTH_ADDED:
case Status.BOTH_MODIFIED:
return 'C';
default:
throw new Error('Unknown git status: ' + this.type);
}
}
@@ -233,6 +216,8 @@ export class Resource implements SourceControlResourceState {
case Status.BOTH_ADDED:
case Status.BOTH_MODIFIED:
return new ThemeColor('gitDecoration.conflictingResourceForeground');
default:
throw new Error('Unknown git status: ' + this.type);
}
}
@@ -259,10 +244,10 @@ export class Resource implements SourceControlResourceState {
get resourceDecoration(): DecorationData {
const title = this.tooltip;
const abbreviation = this.letter;
const letter = this.letter;
const color = this.color;
const priority = this.priority;
return { bubble: true, source: 'git.resource', title, abbreviation, color, priority };
return { bubble: true, source: 'git.resource', title, letter, color, priority };
}
constructor(
@@ -274,16 +259,24 @@ export class Resource implements SourceControlResourceState {
) { }
}
export enum Operation {
export const enum Operation {
Status = 'Status',
Config = 'Config',
Diff = 'Diff',
MergeBase = 'MergeBase',
Add = 'Add',
Remove = 'Remove',
RevertFiles = 'RevertFiles',
Commit = 'Commit',
Clean = 'Clean',
Branch = 'Branch',
GetBranch = 'GetBranch',
SetBranchUpstream = 'SetBranchUpstream',
HashObject = 'HashObject',
Checkout = 'Checkout',
CheckoutTracking = 'CheckoutTracking',
Reset = 'Reset',
Remote = 'Remote',
Fetch = 'Fetch',
Pull = 'Pull',
Push = 'Push',
@@ -302,6 +295,7 @@ export enum Operation {
GetObjectDetails = 'GetObjectDetails',
SubmoduleUpdate = 'SubmoduleUpdate',
RebaseContinue = 'RebaseContinue',
Apply = 'Apply'
}
function isReadOnly(operation: Operation): boolean {
@@ -310,6 +304,7 @@ function isReadOnly(operation: Operation): boolean {
case Operation.GetCommitTemplate:
case Operation.CheckIgnore:
case Operation.GetObjectDetails:
case Operation.MergeBase:
return true;
default:
return false;
@@ -381,13 +376,6 @@ class OperationsImpl implements Operations {
}
}
export interface CommitOptions {
all?: boolean;
amend?: boolean;
signoff?: boolean;
signCommit?: boolean;
}
export interface GitResourceGroup extends SourceControlResourceGroup {
resourceStates: Resource[];
}
@@ -399,11 +387,32 @@ export interface OperationResult {
class ProgressManager {
private enabled = false;
private disposable: IDisposable = EmptyDisposable;
constructor(repository: Repository) {
const start = onceEvent(filterEvent(repository.onDidChangeOperations, () => repository.operations.shouldShowProgress()));
const end = onceEvent(filterEvent(debounceEvent(repository.onDidChangeOperations, 300), () => !repository.operations.shouldShowProgress()));
constructor(private repository: Repository) {
const onDidChange = filterEvent(workspace.onDidChangeConfiguration, e => e.affectsConfiguration('git', Uri.file(this.repository.root)));
onDidChange(_ => this.updateEnablement());
this.updateEnablement();
}
private updateEnablement(): void {
const config = workspace.getConfiguration('git', Uri.file(this.repository.root));
if (config.get<boolean>('showProgress')) {
this.enable();
} else {
this.disable();
}
}
private enable(): void {
if (this.enabled) {
return;
}
const start = onceEvent(filterEvent(this.repository.onDidChangeOperations, () => this.repository.operations.shouldShowProgress()));
const end = onceEvent(filterEvent(debounceEvent(this.repository.onDidChangeOperations, 300), () => !this.repository.operations.shouldShowProgress()));
const setup = () => {
this.disposable = start(() => {
@@ -413,17 +422,26 @@ class ProgressManager {
};
setup();
this.enabled = true;
}
private disable(): void {
if (!this.enabled) {
return;
}
this.disposable.dispose();
this.disposable = EmptyDisposable;
this.enabled = false;
}
dispose(): void {
this.disposable.dispose();
this.disable();
}
}
export class Repository implements Disposable {
private static readonly InputValidationLength = 72;
private _onDidChangeRepository = new EventEmitter<Uri>();
readonly onDidChangeRepository: Event<Uri> = this._onDidChangeRepository.event;
@@ -521,6 +539,7 @@ export class Repository implements Disposable {
private isRepositoryHuge = false;
private didWarnAboutLimit = false;
private isFreshRepository: boolean | undefined = undefined;
private disposables: Disposable[] = [];
constructor(
@@ -530,15 +549,27 @@ export class Repository implements Disposable {
const fsWatcher = workspace.createFileSystemWatcher('**');
this.disposables.push(fsWatcher);
const onWorkspaceChange = anyEvent(fsWatcher.onDidChange, fsWatcher.onDidCreate, fsWatcher.onDidDelete);
const onRepositoryChange = filterEvent(onWorkspaceChange, uri => isDescendant(repository.root, uri.fsPath));
const onRelevantRepositoryChange = filterEvent(onRepositoryChange, uri => !/\/\.git(\/index\.lock)?$/.test(uri.path));
const workspaceFilter = (uri: Uri) => isDescendant(repository.root, uri.fsPath);
const onWorkspaceDelete = filterEvent(fsWatcher.onDidDelete, workspaceFilter);
const onWorkspaceChange = filterEvent(anyEvent(fsWatcher.onDidChange, fsWatcher.onDidCreate), workspaceFilter);
const onRepositoryDotGitDelete = filterEvent(onWorkspaceDelete, uri => /\/\.git$/.test(uri.path));
const onRepositoryChange = anyEvent(onWorkspaceDelete, onWorkspaceChange);
// relevant repository changes are:
// - DELETE .git folder
// - ANY CHANGE within .git folder except .git itself and .git/index.lock
const onRelevantRepositoryChange = anyEvent(
onRepositoryDotGitDelete,
filterEvent(onRepositoryChange, uri => !/\/\.git(\/index\.lock)?$/.test(uri.path))
);
onRelevantRepositoryChange(this.onFSChange, this, this.disposables);
const onRelevantGitChange = filterEvent(onRelevantRepositoryChange, uri => /\/\.git\//.test(uri.path));
onRelevantGitChange(this._onDidChangeRepository.fire, this._onDidChangeRepository, this.disposables);
this._sourceControl = scm.createSourceControl('git', 'Git', Uri.file(repository.root));
const root = Uri.file(repository.root);
this._sourceControl = scm.createSourceControl('git', 'Git', root);
this._sourceControl.inputBox.placeholder = localize('commitMessage', "Message (press {0} to commit)");
this._sourceControl.acceptInputCommand = { command: 'git.commitWithInput', title: localize('commit', "Commit"), arguments: [this._sourceControl] };
this._sourceControl.quickDiffProvider = this;
@@ -549,8 +580,16 @@ export class Repository implements Disposable {
this._indexGroup = this._sourceControl.createResourceGroup('index', localize('staged changes', "Staged Changes"));
this._workingTreeGroup = this._sourceControl.createResourceGroup('workingTree', localize('changes', "Changes"));
const updateIndexGroupVisibility = () => {
const config = workspace.getConfiguration('git', root);
this.indexGroup.hideWhenEmpty = !config.get<boolean>('alwaysShowStagedChangesResourceGroup');
};
const onConfigListener = filterEvent(workspace.onDidChangeConfiguration, e => e.affectsConfiguration('git.alwaysShowStagedChangesResourceGroup', root));
onConfigListener(updateIndexGroupVisibility, this, this.disposables);
updateIndexGroupVisibility();
this.mergeGroup.hideWhenEmpty = true;
this.indexGroup.hideWhenEmpty = true;
this.disposables.push(this.mergeGroup);
this.disposables.push(this.indexGroup);
@@ -615,19 +654,20 @@ export class Repository implements Disposable {
end = match ? match.index : text.length;
const line = text.substring(start, end);
const threshold = Math.max(config.get<number>('inputValidationLength') || 72, 0) || 72;
if (line.length <= Repository.InputValidationLength) {
if (line.length <= threshold) {
if (setting !== 'always') {
return;
}
return {
message: localize('commitMessageCountdown', "{0} characters left in current line", Repository.InputValidationLength - line.length),
message: localize('commitMessageCountdown', "{0} characters left in current line", threshold - line.length),
type: SourceControlInputBoxValidationType.Information
};
} else {
return {
message: localize('commitMessageWarning', "{0} characters over {1} in current line", line.length - Repository.InputValidationLength, Repository.InputValidationLength),
message: localize('commitMessageWarning', "{0} characters over {1} in current line", line.length - threshold, threshold),
type: SourceControlInputBoxValidationType.Warning
};
}
@@ -649,19 +689,67 @@ export class Repository implements Disposable {
}
}
getConfigs(): Promise<{ key: string; value: string; }[]> {
return this.run(Operation.Config, () => this.repository.getConfigs('local'));
}
getConfig(key: string): Promise<string> {
return this.run(Operation.Config, () => this.repository.config('local', key));
}
setConfig(key: string, value: string): Promise<string> {
return this.run(Operation.Config, () => this.repository.config('local', key, value));
}
@throttle
async status(): Promise<void> {
await this.run(Operation.Status);
}
diff(path: string, options: DiffOptions = {}): Promise<string> {
return this.run(Operation.Diff, () => this.repository.diff(path, options));
diff(cached?: boolean): Promise<string> {
return this.run(Operation.Diff, () => this.repository.diff(cached));
}
diffWithHEAD(path: string): Promise<string> {
return this.run(Operation.Diff, () => this.repository.diffWithHEAD(path));
}
diffWith(ref: string, path: string): Promise<string> {
return this.run(Operation.Diff, () => this.repository.diffWith(ref, path));
}
diffIndexWithHEAD(path: string): Promise<string> {
return this.run(Operation.Diff, () => this.repository.diffIndexWithHEAD(path));
}
diffIndexWith(ref: string, path: string): Promise<string> {
return this.run(Operation.Diff, () => this.repository.diffIndexWith(ref, path));
}
diffBlobs(object1: string, object2: string): Promise<string> {
return this.run(Operation.Diff, () => this.repository.diffBlobs(object1, object2));
}
diffBetween(ref1: string, ref2: string, path: string): Promise<string> {
return this.run(Operation.Diff, () => this.repository.diffBetween(ref1, ref2, path));
}
getMergeBase(ref1: string, ref2: string): Promise<string> {
return this.run(Operation.MergeBase, () => this.repository.getMergeBase(ref1, ref2));
}
async hashObject(data: string): Promise<string> {
return this.run(Operation.HashObject, () => this.repository.hashObject(data));
}
async add(resources: Uri[]): Promise<void> {
await this.run(Operation.Add, () => this.repository.add(resources.map(r => r.fsPath)));
}
async rm(resources: Uri[]): Promise<void> {
await this.run(Operation.Remove, () => this.repository.rm(resources.map(r => r.fsPath)));
}
async stage(resource: Uri, contents: string): Promise<void> {
const relativePath = path.relative(this.repository.root, resource.fsPath).replace(/\\/g, '/');
await this.run(Operation.Stage, () => this.repository.stage(relativePath, contents));
@@ -745,8 +833,8 @@ export class Repository implements Disposable {
});
}
async branch(name: string): Promise<void> {
await this.run(Operation.Branch, () => this.repository.branch(name, true));
async branch(name: string, _checkout: boolean, _ref?: string): Promise<void> {
await this.run(Operation.Branch, () => this.repository.branch(name, _checkout, _ref));
}
async deleteBranch(name: string, force?: boolean): Promise<void> {
@@ -757,6 +845,14 @@ export class Repository implements Disposable {
await this.run(Operation.RenameBranch, () => this.repository.renameBranch(name));
}
async getBranch(name: string): Promise<Branch> {
return await this.run(Operation.GetBranch, () => this.repository.getBranch(name));
}
async setBranchUpstream(name: string, upstream: string): Promise<void> {
await this.run(Operation.SetBranchUpstream, () => this.repository.setBranchUpstream(name, upstream));
}
async merge(ref: string): Promise<void> {
await this.run(Operation.Merge, () => this.repository.merge(ref));
}
@@ -769,6 +865,10 @@ export class Repository implements Disposable {
await this.run(Operation.Checkout, () => this.repository.checkout(treeish, []));
}
async checkoutTracking(treeish: string): Promise<void> {
await this.run(Operation.CheckoutTracking, () => this.repository.checkout(treeish, [], { track: true }));
}
async getCommit(ref: string): Promise<Commit> {
return await this.repository.getCommit(ref);
}
@@ -781,11 +881,33 @@ export class Repository implements Disposable {
await this.run(Operation.DeleteRef, () => this.repository.deleteRef(ref));
}
async addRemote(name: string, url: string): Promise<void> {
await this.run(Operation.Remote, () => this.repository.addRemote(name, url));
}
async removeRemote(name: string): Promise<void> {
await this.run(Operation.Remote, () => this.repository.removeRemote(name));
}
@throttle
async fetch(): Promise<void> {
async fetchDefault(): Promise<void> {
await this.run(Operation.Fetch, () => this.repository.fetch());
}
@throttle
async fetchPrune(): Promise<void> {
await this.run(Operation.Fetch, () => this.repository.fetch({ prune: true }));
}
@throttle
async fetchAll(): Promise<void> {
await this.run(Operation.Fetch, () => this.repository.fetch({ all: true }));
}
async fetch(remote?: string, ref?: string): Promise<void> {
await this.run(Operation.Fetch, () => this.repository.fetch({ remote, ref }));
}
@throttle
async pullWithRebase(head: Branch | undefined): Promise<void> {
let remote: string | undefined;
@@ -796,11 +918,18 @@ export class Repository implements Disposable {
branch = `${head.upstream.name}`;
}
await this.run(Operation.Pull, () => this.repository.pull(true, remote, branch));
const config = workspace.getConfiguration('git', Uri.file(this.root));
const fetchOnPull = config.get<boolean>('fetchOnPull');
if (fetchOnPull) {
await this.run(Operation.Pull, () => this.repository.pull(true));
} else {
await this.run(Operation.Pull, () => this.repository.pull(true, remote, branch));
}
}
@throttle
async pull(head: Branch | undefined): Promise<void> {
async pull(head?: Branch): Promise<void> {
let remote: string | undefined;
let branch: string | undefined;
@@ -809,15 +938,29 @@ export class Repository implements Disposable {
branch = `${head.upstream.name}`;
}
await this.run(Operation.Pull, () => this.repository.pull(false, remote, branch));
const config = workspace.getConfiguration('git', Uri.file(this.root));
const fetchOnPull = config.get<boolean>('fetchOnPull');
if (fetchOnPull) {
await this.run(Operation.Pull, () => this.repository.pull(false));
} else {
await this.run(Operation.Pull, () => this.repository.pull(false, remote, branch));
}
}
async pullFrom(rebase?: boolean, remote?: string, branch?: string): Promise<void> {
await this.run(Operation.Pull, () => this.repository.pull(rebase, remote, branch));
const config = workspace.getConfiguration('git', Uri.file(this.root));
const fetchOnPull = config.get<boolean>('fetchOnPull');
if (fetchOnPull) {
await this.run(Operation.Pull, () => this.repository.pull(rebase));
} else {
await this.run(Operation.Pull, () => this.repository.pull(rebase, remote, branch));
}
}
@throttle
async push(head: Branch): Promise<void> {
async push(head: Branch, forcePushMode?: ForcePushMode): Promise<void> {
let remote: string | undefined;
let branch: string | undefined;
@@ -826,15 +969,15 @@ export class Repository implements Disposable {
branch = `${head.name}:${head.upstream.name}`;
}
await this.run(Operation.Push, () => this.repository.push(remote, branch));
await this.run(Operation.Push, () => this.repository.push(remote, branch, undefined, undefined, forcePushMode));
}
async pushTo(remote?: string, name?: string, setUpstream: boolean = false): Promise<void> {
await this.run(Operation.Push, () => this.repository.push(remote, name, setUpstream));
async pushTo(remote?: string, name?: string, setUpstream: boolean = false, forcePushMode?: ForcePushMode): Promise<void> {
await this.run(Operation.Push, () => this.repository.push(remote, name, setUpstream, undefined, forcePushMode));
}
async pushTags(remote?: string): Promise<void> {
await this.run(Operation.Push, () => this.repository.push(remote, undefined, false, true));
async pushTags(remote?: string, forcePushMode?: ForcePushMode): Promise<void> {
await this.run(Operation.Push, () => this.repository.push(remote, undefined, false, true, forcePushMode));
}
@throttle
@@ -859,7 +1002,14 @@ export class Repository implements Disposable {
}
await this.run(Operation.Sync, async () => {
await this.repository.pull(rebase, remoteName, pullBranch);
const config = workspace.getConfiguration('git', Uri.file(this.root));
const fetchOnPull = config.get<boolean>('fetchOnPull');
if (fetchOnPull) {
await this.repository.pull(rebase);
} else {
await this.repository.pull(rebase, remoteName, pullBranch);
}
const remote = this.remotes.find(r => r.name === remoteName);
@@ -910,6 +1060,10 @@ export class Repository implements Disposable {
return this.run(Operation.Show, () => this.repository.detectObjectType(object));
}
async apply(patch: string, reverse?: boolean): Promise<void> {
return await this.run(Operation.Apply, () => this.repository.apply(patch, reverse));
}
async getStashes(): Promise<Stash[]> {
return await this.repository.getStashes();
}
@@ -922,6 +1076,10 @@ export class Repository implements Disposable {
return await this.run(Operation.Stash, () => this.repository.popStash(index));
}
async applyStash(index?: number): Promise<void> {
return await this.run(Operation.Stash, () => this.repository.applyStash(index));
}
async getCommitTemplate(): Promise<string> {
return await this.run(Operation.GetCommitTemplate, async () => this.repository.getCommitTemplate());
}
@@ -1009,7 +1167,7 @@ export class Repository implements Disposable {
this._onRunOperation.fire(operation);
try {
const result = await this.retryRun(runOperation);
const result = await this.retryRun(operation, runOperation);
if (!isReadOnly(operation)) {
await this.updateModelState();
@@ -1030,7 +1188,7 @@ export class Repository implements Disposable {
}
}
private async retryRun<T>(runOperation: () => Promise<T> = () => Promise.resolve<any>(null)): Promise<T> {
private async retryRun<T>(operation: Operation, runOperation: () => Promise<T> = () => Promise.resolve<any>(null)): Promise<T> {
let attempt = 0;
while (true) {
@@ -1038,7 +1196,12 @@ export class Repository implements Disposable {
attempt++;
return await runOperation();
} catch (err) {
if (err.gitErrorCode === GitErrorCodes.RepositoryIsLocked && attempt <= 10) {
const shouldRetry = attempt <= 10 && (
(err.gitErrorCode === GitErrorCodes.RepositoryIsLocked)
|| ((operation === Operation.Pull || operation === Operation.Sync || operation === Operation.Fetch) && (err.gitErrorCode === GitErrorCodes.CantLockRef || err.gitErrorCode === GitErrorCodes.CantRebaseMultipleBranches))
);
if (shouldRetry) {
// quatratic backoff
await timeout(Math.pow(attempt, 2) * 50);
} else {
@@ -1125,6 +1288,7 @@ export class Repository implements Disposable {
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;
}
return undefined;
});
// set resource groups
@@ -1145,23 +1309,37 @@ export class Repository implements Disposable {
// Disable `Discard All Changes` for "fresh" repositories
// https://github.com/Microsoft/vscode/issues/43066
commands.executeCommand('setContext', 'gitFreshRepository', !this._HEAD || !this._HEAD.commit);
const isFreshRepository = !this._HEAD || !this._HEAD.commit;
if (this.isFreshRepository !== isFreshRepository) {
commands.executeCommand('setContext', 'gitFreshRepository', isFreshRepository);
this.isFreshRepository = isFreshRepository;
}
this._onDidChangeStatus.fire();
}
private async getRebaseCommit(): Promise<Commit | undefined> {
const rebaseHeadPath = path.join(this.repository.root, '.git', 'REBASE_HEAD');
const rebaseApplyPath = path.join(this.repository.root, '.git', 'rebase-apply');
const rebaseMergePath = path.join(this.repository.root, '.git', 'rebase-merge');
try {
const rebaseHead = await new Promise<string>((c, e) => fs.readFile(rebaseHeadPath, 'utf8', (err, result) => err ? e(err) : c(result)));
const [rebaseApplyExists, rebaseMergePathExists, rebaseHead] = await Promise.all([
new Promise<boolean>(c => fs.exists(rebaseApplyPath, c)),
new Promise<boolean>(c => fs.exists(rebaseMergePath, c)),
new Promise<string>((c, e) => fs.readFile(rebaseHeadPath, 'utf8', (err, result) => err ? e(err) : c(result)))
]);
if (!rebaseApplyExists && !rebaseMergePathExists) {
return undefined;
}
return await this.getCommit(rebaseHead.trim());
} catch (err) {
return undefined;
}
}
private onFSChange(uri: Uri): void {
private onFSChange(_uri: Uri): void {
const config = workspace.getConfiguration('git');
const autorefresh = config.get<boolean>('autorefresh');

View File

@@ -3,8 +3,6 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { TextDocument, Range, LineChange, Selection } from 'vscode';
export function applyLineChanges(original: TextDocument, modified: TextDocument, diffs: LineChange[]): string {
@@ -21,9 +19,12 @@ export function applyLineChanges(original: TextDocument, modified: TextDocument,
let fromLine = diff.modifiedStartLineNumber - 1;
let fromCharacter = 0;
// if this is an insertion at the very end of the document,
// then we must start the next range after the last character of the
// previous line, in order to take the correct eol
if (isInsertion && diff.originalStartLineNumber === original.lineCount) {
fromLine = original.lineCount - 1;
fromCharacter = original.lineAt(fromLine).range.end.character;
fromLine -= 1;
fromCharacter = modified.lineAt(fromLine).range.end.character;
}
result.push(modified.getText(new Range(fromLine, fromCharacter, diff.modifiedEndLineNumber, 0)));
@@ -76,6 +77,8 @@ export function getModifiedRange(textDocument: TextDocument, diff: LineChange):
if (diff.modifiedEndLineNumber === 0) {
if (diff.modifiedStartLineNumber === 0) {
return new Range(textDocument.lineAt(diff.modifiedStartLineNumber).range.end, textDocument.lineAt(diff.modifiedStartLineNumber).range.start);
} else if (textDocument.lineCount === diff.modifiedStartLineNumber) {
return new Range(textDocument.lineAt(diff.modifiedStartLineNumber - 1).range.end, textDocument.lineAt(diff.modifiedStartLineNumber - 1).range.end);
} else {
return new Range(textDocument.lineAt(diff.modifiedStartLineNumber - 1).range.end, textDocument.lineAt(diff.modifiedStartLineNumber).range.start);
}

View File

@@ -3,13 +3,11 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { Disposable, Command, EventEmitter, Event } from 'vscode';
import { Branch } from './git';
import { Disposable, Command, EventEmitter, Event, workspace, Uri } from 'vscode';
import { Repository, Operation } from './repository';
import { anyEvent, dispose } from './util';
import * as nls from 'vscode-nls';
import { Branch } from './api/git';
const localize = nls.loadMessageBundle();
@@ -103,7 +101,11 @@ class SyncStatusBar {
if (HEAD.ahead || HEAD.behind) {
text += this.repository.syncLabel;
}
command = 'git.sync';
const config = workspace.getConfiguration('git', Uri.file(this.repository.root));
const rebaseWhenSync = config.get<string>('rebaseWhenSync');
command = rebaseWhenSync ? 'git.syncRebase' : 'git.sync';
tooltip = localize('sync changes', "Synchronize Changes");
} else {
icon = '$(cloud-upload)';

View File

@@ -3,8 +3,6 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import 'mocha';
import { GitStatusParser, parseGitCommit, parseGitmodules, parseLsTree, parseLsFiles } from '../git';
import * as assert from 'assert';

View File

@@ -3,7 +3,7 @@ declare module 'jschardet' {
encoding: string,
confidence: number
}
export function detect(buffer: NodeBuffer): IDetectedMap;
export function detect(buffer: Buffer): IDetectedMap;
export const Constants: {
MINIMUM_THRESHOLD: number,

View File

@@ -3,8 +3,6 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { Uri } from 'vscode';
export interface GitUriParams {

View File

@@ -3,8 +3,6 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { Event } from 'vscode';
import { dirname, sep } from 'path';
import { Readable } from 'stream';
@@ -35,7 +33,7 @@ export function combinedDisposable(disposables: IDisposable[]): IDisposable {
export const EmptyDisposable = toDisposable(() => null);
export function fireEvent<T>(event: Event<T>): Event<T> {
return (listener, thisArgs = null, disposables?) => event(_ => listener.call(thisArgs), null, disposables);
return (listener, thisArgs = null, disposables?) => event(_ => (listener as any).call(thisArgs), null, disposables);
}
export function mapEvent<I, O>(event: Event<I>, map: (i: I) => O): Event<O> {
@@ -46,6 +44,18 @@ export function filterEvent<T>(event: Event<T>, filter: (e: T) => boolean): Even
return (listener, thisArgs = null, disposables?) => event(e => filter(e) && listener.call(thisArgs, e), null, disposables);
}
export function latchEvent<T>(event: Event<T>): Event<T> {
let firstCall = true;
let cache: T;
return filterEvent(event, value => {
let shouldEmit = firstCall || value !== cache;
firstCall = false;
cache = value;
return shouldEmit;
});
}
export function anyEvent<T>(...events: Event<T>[]): Event<T> {
return (listener, thisArgs = null, disposables?) => {
const result = combinedDisposable(events.map(event => event(i => listener.call(thisArgs, i))));
@@ -268,7 +278,7 @@ export function readBytes(stream: Readable, bytes: number): Promise<Buffer> {
});
}
export enum Encoding {
export const enum Encoding {
UTF8 = 'utf8',
UTF16be = 'utf16be',
UTF16le = 'utf16le'

View File

@@ -1,17 +1,11 @@
{
"extends": "../shared.tsconfig.json",
"compilerOptions": {
"target": "es6",
"lib": [
"es2016"
],
"module": "commonjs",
"outDir": "./out",
"experimentalDecorators": true,
"typeRoots": [
"./node_modules/@types"
],
"strict": true,
"experimentalDecorators": true,
"noUnusedLocals": true
]
},
"include": [
"src/**/*"

View File

@@ -26,20 +26,20 @@
resolved "https://registry.yarnpkg.com/@types/node/-/node-8.0.51.tgz#b31d716fb8d58eeb95c068a039b9b6292817d5fb"
integrity sha512-El3+WJk2D/ppWNd2X05aiP5l2k4EwF7KwheknQZls+I26eSICoWRhRIJ56jGgw2dqNGQ5LtNajmBU2ajS28EvQ==
"@types/node@7.0.43":
version "7.0.43"
resolved "https://registry.yarnpkg.com/@types/node/-/node-7.0.43.tgz#a187e08495a075f200ca946079c914e1a5fe962c"
integrity sha512-7scYwwfHNppXvH/9JzakbVxk0o0QUILVk1Lv64GRaxwPuGpnF1QBiwdvhDpLcymb8BpomQL3KYoWKq3wUdDMhQ==
"@types/node@^8.10.25":
version "8.10.25"
resolved "https://registry.yarnpkg.com/@types/node/-/node-8.10.25.tgz#801fe4e39372cef18f268db880a5fbfcf71adc7e"
integrity sha512-WXvAXaknB0c2cJ7N44e1kUrVu5K90mSfPPaT5XxfuSMxEWva86EYIwxUZM3jNZ2P1CIC9e2z4WJqpAF69PQxeA==
"@types/which@^1.0.28":
version "1.0.28"
resolved "https://registry.yarnpkg.com/@types/which/-/which-1.0.28.tgz#016e387629b8817bed653fe32eab5d11279c8df6"
integrity sha1-AW44dim4gXvtZT/jLqtdESecjfY=
applicationinsights@1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/applicationinsights/-/applicationinsights-1.0.1.tgz#53446b830fe8d5d619eee2a278b31d3d25030927"
integrity sha1-U0Rrgw/o1dYZ7uKieLMdPSUDCSc=
applicationinsights@1.0.6:
version "1.0.6"
resolved "https://registry.yarnpkg.com/applicationinsights/-/applicationinsights-1.0.6.tgz#bc201810de91cea910dab34e8ad35ecde488edeb"
integrity sha512-VQT3kBpJVPw5fCO5n+WUeSx0VHjxFtD7znYbILBlVgOS9/cMDuGFmV2Br3ObzFyZUDGNbEfW36fD1y2/vAiCKw==
dependencies:
diagnostic-channel "0.2.0"
diagnostic-channel-publishers "0.2.1"
@@ -151,10 +151,12 @@ he@1.1.1:
resolved "https://registry.yarnpkg.com/he/-/he-1.1.1.tgz#93410fd21b009735151f8868c2f271f3427e23fd"
integrity sha1-k0EP0hsAlzUVH4howvJx80J+I/0=
iconv-lite@0.4.19:
version "0.4.19"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.19.tgz#f7468f60135f5e5dad3399c0a81be9a1603a082b"
integrity sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ==
iconv-lite@^0.4.24:
version "0.4.24"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==
dependencies:
safer-buffer ">= 2.1.2 < 3"
inflight@^1.0.4:
version "1.0.6"
@@ -294,6 +296,11 @@ path-is-absolute@^1.0.0:
resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18=
"safer-buffer@>= 2.1.2 < 3":
version "2.1.2"
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
semver@^5.3.0:
version "5.5.0"
resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.0.tgz#dc4bbc7a6ca9d916dee5d43516f0092b58f7b8ab"
@@ -306,17 +313,17 @@ supports-color@3.1.2:
dependencies:
has-flag "^1.0.0"
vscode-extension-telemetry@0.0.18:
version "0.0.18"
resolved "https://registry.yarnpkg.com/vscode-extension-telemetry/-/vscode-extension-telemetry-0.0.18.tgz#602ba20d8c71453aa34533a291e7638f6e5c0327"
integrity sha512-Vw3Sr+dZwl+c6PlsUwrTtCOJkgrmvS3OUVDQGcmpXWAgq9xGq6as0K4pUx+aGqTjzLAESmWSrs6HlJm6J6Khcg==
vscode-extension-telemetry@0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/vscode-extension-telemetry/-/vscode-extension-telemetry-0.1.0.tgz#3cdcb61d03829966bd04b5f11471a1e40d6abaad"
integrity sha512-WVCnP+uLxlqB6UD98yQNV47mR5Rf79LFxpuZhSPhEf0Sb4tPZed3a63n003/dchhOwyCTCBuNN4n8XKJkLEI1Q==
dependencies:
applicationinsights "1.0.1"
applicationinsights "1.0.6"
vscode-nls@^3.2.4:
version "3.2.4"
resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-3.2.4.tgz#2166b4183c8aea884d20727f5449e62be69fd398"
integrity sha512-FTjdqa4jDDoBjJqr36O8lmmZf/55kQ2w4ZY/+GL6K92fq765BqO3aYw21atnXUno/P04V5DWagNl4ybDIndJsw==
vscode-nls@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-4.0.0.tgz#4001c8a6caba5cedb23a9c5ce1090395c0e44002"
integrity sha512-qCfdzcH+0LgQnBpZA53bA32kzp9rpq/f66Som577ObeuDlFIrtbEJ+A/+CCxjIh4G8dpJYNCKIsxpRAHIfsbNw==
which@^1.3.0:
version "1.3.0"