mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 10:58:30 -05:00
Merge from master
This commit is contained in:
@@ -1,5 +1,8 @@
|
||||
src/**
|
||||
test/**
|
||||
out/test/**
|
||||
out/**
|
||||
tsconfig.json
|
||||
build/**
|
||||
build/**
|
||||
extension.webpack.config.js
|
||||
cgmanifest.json
|
||||
yarn.lock
|
||||
|
||||
@@ -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\"."
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -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);
|
||||
```
|
||||
66
extensions/git/cgmanifest.json
Normal file
66
extensions/git/cgmanifest.json
Normal 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
|
||||
}
|
||||
18
extensions/git/extension.webpack.config.js
Normal file
18
extensions/git/extension.webpack.config.js
Normal 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'
|
||||
}
|
||||
});
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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.",
|
||||
|
||||
@@ -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 |
@@ -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');
|
||||
}
|
||||
}
|
||||
208
extensions/git/src/api/api1.ts
Normal file
208
extensions/git/src/api/api1.ts
Normal 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) { }
|
||||
}
|
||||
76
extensions/git/src/api/extension.ts
Normal file
76
extensions/git/src/api/extension.ts
Normal 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
217
extensions/git/src/api/git.d.ts
vendored
Normal 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',
|
||||
}
|
||||
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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[]>;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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')
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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]);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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');
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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)';
|
||||
|
||||
@@ -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';
|
||||
|
||||
2
extensions/git/src/typings/jschardet.d.ts
vendored
2
extensions/git/src/typings/jschardet.d.ts
vendored
@@ -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,
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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/**/*"
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user