SQL Operations Studio Public Preview 1 (0.23) release source code

This commit is contained in:
Karl Burtram
2017-11-09 14:30:27 -08:00
parent b88ecb8d93
commit 3cdac41339
8829 changed files with 759707 additions and 286 deletions

View File

@@ -0,0 +1,5 @@
src/**
test/**
out/test/**
tsconfig.json
npm-shrinkwrap.json

View File

@@ -0,0 +1,29 @@
// 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."
]
}]

31
extensions/git/npm-shrinkwrap.json generated Normal file
View File

@@ -0,0 +1,31 @@
{
"name": "git",
"version": "0.0.1",
"dependencies": {
"applicationinsights": {
"version": "0.18.0",
"from": "applicationinsights@0.18.0",
"resolved": "https://registry.npmjs.org/applicationinsights/-/applicationinsights-0.18.0.tgz"
},
"iconv-lite": {
"version": "0.4.15",
"from": "iconv-lite@0.4.15",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.15.tgz"
},
"vscode-extension-telemetry": {
"version": "0.0.7",
"from": "vscode-extension-telemetry@>=0.0.8 <0.0.9",
"resolved": "https://registry.npmjs.org/vscode-extension-telemetry/-/vscode-extension-telemetry-0.0.8.tgz"
},
"vscode-nls": {
"version": "2.0.2",
"from": "vscode-nls@>=2.0.1 <3.0.0",
"resolved": "https://registry.npmjs.org/vscode-nls/-/vscode-nls-2.0.2.tgz"
},
"winreg": {
"version": "1.2.3",
"from": "winreg@1.2.3",
"resolved": "https://registry.npmjs.org/winreg/-/winreg-1.2.3.tgz"
}
}
}

801
extensions/git/package.json Normal file
View File

@@ -0,0 +1,801 @@
{
"name": "git",
"publisher": "vscode",
"displayName": "git",
"description": "Git",
"version": "0.0.1",
"engines": {
"vscode": "^1.5.0"
},
"aiKey": "AIF-d9b70cd4-b9f9-4d70-929b-a071c400b217",
"enableProposedApi": true,
"categories": [
"Other"
],
"activationEvents": [
"*"
],
"main": "./out/main",
"scripts": {
"compile": "gulp compile-extension:git",
"watch": "gulp watch-extension:git"
},
"contributes": {
"commands": [
{
"command": "git.clone",
"title": "%command.clone%",
"category": "Git"
},
{
"command": "git.init",
"title": "%command.init%",
"category": "Git",
"icon": {
"light": "resources/icons/light/git.svg",
"dark": "resources/icons/dark/git.svg"
}
},
{
"command": "git.refresh",
"title": "%command.refresh%",
"category": "Git",
"icon": {
"light": "resources/icons/light/refresh.svg",
"dark": "resources/icons/dark/refresh.svg"
}
},
{
"command": "git.openChange",
"title": "%command.openChange%",
"category": "Git",
"icon": {
"light": "resources/icons/light/open-change.svg",
"dark": "resources/icons/dark/open-change.svg"
}
},
{
"command": "git.openFile",
"title": "%command.openFile%",
"category": "Git",
"icon": {
"light": "resources/icons/light/open-file.svg",
"dark": "resources/icons/dark/open-file.svg"
}
},
{
"command": "git.openHEADFile",
"title": "%command.openHEADFile%",
"category": "Git"
},
{
"command": "git.stage",
"title": "%command.stage%",
"category": "Git",
"icon": {
"light": "resources/icons/light/stage.svg",
"dark": "resources/icons/dark/stage.svg"
}
},
{
"command": "git.stageAll",
"title": "%command.stageAll%",
"category": "Git",
"icon": {
"light": "resources/icons/light/stage.svg",
"dark": "resources/icons/dark/stage.svg"
}
},
{
"command": "git.stageSelectedRanges",
"title": "%command.stageSelectedRanges%",
"category": "Git"
},
{
"command": "git.revertSelectedRanges",
"title": "%command.revertSelectedRanges%",
"category": "Git"
},
{
"command": "git.unstage",
"title": "%command.unstage%",
"category": "Git",
"icon": {
"light": "resources/icons/light/unstage.svg",
"dark": "resources/icons/dark/unstage.svg"
}
},
{
"command": "git.unstageAll",
"title": "%command.unstageAll%",
"category": "Git",
"icon": {
"light": "resources/icons/light/unstage.svg",
"dark": "resources/icons/dark/unstage.svg"
}
},
{
"command": "git.unstageSelectedRanges",
"title": "%command.unstageSelectedRanges%",
"category": "Git"
},
{
"command": "git.clean",
"title": "%command.clean%",
"category": "Git",
"icon": {
"light": "resources/icons/light/clean.svg",
"dark": "resources/icons/dark/clean.svg"
}
},
{
"command": "git.cleanAll",
"title": "%command.cleanAll%",
"category": "Git",
"icon": {
"light": "resources/icons/light/clean.svg",
"dark": "resources/icons/dark/clean.svg"
}
},
{
"command": "git.commit",
"title": "%command.commit%",
"category": "Git",
"icon": {
"light": "resources/icons/light/check.svg",
"dark": "resources/icons/dark/check.svg"
}
},
{
"command": "git.commitStaged",
"title": "%command.commitStaged%",
"category": "Git"
},
{
"command": "git.commitStagedSigned",
"title": "%command.commitStagedSigned%",
"category": "Git"
},
{
"command": "git.commitStagedAmend",
"title": "%command.commitStagedAmend%",
"category": "Git"
},
{
"command": "git.commitAll",
"title": "%command.commitAll%",
"category": "Git"
},
{
"command": "git.commitAllSigned",
"title": "%command.commitAllSigned%",
"category": "Git"
},
{
"command": "git.commitAllAmend",
"title": "%command.commitAllAmend%",
"category": "Git"
},
{
"command": "git.undoCommit",
"title": "%command.undoCommit%",
"category": "Git"
},
{
"command": "git.checkout",
"title": "%command.checkout%",
"category": "Git"
},
{
"command": "git.branch",
"title": "%command.branch%",
"category": "Git"
},
{
"command": "git.deleteBranch",
"title": "%command.deleteBranch%",
"category": "Git"
},
{
"command": "git.merge",
"title": "%command.merge%",
"category": "Git"
},
{
"command": "git.createTag",
"title": "%command.createTag%",
"category": "Git"
},
{
"command": "git.pull",
"title": "%command.pull%",
"category": "Git"
},
{
"command": "git.pullRebase",
"title": "%command.pullRebase%",
"category": "Git"
},
{
"command": "git.pullFrom",
"title": "%command.pullFrom%",
"category": "Git"
},
{
"command": "git.push",
"title": "%command.push%",
"category": "Git"
},
{
"command": "git.pushTo",
"title": "%command.pushTo%",
"category": "Git"
},
{
"command": "git.pushWithTags",
"title": "%command.pushWithTags%",
"category": "Git"
},
{
"command": "git.sync",
"title": "%command.sync%",
"category": "Git"
},
{
"command": "git.publish",
"title": "%command.publish%",
"category": "Git"
},
{
"command": "git.showOutput",
"title": "%command.showOutput%",
"category": "Git"
},
{
"command": "git.ignore",
"title": "%command.ignore%",
"category": "Git"
},
{
"command": "git.stash",
"title": "%command.stash%",
"category": "Git"
},
{
"command": "git.stashPop",
"title": "%command.stashPop%",
"category": "Git"
},
{
"command": "git.stashPopLatest",
"title": "%command.stashPopLatest%",
"category": "Git"
}
],
"menus": {
"commandPalette": [
{
"command": "git.clone",
"when": "config.git.enabled"
},
{
"command": "git.init",
"when": "config.git.enabled"
},
{
"command": "git.refresh",
"when": "config.git.enabled && gitOpenRepositoryCount != 0"
},
{
"command": "git.openFile",
"when": "config.git.enabled && gitOpenRepositoryCount != 0"
},
{
"command": "git.openHEADFile",
"when": "config.git.enabled && gitOpenRepositoryCount != 0"
},
{
"command": "git.openChange",
"when": "config.git.enabled && gitOpenRepositoryCount != 0"
},
{
"command": "git.stage",
"when": "config.git.enabled && gitOpenRepositoryCount != 0"
},
{
"command": "git.stageAll",
"when": "config.git.enabled && gitOpenRepositoryCount != 0"
},
{
"command": "git.stageSelectedRanges",
"when": "config.git.enabled && gitOpenRepositoryCount != 0"
},
{
"command": "git.revertSelectedRanges",
"when": "config.git.enabled && gitOpenRepositoryCount != 0"
},
{
"command": "git.unstage",
"when": "config.git.enabled && gitOpenRepositoryCount != 0"
},
{
"command": "git.unstageAll",
"when": "config.git.enabled && gitOpenRepositoryCount != 0"
},
{
"command": "git.unstageSelectedRanges",
"when": "config.git.enabled && gitOpenRepositoryCount != 0"
},
{
"command": "git.clean",
"when": "config.git.enabled && gitOpenRepositoryCount != 0"
},
{
"command": "git.cleanAll",
"when": "config.git.enabled && gitOpenRepositoryCount != 0"
},
{
"command": "git.commit",
"when": "config.git.enabled && gitOpenRepositoryCount != 0"
},
{
"command": "git.commitStaged",
"when": "config.git.enabled && gitOpenRepositoryCount != 0"
},
{
"command": "git.commitStagedSigned",
"when": "config.git.enabled && gitOpenRepositoryCount != 0"
},
{
"command": "git.commitStagedAmend",
"when": "config.git.enabled && gitOpenRepositoryCount != 0"
},
{
"command": "git.commitAll",
"when": "config.git.enabled && gitOpenRepositoryCount != 0"
},
{
"command": "git.commitAllSigned",
"when": "config.git.enabled && gitOpenRepositoryCount != 0"
},
{
"command": "git.commitAllAmend",
"when": "config.git.enabled && gitOpenRepositoryCount != 0"
},
{
"command": "git.undoCommit",
"when": "config.git.enabled && gitOpenRepositoryCount != 0"
},
{
"command": "git.checkout",
"when": "config.git.enabled && gitOpenRepositoryCount != 0"
},
{
"command": "git.branch",
"when": "config.git.enabled && gitOpenRepositoryCount != 0"
},
{
"command": "git.deleteBranch",
"when": "config.git.enabled && gitOpenRepositoryCount != 0"
},
{
"command": "git.pull",
"when": "config.git.enabled && gitOpenRepositoryCount != 0"
},
{
"command": "git.pullFrom",
"when": "config.git.enabled && gitOpenRepositoryCount != 0"
},
{
"command": "git.pullRebase",
"when": "config.git.enabled && gitOpenRepositoryCount != 0"
},
{
"command": "git.pullFrom",
"when": "config.git.enabled && gitOpenRepositoryCount != 0"
},
{
"command": "git.merge",
"when": "config.git.enabled && gitOpenRepositoryCount != 0"
},
{
"command": "git.createTag",
"when": "config.git.enabled && gitOpenRepositoryCount != 0"
},
{
"command": "git.push",
"when": "config.git.enabled && gitOpenRepositoryCount != 0"
},
{
"command": "git.pushTo",
"when": "config.git.enabled && gitOpenRepositoryCount != 0"
},
{
"command": "git.pushWithTags",
"when": "config.git.enabled && gitOpenRepositoryCount != 0"
},
{
"command": "git.sync",
"when": "config.git.enabled && gitOpenRepositoryCount != 0"
},
{
"command": "git.publish",
"when": "config.git.enabled && gitOpenRepositoryCount != 0"
},
{
"command": "git.showOutput",
"when": "config.git.enabled && gitOpenRepositoryCount != 0"
},
{
"command": "git.ignore",
"when": "config.git.enabled && gitOpenRepositoryCount != 0"
},
{
"command": "git.stash",
"when": "config.git.enabled && gitOpenRepositoryCount != 0"
},
{
"command": "git.stashPop",
"when": "config.git.enabled && gitOpenRepositoryCount != 0"
},
{
"command": "git.stashPopLatest",
"when": "config.git.enabled && gitOpenRepositoryCount != 0"
}
],
"scm/title": [
{
"command": "git.init",
"group": "navigation",
"when": "config.git.enabled && !scmProvider && gitOpenRepositoryCount == 0"
},
{
"command": "git.commit",
"group": "navigation",
"when": "config.git.enabled && scmProvider == git"
},
{
"command": "git.refresh",
"group": "navigation",
"when": "config.git.enabled && scmProvider == git"
},
{
"command": "git.sync",
"group": "1_sync",
"when": "config.git.enabled && scmProvider == git"
},
{
"command": "git.pull",
"group": "1_sync",
"when": "config.git.enabled && scmProvider == git"
},
{
"command": "git.pullRebase",
"group": "1_sync",
"when": "config.git.enabled && scmProvider == git"
},
{
"command": "git.pullFrom",
"group": "1_sync",
"when": "config.git.enabled && scmProvider == git"
},
{
"command": "git.push",
"group": "1_sync",
"when": "config.git.enabled && scmProvider == git"
},
{
"command": "git.pushTo",
"group": "1_sync",
"when": "config.git.enabled && scmProvider == git"
},
{
"command": "git.publish",
"group": "2_publish",
"when": "config.git.enabled && scmProvider == git"
},
{
"command": "git.commitStaged",
"group": "3_commit",
"when": "config.git.enabled && scmProvider == git"
},
{
"command": "git.commitStagedSigned",
"group": "3_commit",
"when": "config.git.enabled && scmProvider == git"
},
{
"command": "git.commitStagedAmend",
"group": "3_commit",
"when": "config.git.enabled && scmProvider == git"
},
{
"command": "git.commitAll",
"group": "3_commit",
"when": "config.git.enabled && scmProvider == git"
},
{
"command": "git.commitAllSigned",
"group": "3_commit",
"when": "config.git.enabled && scmProvider == git"
},
{
"command": "git.commitAllAmend",
"group": "3_commit",
"when": "config.git.enabled && scmProvider == git"
},
{
"command": "git.undoCommit",
"group": "3_commit",
"when": "config.git.enabled && scmProvider == git"
},
{
"command": "git.unstageAll",
"group": "4_stage",
"when": "config.git.enabled && scmProvider == git"
},
{
"command": "git.cleanAll",
"group": "4_stage",
"when": "config.git.enabled && scmProvider == git"
},
{
"command": "git.stash",
"group": "5_stash",
"when": "config.git.enabled && scmProvider == git"
},
{
"command": "git.stashPop",
"group": "5_stash",
"when": "config.git.enabled && scmProvider == git"
},
{
"command": "git.stashPopLatest",
"group": "5_stash",
"when": "config.git.enabled && scmProvider == git"
},
{
"command": "git.showOutput",
"group": "7_repository",
"when": "config.git.enabled && scmProvider == git"
}
],
"scm/resourceGroup/context": [
{
"command": "git.stageAll",
"when": "config.git.enabled && scmProvider == git && scmResourceGroup == merge",
"group": "1_modification"
},
{
"command": "git.stageAll",
"when": "config.git.enabled && scmProvider == git && scmResourceGroup == merge",
"group": "inline"
},
{
"command": "git.unstageAll",
"when": "config.git.enabled && scmProvider == git && scmResourceGroup == index",
"group": "1_modification"
},
{
"command": "git.unstageAll",
"when": "config.git.enabled && scmProvider == git && scmResourceGroup == index",
"group": "inline"
},
{
"command": "git.cleanAll",
"when": "config.git.enabled && scmProvider == git && scmResourceGroup == workingTree",
"group": "1_modification"
},
{
"command": "git.stageAll",
"when": "config.git.enabled && scmProvider == git && scmResourceGroup == workingTree",
"group": "1_modification"
},
{
"command": "git.cleanAll",
"when": "config.git.enabled && scmProvider == git && scmResourceGroup == workingTree",
"group": "inline"
},
{
"command": "git.stageAll",
"when": "config.git.enabled && scmProvider == git && scmResourceGroup == workingTree",
"group": "inline"
}
],
"scm/resourceState/context": [
{
"command": "git.stage",
"when": "config.git.enabled && scmProvider == git && scmResourceGroup == merge",
"group": "1_modification"
},
{
"command": "git.stage",
"when": "config.git.enabled && scmProvider == git && scmResourceGroup == merge",
"group": "inline"
},
{
"command": "git.openChange",
"when": "config.git.enabled && scmProvider == git && scmResourceGroup == index",
"group": "navigation"
},
{
"command": "git.openFile",
"when": "config.git.enabled && scmProvider == git && scmResourceGroup == index",
"group": "navigation"
},
{
"command": "git.openHEADFile",
"when": "config.git.enabled && scmProvider == git && scmResourceGroup == index",
"group": "navigation"
},
{
"command": "git.unstage",
"when": "config.git.enabled && scmProvider == git && scmResourceGroup == index",
"group": "1_modification"
},
{
"command": "git.unstage",
"when": "config.git.enabled && scmProvider == git && scmResourceGroup == index",
"group": "inline"
},
{
"command": "git.openChange",
"when": "config.git.enabled && scmProvider == git && scmResourceGroup == workingTree",
"group": "navigation"
},
{
"command": "git.openHEADFile",
"when": "config.git.enabled && scmProvider == git && scmResourceGroup == workingTree",
"group": "navigation"
},
{
"command": "git.openFile",
"when": "config.git.enabled && scmProvider == git && scmResourceGroup == workingTree",
"group": "navigation"
},
{
"command": "git.stage",
"when": "config.git.enabled && scmProvider == git && scmResourceGroup == workingTree",
"group": "1_modification"
},
{
"command": "git.clean",
"when": "config.git.enabled && scmProvider == git && scmResourceGroup == workingTree",
"group": "1_modification"
},
{
"command": "git.clean",
"when": "config.git.enabled && scmProvider == git && scmResourceGroup == workingTree",
"group": "inline"
},
{
"command": "git.stage",
"when": "config.git.enabled && scmProvider == git && scmResourceGroup == workingTree",
"group": "inline"
},
{
"command": "git.ignore",
"when": "config.git.enabled && scmProvider == git && scmResourceGroup == workingTree",
"group": "1_modification@3"
}
],
"editor/title": [
{
"command": "git.openFile",
"group": "navigation",
"when": "config.git.enabled && gitOpenRepositoryCount != 0 && isInDiffEditor && resourceScheme != extension && resourceScheme != merge-conflict.conflict-diff"
},
{
"command": "git.openChange",
"group": "navigation",
"when": "config.git.enabled && gitOpenRepositoryCount != 0 && !isInDiffEditor && resourceScheme != extension"
},
{
"command": "git.stageSelectedRanges",
"group": "2_git@1",
"when": "config.git.enabled && gitOpenRepositoryCount != 0 && isInDiffEditor && resourceScheme != merge-conflict.conflict-diff"
},
{
"command": "git.unstageSelectedRanges",
"group": "2_git@2",
"when": "config.git.enabled && gitOpenRepositoryCount != 0 && isInDiffEditor && resourceScheme != merge-conflict.conflict-diff"
},
{
"command": "git.revertSelectedRanges",
"group": "2_git@3",
"when": "config.git.enabled && gitOpenRepositoryCount != 0 && isInDiffEditor && resourceScheme != merge-conflict.conflict-diff"
}
]
},
"configuration": {
"title": "Git",
"properties": {
"git.enabled": {
"type": "boolean",
"description": "%config.enabled%",
"default": true
},
"git.path": {
"type": [
"string",
"null"
],
"description": "%config.path%",
"default": null,
"isExecutable": true
},
"git.autorefresh": {
"type": "boolean",
"description": "%config.autorefresh%",
"default": true
},
"git.autofetch": {
"type": "boolean",
"description": "%config.autofetch%",
"default": true
},
"git.confirmSync": {
"type": "boolean",
"description": "%config.confirmSync%",
"default": true
},
"git.countBadge": {
"type": "string",
"enum": [
"all",
"tracked",
"off"
],
"description": "%config.countBadge%",
"default": "all"
},
"git.checkoutType": {
"type": "string",
"enum": [
"all",
"local",
"tags",
"remote"
],
"description": "%config.checkoutType%",
"default": "all"
},
"git.ignoreLegacyWarning": {
"type": "boolean",
"description": "%config.ignoreLegacyWarning%",
"default": false
},
"git.ignoreLimitWarning": {
"type": "boolean",
"description": "%config.ignoreLimitWarning%",
"default": false
},
"git.defaultCloneDirectory": {
"type": "string",
"default": null,
"description": "%config.defaultCloneDirectory%"
},
"git.enableSmartCommit": {
"type": "boolean",
"description": "%config.enableSmartCommit%",
"default": false
},
"git.enableCommitSigning": {
"type": "boolean",
"description": "%config.enableCommitSigning%",
"default": false
}
}
}
},
"dependencies": {
"iconv-lite": "0.4.15",
"vscode-extension-telemetry": "0.0.8",
"vscode-nls": "2.0.2"
},
"devDependencies": {
"@types/mocha": "^2.2.41",
"@types/node": "^7.0.4",
"mocha": "^3.2.0"
}
}

View File

@@ -0,0 +1,58 @@
{
"command.clone": "Clone",
"command.init": "Initialize Repository",
"command.close": "Close Repository",
"command.refresh": "Refresh",
"command.openChange": "Open Changes",
"command.openFile": "Open File",
"command.openHEADFile": "Open File (HEAD)",
"command.stage": "Stage Changes",
"command.stageAll": "Stage All Changes",
"command.stageSelectedRanges": "Stage Selected Ranges",
"command.revertSelectedRanges": "Revert Selected Ranges",
"command.unstage": "Unstage Changes",
"command.unstageAll": "Unstage All Changes",
"command.unstageSelectedRanges": "Unstage Selected Ranges",
"command.clean": "Discard Changes",
"command.cleanAll": "Discard All Changes",
"command.commit": "Commit",
"command.commitStaged": "Commit Staged",
"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.undoCommit": "Undo Last Commit",
"command.checkout": "Checkout to...",
"command.branch": "Create Branch...",
"command.deleteBranch": "Delete Branch...",
"command.merge": "Merge Branch...",
"command.createTag": "Create Tag",
"command.pull": "Pull",
"command.pullRebase": "Pull (Rebase)",
"command.pullFrom": "Pull from...",
"command.push": "Push",
"command.pushTo": "Push to...",
"command.pushWithTags": "Push With Tags",
"command.sync": "Sync",
"command.publish": "Publish Branch",
"command.showOutput": "Show Git Output",
"command.ignore": "Add File to .gitignore",
"command.stash": "Stash",
"command.stashPop": "Pop Stash...",
"command.stashPopLatest": "Pop Latest Stash",
"config.enabled": "Whether git is enabled",
"config.path": "Path to the git executable",
"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.confirmSync": "Confirm before synchronizing git repositories",
"config.countBadge": "Controls the git badge counter. `all` counts all changes. `tracked` counts only the tracked changes. `off` turns it off.",
"config.checkoutType": "Controls what type of branches are listed when running `Checkout to...`. `all` shows all refs, `local` shows only the local branchs, `tags` shows only tags and `remote` shows only remote branches.",
"config.ignoreLegacyWarning": "Ignores the legacy Git warning",
"config.ignoreLimitWarning": "Ignores the warning when there are too many changes in a repository",
"config.defaultCloneDirectory": "The default location where to clone a git repository",
"config.enableSmartCommit": "Commit all changes when there are no staged changes.",
"config.enableCommitSigning": "Enables commit signing with GPG.",
"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."
}

View File

@@ -0,0 +1 @@
<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="#C5C5C5" points="9,0 4.5,9 3,6 0,6 3,12 6,12 12,0"/></svg>

After

Width:  |  Height:  |  Size: 194 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="-0.994 0 16 16" enable-background="new -0.994 0 16 16"><path fill="#C5C5C5" d="M13 6c0 1.461-.636 2.846-1.746 3.797l-5.584 4.951-1.324-1.496 5.595-4.962c.678-.582 1.061-1.413 1.061-2.29 0-1.654-1.345-3-2.997-3-.71 0-1.399.253-1.938.713l-1.521 1.287h2.448l-1.998 2h-3.996v-4l1.998-2v2.692l1.775-1.504c.899-.766 2.047-1.188 3.232-1.188 2.754 0 4.995 2.243 4.995 5z"/></svg>

After

Width:  |  Height:  |  Size: 443 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32"><path d="M27.459 14.902l-10.439-10.439c-.296-.294-.672-.452-1.089-.452-.417 0-.793.157-1.089.452l-2.248 2.247 2.549 2.549c.249-.112.522-.177.813-.177 1.106 0 2.002.896 2.002 2.002 0 .291-.064.565-.176.814l2.311 2.336c.25-.111.633-.234.923-.234 1.106 0 2 .911 2 2.016s-.894 1.969-2 1.969c-1.105-.001-2.016-.751-2.016-1.985 0-.28.016-.462.119-.704l-2.373-2.374-.023.007v6.274c.747.295 1.277 1.026 1.277 1.875 0 1.105-.878 2.016-1.984 2.016-1.104 0-2.031-.926-2.031-2.031 0-.846.535-1.564 1.28-1.857l.001-6.25c-.762-.282-1.309-1.009-1.309-1.871 0-.28.059-.546.162-.788l-2.555-2.557-7.115 7.114c-.599.601-.601 1.576.001 2.178l10.44 10.518c.296.295.671.45 1.089.45.415 0 .796-.159 1.089-.45l10.391-10.471c.601-.599.599-1.576 0-2.177z" fill="#fff"/></svg>

After

Width:  |  Height:  |  Size: 812 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#C5C5C5" d="M15 4v6h-2.276c.113-.318.187-.653.226-1h1.05v-5h-2v-2h-4v2.051c-.347.038-.681.112-1 .225v-3.276h5l3 3zm-7 8.949v1.051h-6v-7h2.276c.126-.354.28-.693.485-1h-3.761v9h8v-2.051c-.166.02-.329.051-.5.051l-.5-.051z"/><path fill="#75BEFF" d="M12 8.5c0-1.933-1.567-3.5-3.5-3.5s-3.5 1.567-3.5 3.5 1.567 3.5 3.5 3.5c.711 0 1.369-.215 1.922-.578l3.578 3.578 1-1-3.578-3.578c.363-.553.578-1.211.578-1.922zm-3.5 2.5c-1.381 0-2.5-1.119-2.5-2.5s1.119-2.5 2.5-2.5 2.5 1.119 2.5 2.5-1.119 2.5-2.5 2.5z"/></svg>

After

Width:  |  Height:  |  Size: 578 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><polygon fill="#C5C5C5" points="10,2 7.414,2 8.414,3 9,3 9,3.586 9,4 9,4.414 9,6 12,6 12,13 4,13 4,8 3,8 3,14 13,14 13,5"/><polygon fill="#75BEFF" points="5,1 3,1 5,3 1,3 1,5 5,5 3,7 5,7 8,4"/></svg>

After

Width:  |  Height:  |  Size: 262 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path d="M13.451 5.609l-.579-.939-1.068.812-.076.094c-.335.415-.927 1.341-1.124 2.876l-.021.165.033.163.071.345c0 1.654-1.346 3-3 3-.795 0-1.545-.311-2.107-.868-.563-.567-.873-1.317-.873-2.111 0-1.431 1.007-2.632 2.351-2.929v2.926s2.528-2.087 2.984-2.461h.012l3.061-2.582-4.919-4.1h-1.137v2.404c-3.429.318-6.121 3.211-6.121 6.721 0 1.809.707 3.508 1.986 4.782 1.277 1.282 2.976 1.988 4.784 1.988 3.722 0 6.75-3.028 6.75-6.75 0-1.245-.349-2.468-1.007-3.536z" fill="#2D2D30"/><path d="M12.6 6.134l-.094.071c-.269.333-.746 1.096-.91 2.375.057.277.092.495.092.545 0 2.206-1.794 4-4 4-1.098 0-2.093-.445-2.817-1.164-.718-.724-1.163-1.718-1.163-2.815 0-2.206 1.794-4 4-4l.351.025v1.85s1.626-1.342 1.631-1.339l1.869-1.577-3.5-2.917v2.218l-.371-.03c-3.176 0-5.75 2.574-5.75 5.75 0 1.593.648 3.034 1.695 4.076 1.042 1.046 2.482 1.694 4.076 1.694 3.176 0 5.75-2.574 5.75-5.75-.001-1.106-.318-2.135-.859-3.012z" fill="#C5C5C5"/></svg>

After

Width:  |  Height:  |  Size: 986 B

View File

@@ -0,0 +1 @@
<svg width="16" height="16" xmlns="http://www.w3.org/2000/svg"><title>Layer 1</title><rect height="11" width="3" y="3" x="7" fill="#C5C5C5"/><rect height="3" width="11" y="7" x="3" fill="#C5C5C5"/></svg>

After

Width:  |  Height:  |  Size: 203 B

View File

@@ -0,0 +1,6 @@
<svg width="14px" height="14px" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
<rect fill="#3c8746" x="0" y="0" width="100" height="100" rx="35" ry="35"/>
<text x="50" y="75" font-size="75" text-anchor="middle" style="font-family: Menlo, Monaco, Consolas, &quot;Droid Sans Mono&quot;, &quot;Inconsolata&quot;, &quot;Courier New&quot;, monospace, &quot;Droid Sans Fallback&quot;;" fill="white">
A
</text>
</svg>

After

Width:  |  Height:  |  Size: 431 B

View File

@@ -0,0 +1,6 @@
<svg width="14px" height="14px" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
<rect fill="#7F4E7E" x="0" y="0" width="100" height="100" rx="35" ry="35"/>
<text x="50" y="75" font-size="75" text-anchor="middle" style="font-family: Menlo, Monaco, Consolas, &quot;Droid Sans Mono&quot;, &quot;Inconsolata&quot;, &quot;Courier New&quot;, monospace, &quot;Droid Sans Fallback&quot;;" fill="white">
C
</text>
</svg>

After

Width:  |  Height:  |  Size: 431 B

View File

@@ -0,0 +1,6 @@
<svg width="14px" height="14px" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
<rect fill="#692C77" x="0" y="0" width="100" height="100" rx="35" ry="35"/>
<text x="50" y="75" font-size="75" text-anchor="middle" style="font-family: Menlo, Monaco, Consolas, &quot;Droid Sans Mono&quot;, &quot;Inconsolata&quot;, &quot;Courier New&quot;, monospace, &quot;Droid Sans Fallback&quot;;" fill="white">
C
</text>
</svg>

After

Width:  |  Height:  |  Size: 431 B

View File

@@ -0,0 +1,6 @@
<svg width="14px" height="14px" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
<rect fill="#9E121D" x="0" y="0" width="100" height="100" rx="35" ry="35"/>
<text x="50" y="75" font-size="75" text-anchor="middle" style="font-family: Menlo, Monaco, Consolas, &quot;Droid Sans Mono&quot;, &quot;Inconsolata&quot;, &quot;Courier New&quot;, monospace, &quot;Droid Sans Fallback&quot;;" fill="white">
D
</text>
</svg>

After

Width:  |  Height:  |  Size: 431 B

View File

@@ -0,0 +1,6 @@
<svg width="14px" height="14px" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
<rect fill="#969696" x="0" y="0" width="100" height="100" rx="35" ry="35"/>
<text x="50" y="75" font-size="75" text-anchor="middle" style="font-family: Menlo, Monaco, Consolas, &quot;Droid Sans Mono&quot;, &quot;Inconsolata&quot;, &quot;Courier New&quot;, monospace, &quot;Droid Sans Fallback&quot;;" fill="white">
I
</text>
</svg>

After

Width:  |  Height:  |  Size: 431 B

View File

@@ -0,0 +1,6 @@
<svg width="14px" height="14px" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
<rect fill="#1B80B2" x="0" y="0" width="100" height="100" rx="35" ry="35"/>
<text x="50" y="75" font-size="75" text-anchor="middle" style="font-family: Menlo, Monaco, Consolas, &quot;Droid Sans Mono&quot;, &quot;Inconsolata&quot;, &quot;Courier New&quot;, monospace, &quot;Droid Sans Fallback&quot;;" fill="white">
M
</text>
</svg>

After

Width:  |  Height:  |  Size: 431 B

View File

@@ -0,0 +1,6 @@
<svg width="14px" height="14px" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
<rect fill="#CC6633" x="0" y="0" width="100" height="100" rx="35" ry="35"/>
<text x="50" y="75" font-size="75" text-anchor="middle" style="font-family: Menlo, Monaco, Consolas, &quot;Droid Sans Mono&quot;, &quot;Inconsolata&quot;, &quot;Courier New&quot;, monospace, &quot;Droid Sans Fallback&quot;;" fill="white">
R
</text>
</svg>

After

Width:  |  Height:  |  Size: 431 B

View File

@@ -0,0 +1,6 @@
<svg width="14px" height="14px" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
<rect fill="#6C6C6C" x="0" y="0" width="100" height="100" rx="35" ry="35"/>
<text x="50" y="75" font-size="75" text-anchor="middle" style="font-family: Menlo, Monaco, Consolas, &quot;Droid Sans Mono&quot;, &quot;Inconsolata&quot;, &quot;Courier New&quot;, monospace, &quot;Droid Sans Fallback&quot;;" fill="white">
U
</text>
</svg>

After

Width:  |  Height:  |  Size: 431 B

View File

@@ -0,0 +1 @@
<svg width="16" height="16" xmlns="http://www.w3.org/2000/svg"><title>Layer 1</title><rect height="3" width="11" y="7" x="3" fill="#C5C5C5"/></svg>

After

Width:  |  Height:  |  Size: 147 B

View File

@@ -0,0 +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>

After

Width:  |  Height:  |  Size: 220 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="-0.994 0 16 16" enable-background="new -0.994 0 16 16"><path fill="#424242" d="M13 6c0 1.461-.636 2.846-1.746 3.797l-5.584 4.951-1.324-1.496 5.595-4.962c.678-.582 1.061-1.413 1.061-2.29 0-1.654-1.345-3-2.997-3-.71 0-1.399.253-1.938.713l-1.521 1.287h2.448l-1.998 2h-3.996v-4l1.998-2v2.692l1.775-1.504c.899-.766 2.047-1.188 3.232-1.188 2.754 0 4.995 2.243 4.995 5z"/></svg>

After

Width:  |  Height:  |  Size: 443 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32"><defs><clipPath><path d="M.06 91.886h91.828v-91.886h-91.828v91.886z"/></clipPath><clipPath id="a"><path d="M0 0h92v92h-92v-92z"/></clipPath></defs><path d="M28.497 14.84l-11.354-11.353c-.653-.654-1.714-.654-2.368 0l-2.357 2.358 2.99 2.991c.695-.235 1.492-.077 2.046.477.557.558.713 1.361.473 2.059l2.882 2.882c.698-.241 1.502-.085 2.059.473.778.778.778 2.039 0 2.818-.779.779-2.04.779-2.819 0-.586-.586-.73-1.446-.434-2.167l-2.688-2.688v7.074c.19.094.369.219.527.377.778.778.778 2.039 0 2.819-.778.778-2.04.778-2.818 0-.778-.779-.778-2.04 0-2.819.192-.192.415-.338.653-.435v-7.14c-.237-.097-.46-.241-.653-.435-.589-.589-.731-1.455-.429-2.179l-2.948-2.949-7.785 7.785c-.654.655-.654 1.715 0 2.369l11.354 11.353c.654.654 1.714.654 2.369 0l11.3-11.301c.654-.654.654-1.715 0-2.369" fill="#424242" clip-path="url(#a)"/></svg>

After

Width:  |  Height:  |  Size: 883 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#656565" d="M15 4v6h-2.276c.113-.318.187-.653.226-1h1.05v-5h-2v-2h-4v2.051c-.347.038-.681.112-1 .225v-3.276h5l3 3zm-7 8.949v1.051h-6v-7h2.276c.126-.354.28-.693.485-1h-3.761v9h8v-2.051c-.166.02-.329.051-.5.051l-.5-.051z"/><path fill="#00539C" d="M12 8.5c0-1.933-1.567-3.5-3.5-3.5s-3.5 1.567-3.5 3.5 1.567 3.5 3.5 3.5c.711 0 1.369-.215 1.922-.578l3.578 3.578 1-1-3.578-3.578c.363-.553.578-1.211.578-1.922zm-3.5 2.5c-1.381 0-2.5-1.119-2.5-2.5s1.119-2.5 2.5-2.5 2.5 1.119 2.5 2.5-1.119 2.5-2.5 2.5z"/></svg>

After

Width:  |  Height:  |  Size: 578 B

View File

@@ -0,0 +1,3 @@
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
<!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/">
]><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><polygon fill="#656565" points="10,2 7.414,2 8.414,3 9,3 9,3.586 9,4 9,4.414 9,6 12,6 12,13 4,13 4,8 3,8 3,14 13,14 13,5"/><polygon fill="#00539C" points="5,1 3,1 5,3 1,3 1,5 5,5 3,7 5,7 8,4"/></svg>

After

Width:  |  Height:  |  Size: 417 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path d="M13.451 5.609l-.579-.939-1.068.812-.076.094c-.335.415-.927 1.341-1.124 2.876l-.021.165.033.163.071.345c0 1.654-1.346 3-3 3-.795 0-1.545-.311-2.107-.868-.563-.567-.873-1.317-.873-2.111 0-1.431 1.007-2.632 2.351-2.929v2.926s2.528-2.087 2.984-2.461h.012l3.061-2.582-4.919-4.1h-1.137v2.404c-3.429.318-6.121 3.211-6.121 6.721 0 1.809.707 3.508 1.986 4.782 1.277 1.282 2.976 1.988 4.784 1.988 3.722 0 6.75-3.028 6.75-6.75 0-1.245-.349-2.468-1.007-3.536z" fill="#F6F6F6"/><path d="M12.6 6.134l-.094.071c-.269.333-.746 1.096-.91 2.375.057.277.092.495.092.545 0 2.206-1.794 4-4 4-1.098 0-2.093-.445-2.817-1.164-.718-.724-1.163-1.718-1.163-2.815 0-2.206 1.794-4 4-4l.351.025v1.85s1.626-1.342 1.631-1.339l1.869-1.577-3.5-2.917v2.218l-.371-.03c-3.176 0-5.75 2.574-5.75 5.75 0 1.593.648 3.034 1.695 4.076 1.042 1.046 2.482 1.694 4.076 1.694 3.176 0 5.75-2.574 5.75-5.75-.001-1.106-.318-2.135-.859-3.012z" fill="#424242"/></svg>

After

Width:  |  Height:  |  Size: 986 B

View File

@@ -0,0 +1 @@
<svg width="16" height="16" xmlns="http://www.w3.org/2000/svg"><title>Layer 1</title><rect height="11" width="3" y="3" x="7" fill="#424242"/><rect height="3" width="11" y="7" x="3" fill="#424242"/></svg>

After

Width:  |  Height:  |  Size: 203 B

View File

@@ -0,0 +1,6 @@
<svg width="14px" height="14px" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
<rect fill="#2d883e" x="0" y="0" width="100" height="100" rx="35" ry="35"/>
<text x="50" y="75" font-size="75" text-anchor="middle" style="font-family: Menlo, Monaco, Consolas, &quot;Droid Sans Mono&quot;, &quot;Inconsolata&quot;, &quot;Courier New&quot;, monospace, &quot;Droid Sans Fallback&quot;;" fill="white">
A
</text>
</svg>

After

Width:  |  Height:  |  Size: 431 B

View File

@@ -0,0 +1,6 @@
<svg width="14px" height="14px" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
<rect fill="#9B4F96" x="0" y="0" width="100" height="100" rx="35" ry="35"/>
<text x="50" y="75" font-size="75" text-anchor="middle" style="font-family: Menlo, Monaco, Consolas, &quot;Droid Sans Mono&quot;, &quot;Inconsolata&quot;, &quot;Courier New&quot;, monospace, &quot;Droid Sans Fallback&quot;;" fill="white">
C
</text>
</svg>

After

Width:  |  Height:  |  Size: 431 B

View File

@@ -0,0 +1,6 @@
<svg width="14px" height="14px" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
<rect fill="#682079" x="0" y="0" width="100" height="100" rx="35" ry="35"/>
<text x="50" y="75" font-size="75" text-anchor="middle" style="font-family: Menlo, Monaco, Consolas, &quot;Droid Sans Mono&quot;, &quot;Inconsolata&quot;, &quot;Courier New&quot;, monospace, &quot;Droid Sans Fallback&quot;;" fill="white">
C
</text>
</svg>

After

Width:  |  Height:  |  Size: 431 B

View File

@@ -0,0 +1,6 @@
<svg width="14px" height="14px" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
<rect fill="#B9131A" x="0" y="0" width="100" height="100" rx="35" ry="35"/>
<text x="50" y="75" font-size="75" text-anchor="middle" style="font-family: Menlo, Monaco, Consolas, &quot;Droid Sans Mono&quot;, &quot;Inconsolata&quot;, &quot;Courier New&quot;, monospace, &quot;Droid Sans Fallback&quot;;" fill="white">
D
</text>
</svg>

After

Width:  |  Height:  |  Size: 431 B

View File

@@ -0,0 +1,6 @@
<svg width="14px" height="14px" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
<rect fill="#969696" x="0" y="0" width="100" height="100" rx="35" ry="35"/>
<text x="50" y="75" font-size="75" text-anchor="middle" style="font-family: Menlo, Monaco, Consolas, &quot;Droid Sans Mono&quot;, &quot;Inconsolata&quot;, &quot;Courier New&quot;, monospace, &quot;Droid Sans Fallback&quot;;" fill="white">
I
</text>
</svg>

After

Width:  |  Height:  |  Size: 431 B

View File

@@ -0,0 +1,6 @@
<svg width="14px" height="14px" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
<rect fill="#007ACC" x="0" y="0" width="100" height="100" rx="35" ry="35"/>
<text x="50" y="75" font-size="75" text-anchor="middle" style="font-family: Menlo, Monaco, Consolas, &quot;Droid Sans Mono&quot;, &quot;Inconsolata&quot;, &quot;Courier New&quot;, monospace, &quot;Droid Sans Fallback&quot;;" fill="white">
M
</text>
</svg>

After

Width:  |  Height:  |  Size: 431 B

View File

@@ -0,0 +1,6 @@
<svg width="14px" height="14px" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
<rect fill="#CC6633" x="0" y="0" width="100" height="100" rx="35" ry="35"/>
<text x="50" y="75" font-size="75" text-anchor="middle" style="font-family: Menlo, Monaco, Consolas, &quot;Droid Sans Mono&quot;, &quot;Inconsolata&quot;, &quot;Courier New&quot;, monospace, &quot;Droid Sans Fallback&quot;;" fill="white">
R
</text>
</svg>

After

Width:  |  Height:  |  Size: 431 B

View File

@@ -0,0 +1,6 @@
<svg width="14px" height="14px" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
<rect fill="#6C6C6C" x="0" y="0" width="100" height="100" rx="35" ry="35"/>
<text x="50" y="75" font-size="75" text-anchor="middle" style="font-family: Menlo, Monaco, Consolas, &quot;Droid Sans Mono&quot;, &quot;Inconsolata&quot;, &quot;Courier New&quot;, monospace, &quot;Droid Sans Fallback&quot;;" fill="white">
U
</text>
</svg>

After

Width:  |  Height:  |  Size: 431 B

View File

@@ -0,0 +1 @@
<svg width="16" height="16" xmlns="http://www.w3.org/2000/svg"><title>Layer 1</title><rect height="3" width="11" y="7" x="3" fill="#424242"/></svg>

After

Width:  |  Height:  |  Size: 147 B

View File

@@ -0,0 +1,2 @@
#!/bin/sh
echo ''

View File

@@ -0,0 +1,74 @@
/*---------------------------------------------------------------------------------------------
* 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 * as http from 'http';
import * as fs from 'fs';
import * as nls from 'vscode-nls';
const localize = nls.loadMessageBundle();
function fatal(err: any): void {
console.error(localize('missOrInvalid', "Missing or invalid credentials."));
console.error(err);
process.exit(1);
}
function main(argv: string[]): void {
if (argv.length !== 5) {
return fatal('Wrong number of arguments');
}
if (!process.env['VSCODE_GIT_ASKPASS_HANDLE']) {
return fatal('Missing handle');
}
if (!process.env['VSCODE_GIT_ASKPASS_PIPE']) {
return fatal('Missing pipe');
}
if (process.env['VSCODE_GIT_COMMAND'] === 'fetch') {
return fatal('Skip fetch commands');
}
const output = process.env['VSCODE_GIT_ASKPASS_PIPE'];
const socketPath = process.env['VSCODE_GIT_ASKPASS_HANDLE'];
const request = argv[2];
const host = argv[4].substring(1, argv[4].length - 2);
const opts: http.RequestOptions = {
socketPath,
path: '/',
method: 'POST'
};
const req = http.request(opts, res => {
if (res.statusCode !== 200) {
return fatal(`Bad status code: ${res.statusCode}`);
}
const chunks: string[] = [];
res.setEncoding('utf8');
res.on('data', (d: string) => chunks.push(d));
res.on('end', () => {
const raw = chunks.join('');
try {
const result = JSON.parse(raw);
fs.writeFileSync(output, result + '\n');
} catch (err) {
return fatal(`Error parsing response`);
}
setTimeout(() => process.exit(0), 0);
});
});
req.on('error', () => fatal('Error in request'));
req.write(JSON.stringify({ request, host }));
req.end();
}
main(process.argv);

View File

@@ -0,0 +1,5 @@
#!/bin/sh
VSCODE_GIT_ASKPASS_PIPE=`mktemp`
VSCODE_GIT_ASKPASS_PIPE="$VSCODE_GIT_ASKPASS_PIPE" "$VSCODE_GIT_ASKPASS_NODE" "$VSCODE_GIT_ASKPASS_MAIN" $*
cat $VSCODE_GIT_ASKPASS_PIPE
rm $VSCODE_GIT_ASKPASS_PIPE

View File

@@ -0,0 +1,114 @@
/*---------------------------------------------------------------------------------------------
* 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 { Disposable, window, InputBoxOptions } from 'vscode';
import { denodeify } from './util';
import * as path from 'path';
import * as http from 'http';
import * as os from 'os';
import * as crypto from 'crypto';
const randomBytes = denodeify<Buffer>(crypto.randomBytes);
export interface AskpassEnvironment {
GIT_ASKPASS: string;
ELECTRON_RUN_AS_NODE?: string;
VSCODE_GIT_ASKPASS_NODE?: string;
VSCODE_GIT_ASKPASS_MAIN?: string;
VSCODE_GIT_ASKPASS_HANDLE?: string;
}
function getIPCHandlePath(nonce: string): string {
if (process.platform === 'win32') {
return `\\\\.\\pipe\\vscode-git-askpass-${nonce}-sock`;
}
if (process.env['XDG_RUNTIME_DIR']) {
return path.join(process.env['XDG_RUNTIME_DIR'], `vscode-git-askpass-${nonce}.sock`);
}
return path.join(os.tmpdir(), `vscode-git-askpass-${nonce}.sock`);
}
export class Askpass implements Disposable {
private server: http.Server;
private ipcHandlePathPromise: Promise<string>;
private enabled = true;
constructor() {
this.server = http.createServer((req, res) => this.onRequest(req, res));
this.ipcHandlePathPromise = this.setup().catch(err => {
console.error(err);
return '';
});
}
private async setup(): Promise<string> {
const buffer = await randomBytes(20);
const nonce = buffer.toString('hex');
const ipcHandlePath = getIPCHandlePath(nonce);
try {
this.server.listen(ipcHandlePath);
this.server.on('error', err => console.error(err));
} catch (err) {
console.error('Could not launch git askpass helper.');
this.enabled = false;
}
return ipcHandlePath;
}
private onRequest(req: http.ServerRequest, res: http.ServerResponse): void {
const chunks: string[] = [];
req.setEncoding('utf8');
req.on('data', (d: string) => chunks.push(d));
req.on('end', () => {
const { request, host } = JSON.parse(chunks.join(''));
this.prompt(host, request).then(result => {
res.writeHead(200);
res.end(JSON.stringify(result));
}, () => {
res.writeHead(500);
res.end();
});
});
}
private async prompt(host: string, request: string): Promise<string> {
const options: InputBoxOptions = {
password: /password/i.test(request),
placeHolder: request,
prompt: `Git: ${host}`,
ignoreFocusOut: true
};
return await window.showInputBox(options) || '';
}
async getEnv(): Promise<AskpassEnvironment> {
if (!this.enabled) {
return {
GIT_ASKPASS: path.join(__dirname, 'askpass-empty.sh')
};
}
return {
ELECTRON_RUN_AS_NODE: '1',
GIT_ASKPASS: path.join(__dirname, 'askpass.sh'),
VSCODE_GIT_ASKPASS_NODE: process.execPath,
VSCODE_GIT_ASKPASS_MAIN: path.join(__dirname, 'askpass-main.js'),
VSCODE_GIT_ASKPASS_HANDLE: await this.ipcHandlePathPromise
};
}
dispose(): void {
this.server.close();
}
}

View File

@@ -0,0 +1,62 @@
/*---------------------------------------------------------------------------------------------
* 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 { workspace, Disposable } from 'vscode';
import { GitErrorCodes } from './git';
import { Repository } from './repository';
import { throttle } from './decorators';
export class AutoFetcher {
private static Period = 3 * 60 * 1000 /* three minutes */;
private disposables: Disposable[] = [];
private timer: NodeJS.Timer;
constructor(private repository: Repository) {
workspace.onDidChangeConfiguration(this.onConfiguration, this, this.disposables);
this.onConfiguration();
}
private onConfiguration(): void {
const gitConfig = workspace.getConfiguration('git');
if (gitConfig.get<boolean>('autofetch') === false) {
this.disable();
} else {
this.enable();
}
}
enable(): void {
if (this.timer) {
return;
}
this.fetch();
this.timer = setInterval(() => this.fetch(), AutoFetcher.Period);
}
disable(): void {
clearInterval(this.timer);
}
@throttle
private async fetch(): Promise<void> {
try {
await this.repository.fetch();
} catch (err) {
if (err.gitErrorCode === GitErrorCodes.AuthenticationFailed) {
this.disable();
}
}
}
dispose(): void {
this.disable();
this.disposables.forEach(d => d.dispose());
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,117 @@
/*---------------------------------------------------------------------------------------------
* 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 { workspace, Uri, Disposable, Event, EventEmitter, window } from 'vscode';
import { debounce } from './decorators';
import { fromGitUri } from './uri';
import { Model, ModelChangeEvent } from './model';
interface CacheRow {
uri: Uri;
timestamp: number;
}
interface Cache {
[uri: string]: CacheRow;
}
const THREE_MINUTES = 1000 * 60 * 3;
const FIVE_MINUTES = 1000 * 60 * 5;
export class GitContentProvider {
private onDidChangeEmitter = new EventEmitter<Uri>();
get onDidChange(): Event<Uri> { return this.onDidChangeEmitter.event; }
private changedRepositoryRoots = new Set<string>();
private cache: Cache = Object.create(null);
private disposables: Disposable[] = [];
constructor(private model: Model) {
this.disposables.push(
model.onDidChangeRepository(this.onDidChangeRepository, this),
workspace.registerTextDocumentContentProvider('git', this)
);
setInterval(() => this.cleanup(), FIVE_MINUTES);
}
private onDidChangeRepository({ repository }: ModelChangeEvent): void {
this.changedRepositoryRoots.add(repository.root);
this.eventuallyFireChangeEvents();
}
@debounce(1100)
private eventuallyFireChangeEvents(): void {
this.fireChangeEvents();
}
private fireChangeEvents(): void {
Object.keys(this.cache).forEach(key => {
const uri = this.cache[key].uri;
const fsPath = uri.fsPath;
for (const root of this.changedRepositoryRoots) {
if (fsPath.startsWith(root)) {
this.onDidChangeEmitter.fire(uri);
return;
}
}
});
this.changedRepositoryRoots.clear();
}
async provideTextDocumentContent(uri: Uri): Promise<string> {
const repository = this.model.getRepository(uri);
if (!repository) {
return '';
}
const cacheKey = uri.toString();
const timestamp = new Date().getTime();
const cacheValue = { uri, timestamp };
this.cache[cacheKey] = cacheValue;
let { path, ref } = fromGitUri(uri);
if (ref === '~') {
const fileUri = Uri.file(path);
const uriString = fileUri.toString();
const [indexStatus] = repository.indexGroup.resourceStates.filter(r => r.original.toString() === uriString);
ref = indexStatus ? '' : 'HEAD';
}
try {
return await repository.show(ref, path);
} catch (err) {
return '';
}
}
private cleanup(): void {
const now = new Date().getTime();
const cache = Object.create(null);
Object.keys(this.cache).forEach(key => {
const row = this.cache[key];
const isOpen = window.visibleTextEditors.some(e => e.document.uri.fsPath === row.uri.fsPath);
if (isOpen || now - row.timestamp < THREE_MINUTES) {
cache[row.uri.toString()] = row;
}
});
this.cache = cache;
}
dispose(): void {
this.disposables.forEach(d => d.dispose());
}
}

View File

@@ -0,0 +1,103 @@
/*---------------------------------------------------------------------------------------------
* 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 { done } from './util';
function decorate(decorator: (fn: Function, key: string) => Function): Function {
return (target: any, key: string, descriptor: any) => {
let fnKey: string | null = null;
let fn: Function | null = null;
if (typeof descriptor.value === 'function') {
fnKey = 'value';
fn = descriptor.value;
} else if (typeof descriptor.get === 'function') {
fnKey = 'get';
fn = descriptor.get;
}
if (!fn || !fnKey) {
throw new Error('not supported');
}
descriptor[fnKey] = decorator(fn, key);
};
}
function _memoize(fn: Function, key: string): Function {
const memoizeKey = `$memoize$${key}`;
return function (...args: any[]) {
if (!this.hasOwnProperty(memoizeKey)) {
Object.defineProperty(this, memoizeKey, {
configurable: false,
enumerable: false,
writable: false,
value: fn.apply(this, args)
});
}
return this[memoizeKey];
};
}
export const memoize = decorate(_memoize);
function _throttle<T>(fn: Function, key: string): Function {
const currentKey = `$throttle$current$${key}`;
const nextKey = `$throttle$next$${key}`;
const trigger = function (...args: any[]) {
if (this[nextKey]) {
return this[nextKey];
}
if (this[currentKey]) {
this[nextKey] = done(this[currentKey]).then(() => {
this[nextKey] = undefined;
return trigger.apply(this, args);
});
return this[nextKey];
}
this[currentKey] = fn.apply(this, args) as Promise<T>;
const clear = () => this[currentKey] = undefined;
done(this[currentKey]).then(clear, clear);
return this[currentKey];
};
return trigger;
}
export const throttle = decorate(_throttle);
function _sequentialize<T>(fn: Function, key: string): Function {
const currentKey = `__$sequence$${key}`;
return function (...args: any[]) {
const currentPromise = this[currentKey] as Promise<any> || Promise.resolve(null);
const run = async () => await fn.apply(this, args);
this[currentKey] = currentPromise.then(run, run);
return this[currentKey];
};
}
export const sequentialize = decorate(_sequentialize);
export function debounce(delay: number): Function {
return decorate((fn, key) => {
const timerKey = `$debounce$${key}`;
return function (...args: any[]) {
clearTimeout(this[timerKey]);
this[timerKey] = setTimeout(() => fn.apply(this, args), delay);
};
});
}

1067
extensions/git/src/git.ts Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,57 @@
/*---------------------------------------------------------------------------------------------
* 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);
}

104
extensions/git/src/main.ts Normal file
View File

@@ -0,0 +1,104 @@
/*---------------------------------------------------------------------------------------------
* 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 * as nls from 'vscode-nls';
const localize = nls.config(process.env.VSCODE_NLS_CONFIG)();
import { ExtensionContext, workspace, window, Disposable, commands, Uri } from 'vscode';
import { findGit, Git, IGit } from './git';
import { Model } from './model';
import { CommandCenter } from './commands';
import { GitContentProvider } from './contentProvider';
import { Askpass } from './askpass';
import { toDisposable } from './util';
import TelemetryReporter from 'vscode-extension-telemetry';
async function init(context: ExtensionContext, disposables: Disposable[]): Promise<void> {
const { name, version, aiKey } = require(context.asAbsolutePath('./package.json')) as { name: string, version: string, aiKey: string };
const telemetryReporter: TelemetryReporter = new TelemetryReporter(name, version, aiKey);
disposables.push(telemetryReporter);
const outputChannel = window.createOutputChannel('Git');
disposables.push(outputChannel);
const config = workspace.getConfiguration('git');
const enabled = config.get<boolean>('enabled') === true;
const pathHint = workspace.getConfiguration('git').get<string>('path');
const info = await findGit(pathHint);
const askpass = new Askpass();
const env = await askpass.getEnv();
const git = new Git({ gitPath: info.path, version: info.version, env });
const model = new Model(git);
disposables.push(model);
const onRepository = () => commands.executeCommand('setContext', 'gitOpenRepositoryCount', `${model.repositories.length}`);
model.onDidOpenRepository(onRepository, null, disposables);
model.onDidCloseRepository(onRepository, null, disposables);
onRepository();
if (!enabled) {
const commandCenter = new CommandCenter(git, model, outputChannel, telemetryReporter);
disposables.push(commandCenter);
return;
}
outputChannel.appendLine(localize('using git', "Using git {0} from {1}", info.version, info.path));
const onOutput = str => outputChannel.append(str);
git.onOutput.addListener('log', onOutput);
disposables.push(toDisposable(() => git.onOutput.removeListener('log', onOutput)));
disposables.push(
new CommandCenter(git, model, outputChannel, telemetryReporter),
new GitContentProvider(model),
);
await checkGitVersion(info);
}
export function activate(context: ExtensionContext): any {
const disposables: Disposable[] = [];
context.subscriptions.push(new Disposable(() => Disposable.from(...disposables).dispose()));
init(context, disposables)
.catch(err => console.error(err));
}
async function checkGitVersion(info: IGit): Promise<void> {
// {{SQL CARBON EDIT}}
// remove Git version check on since for Carbon
return;
/*
const config = workspace.getConfiguration('git');
const shouldIgnore = config.get<boolean>('ignoreLegacyWarning') === true;
if (shouldIgnore) {
return;
}
if (!/^[01]/.test(info.version)) {
return;
}
const update = localize('updateGit', "Update Git");
const neverShowAgain = localize('neverShowAgain', "Don't show again");
const choice = await window.showWarningMessage(
localize('git20', "You seem to have git {0} installed. Code works best with git >= 2", info.version),
update,
neverShowAgain
);
if (choice === update) {
commands.executeCommand('vscode.open', Uri.parse('https://git-scm.com/'));
} else if (choice === neverShowAgain) {
await config.update('ignoreLegacyWarning', true, true);
}
*/
}

291
extensions/git/src/model.ts Normal file
View File

@@ -0,0 +1,291 @@
/*---------------------------------------------------------------------------------------------
* 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 { workspace, WorkspaceFoldersChangeEvent, Uri, window, Event, EventEmitter, QuickPickItem, Disposable, SourceControl, SourceControlResourceGroup, TextEditor } from 'vscode';
import { Repository, RepositoryState } from './repository';
import { memoize, sequentialize, debounce } from './decorators';
import { dispose, anyEvent, filterEvent } from './util';
import { Git, GitErrorCodes } from './git';
import * as path from 'path';
import * as fs from 'fs';
import * as nls from 'vscode-nls';
const localize = nls.loadMessageBundle();
class RepositoryPick implements QuickPickItem {
@memoize get label(): string { return path.basename(this.repository.root); }
@memoize get description(): string { return path.dirname(this.repository.root); }
constructor(public readonly repository: Repository) { }
}
export interface ModelChangeEvent {
repository: Repository;
uri: Uri;
}
interface OpenRepository extends Disposable {
repository: Repository;
}
export class Model {
private _onDidOpenRepository = new EventEmitter<Repository>();
readonly onDidOpenRepository: Event<Repository> = this._onDidOpenRepository.event;
private _onDidCloseRepository = new EventEmitter<Repository>();
readonly onDidCloseRepository: Event<Repository> = this._onDidCloseRepository.event;
private _onDidChangeRepository = new EventEmitter<ModelChangeEvent>();
readonly onDidChangeRepository: Event<ModelChangeEvent> = this._onDidChangeRepository.event;
private openRepositories: OpenRepository[] = [];
get repositories(): Repository[] { return this.openRepositories.map(r => r.repository); }
private possibleGitRepositoryPaths = new Set<string>();
private enabled = false;
private configurationChangeDisposable: Disposable;
private disposables: Disposable[] = [];
constructor(private git: Git) {
const config = workspace.getConfiguration('git');
this.enabled = config.get<boolean>('enabled') === true;
this.configurationChangeDisposable = workspace.onDidChangeConfiguration(this.onDidChangeConfiguration, this);
if (this.enabled) {
this.enable();
}
}
private onDidChangeConfiguration(): void {
const config = workspace.getConfiguration('git');
const enabled = config.get<boolean>('enabled') === true;
if (enabled === this.enabled) {
return;
}
this.enabled = enabled;
if (enabled) {
this.enable();
} else {
this.disable();
}
}
private enable(): void {
workspace.onDidChangeWorkspaceFolders(this.onDidChangeWorkspaceFolders, this, this.disposables);
this.onDidChangeWorkspaceFolders({ added: workspace.workspaceFolders || [], removed: [] });
window.onDidChangeVisibleTextEditors(this.onDidChangeVisibleTextEditors, this, this.disposables);
this.onDidChangeVisibleTextEditors(window.visibleTextEditors);
const fsWatcher = workspace.createFileSystemWatcher('**');
this.disposables.push(fsWatcher);
const onWorkspaceChange = anyEvent(fsWatcher.onDidChange, fsWatcher.onDidCreate, fsWatcher.onDidDelete);
const onGitRepositoryChange = filterEvent(onWorkspaceChange, uri => /\/\.git\//.test(uri.path));
const onPossibleGitRepositoryChange = filterEvent(onGitRepositoryChange, uri => !this.getRepository(uri));
onPossibleGitRepositoryChange(this.onPossibleGitRepositoryChange, this, this.disposables);
this.scanWorkspaceFolders();
}
private disable(): void {
const openRepositories = [...this.openRepositories];
openRepositories.forEach(r => r.dispose());
this.openRepositories = [];
this.possibleGitRepositoryPaths.clear();
this.disposables = dispose(this.disposables);
}
/**
* Scans the first level of each workspace folder, looking
* for git repositories.
*/
private async scanWorkspaceFolders(): Promise<void> {
for (const folder of workspace.workspaceFolders || []) {
const root = folder.uri.fsPath;
const children = await new Promise<string[]>((c, e) => fs.readdir(root, (err, r) => err ? e(err) : c(r)));
children
.filter(child => child !== '.git')
.forEach(child => this.tryOpenRepository(path.join(root, child)));
}
}
private onPossibleGitRepositoryChange(uri: Uri): void {
const possibleGitRepositoryPath = uri.fsPath.replace(/\.git.*$/, '');
this.possibleGitRepositoryPaths.add(possibleGitRepositoryPath);
this.eventuallyScanPossibleGitRepositories();
}
@debounce(500)
private eventuallyScanPossibleGitRepositories(): void {
for (const path of this.possibleGitRepositoryPaths) {
this.tryOpenRepository(path);
}
this.possibleGitRepositoryPaths.clear();
}
private async onDidChangeWorkspaceFolders({ added, removed }: WorkspaceFoldersChangeEvent): Promise<void> {
const possibleRepositoryFolders = added
.filter(folder => !this.getOpenRepository(folder.uri));
const activeRepositoriesList = window.visibleTextEditors
.map(editor => this.getRepository(editor.document.uri))
.filter(repository => !!repository) as Repository[];
const activeRepositories = new Set<Repository>(activeRepositoriesList);
const openRepositoriesToDispose = removed
.map(folder => this.getOpenRepository(folder.uri))
.filter(r => !!r && !activeRepositories.has(r.repository)) as OpenRepository[];
possibleRepositoryFolders.forEach(p => this.tryOpenRepository(p.uri.fsPath));
openRepositoriesToDispose.forEach(r => r.dispose());
}
private onDidChangeVisibleTextEditors(editors: TextEditor[]): void {
editors.forEach(editor => {
const uri = editor.document.uri;
if (uri.scheme !== 'file') {
return;
}
const repository = this.getRepository(uri);
if (repository) {
return;
}
this.tryOpenRepository(path.dirname(uri.fsPath));
});
}
@sequentialize
async tryOpenRepository(path: string): Promise<void> {
if (this.getRepository(path)) {
return;
}
try {
const repositoryRoot = await this.git.getRepositoryRoot(path);
// This can happen whenever `path` has the wrong case sensitivity in
// case insensitive file systems
// https://github.com/Microsoft/vscode/issues/33498
if (this.getRepository(repositoryRoot)) {
return;
}
const repository = new Repository(this.git.open(repositoryRoot));
this.open(repository);
} catch (err) {
if (err.gitErrorCode === GitErrorCodes.NotAGitRepository) {
return;
}
// console.error('Failed to find repository:', err);
}
}
private open(repository: Repository): void {
const onDidDisappearRepository = filterEvent(repository.onDidChangeState, state => state === RepositoryState.Disposed);
const disappearListener = onDidDisappearRepository(() => dispose());
const changeListener = repository.onDidChangeRepository(uri => this._onDidChangeRepository.fire({ repository, uri }));
const dispose = () => {
disappearListener.dispose();
changeListener.dispose();
repository.dispose();
this.openRepositories = this.openRepositories.filter(e => e !== openRepository);
this._onDidCloseRepository.fire(repository);
};
const openRepository = { repository, dispose };
this.openRepositories.push(openRepository);
this._onDidOpenRepository.fire(repository);
}
async pickRepository(): Promise<Repository | undefined> {
if (this.openRepositories.length === 0) {
throw new Error(localize('no repositories', "There are no available repositories"));
}
const picks = this.openRepositories.map(e => new RepositoryPick(e.repository));
const placeHolder = localize('pick repo', "Choose a repository");
const pick = await window.showQuickPick(picks, { placeHolder });
return pick && pick.repository;
}
getRepository(sourceControl: SourceControl): Repository | undefined;
getRepository(resourceGroup: SourceControlResourceGroup): Repository | undefined;
getRepository(path: string): Repository | undefined;
getRepository(resource: Uri): Repository | undefined;
getRepository(hint: any): Repository | undefined {
const liveRepository = this.getOpenRepository(hint);
return liveRepository && liveRepository.repository;
}
private getOpenRepository(repository: Repository): OpenRepository | undefined;
private getOpenRepository(sourceControl: SourceControl): OpenRepository | undefined;
private getOpenRepository(resourceGroup: SourceControlResourceGroup): OpenRepository | undefined;
private getOpenRepository(path: string): OpenRepository | undefined;
private getOpenRepository(resource: Uri): OpenRepository | undefined;
private getOpenRepository(hint: any): OpenRepository | undefined {
if (!hint) {
return undefined;
}
if (hint instanceof Repository) {
return this.openRepositories.filter(r => r.repository === hint)[0];
}
if (typeof hint === 'string') {
hint = Uri.file(hint);
}
if (hint instanceof Uri) {
const resourcePath = hint.fsPath;
for (const liveRepository of this.openRepositories) {
const relativePath = path.relative(liveRepository.repository.root, resourcePath);
if (!/^\.\./.test(relativePath)) {
return liveRepository;
}
}
return undefined;
}
for (const liveRepository of this.openRepositories) {
const repository = liveRepository.repository;
if (hint === repository.sourceControl) {
return liveRepository;
}
if (hint === repository.mergeGroup || hint === repository.indexGroup || hint === repository.workingTreeGroup) {
return liveRepository;
}
}
return undefined;
}
dispose(): void {
this.disable();
this.configurationChangeDisposable.dispose();
}
}

View File

@@ -0,0 +1,841 @@
/*---------------------------------------------------------------------------------------------
* 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 { Uri, Command, EventEmitter, Event, scm, SourceControl, SourceControlInputBox, SourceControlResourceGroup, SourceControlResourceState, SourceControlResourceDecorations, Disposable, ProgressLocation, window, workspace, WorkspaceEdit } from 'vscode';
import { Repository as BaseRepository, Ref, Branch, Remote, Commit, GitErrorCodes, Stash } from './git';
import { anyEvent, filterEvent, eventToPromise, dispose, find } from './util';
import { memoize, throttle, debounce } from './decorators';
import { toGitUri } from './uri';
import { AutoFetcher } from './autofetch';
import * as path from 'path';
import * as nls from 'vscode-nls';
import * as fs from 'fs';
import { StatusBarCommands } from './statusbar';
const timeout = (millis: number) => new Promise(c => setTimeout(c, millis));
const localize = nls.loadMessageBundle();
const iconsRootPath = path.join(path.dirname(__dirname), 'resources', 'icons');
function getIconUri(iconName: string, theme: string): Uri {
return Uri.file(path.join(iconsRootPath, theme, `${iconName}.svg`));
}
export 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 {
Merge,
Index,
WorkingTree
}
export class Resource implements SourceControlResourceState {
@memoize
get resourceUri(): Uri {
if (this.renameResourceUri && (this._type === Status.MODIFIED || this._type === Status.DELETED || this._type === Status.INDEX_RENAMED || this._type === Status.INDEX_COPIED)) {
return this.renameResourceUri;
}
return this._resourceUri;
}
@memoize
get command(): Command {
return {
command: 'git.openResource',
title: localize('open', "Open"),
arguments: [this]
};
}
get resourceGroupType(): ResourceGroupType { return this._resourceGroupType; }
get type(): Status { return this._type; }
get original(): Uri { return this._resourceUri; }
get renameResourceUri(): Uri | undefined { return this._renameResourceUri; }
private static Icons = {
light: {
Modified: getIconUri('status-modified', 'light'),
Added: getIconUri('status-added', 'light'),
Deleted: getIconUri('status-deleted', 'light'),
Renamed: getIconUri('status-renamed', 'light'),
Copied: getIconUri('status-copied', 'light'),
Untracked: getIconUri('status-untracked', 'light'),
Ignored: getIconUri('status-ignored', 'light'),
Conflict: getIconUri('status-conflict', 'light'),
},
dark: {
Modified: getIconUri('status-modified', 'dark'),
Added: getIconUri('status-added', 'dark'),
Deleted: getIconUri('status-deleted', 'dark'),
Renamed: getIconUri('status-renamed', 'dark'),
Copied: getIconUri('status-copied', 'dark'),
Untracked: getIconUri('status-untracked', 'dark'),
Ignored: getIconUri('status-ignored', 'dark'),
Conflict: getIconUri('status-conflict', 'dark')
}
};
private getIconPath(theme: string): Uri | undefined {
switch (this.type) {
case Status.INDEX_MODIFIED: return Resource.Icons[theme].Modified;
case Status.MODIFIED: return Resource.Icons[theme].Modified;
case Status.INDEX_ADDED: return Resource.Icons[theme].Added;
case Status.INDEX_DELETED: return Resource.Icons[theme].Deleted;
case Status.DELETED: return Resource.Icons[theme].Deleted;
case Status.INDEX_RENAMED: return Resource.Icons[theme].Renamed;
case Status.INDEX_COPIED: return Resource.Icons[theme].Copied;
case Status.UNTRACKED: return Resource.Icons[theme].Untracked;
case Status.IGNORED: return Resource.Icons[theme].Ignored;
case Status.BOTH_DELETED: return Resource.Icons[theme].Conflict;
case Status.ADDED_BY_US: return Resource.Icons[theme].Conflict;
case Status.DELETED_BY_THEM: return Resource.Icons[theme].Conflict;
case Status.ADDED_BY_THEM: return Resource.Icons[theme].Conflict;
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: return void 0;
}
}
private get tooltip(): string {
switch (this.type) {
case Status.INDEX_MODIFIED: return localize('index modified', "Index Modified");
case Status.MODIFIED: return localize('modified', "Modified");
case Status.INDEX_ADDED: return localize('index added', "Index Added");
case Status.INDEX_DELETED: return localize('index deleted', "Index Deleted");
case Status.DELETED: return localize('deleted', "Deleted");
case Status.INDEX_RENAMED: return localize('index renamed', "Index Renamed");
case Status.INDEX_COPIED: return localize('index copied', "Index Copied");
case Status.UNTRACKED: return localize('untracked', "Untracked");
case Status.IGNORED: return localize('ignored', "Ignored");
case Status.BOTH_DELETED: return localize('both deleted', "Both Deleted");
case Status.ADDED_BY_US: return localize('added by us', "Added By Us");
case Status.DELETED_BY_THEM: return localize('deleted by them', "Deleted By Them");
case Status.ADDED_BY_THEM: return localize('added by them', "Added By Them");
case Status.DELETED_BY_US: return localize('deleted by us', "Deleted By Us");
case Status.BOTH_ADDED: return localize('both added', "Both Added");
case Status.BOTH_MODIFIED: return localize('both modified', "Both Modified");
default: return '';
}
}
private get strikeThrough(): boolean {
switch (this.type) {
case Status.DELETED:
case Status.BOTH_DELETED:
case Status.DELETED_BY_THEM:
case Status.DELETED_BY_US:
case Status.INDEX_DELETED:
return true;
default:
return false;
}
}
@memoize
private get faded(): boolean {
// TODO@joao
return false;
// const workspaceRootPath = this.workspaceRoot.fsPath;
// return this.resourceUri.fsPath.substr(0, workspaceRootPath.length) !== workspaceRootPath;
}
get decorations(): SourceControlResourceDecorations {
const light = { iconPath: this.getIconPath('light') };
const dark = { iconPath: this.getIconPath('dark') };
const tooltip = this.tooltip;
const strikeThrough = this.strikeThrough;
const faded = this.faded;
return { strikeThrough, faded, tooltip, light, dark };
}
constructor(
private _resourceGroupType: ResourceGroupType,
private _resourceUri: Uri,
private _type: Status,
private _renameResourceUri?: Uri
) { }
}
export enum Operation {
Status = 1 << 0,
Add = 1 << 1,
RevertFiles = 1 << 2,
Commit = 1 << 3,
Clean = 1 << 4,
Branch = 1 << 5,
Checkout = 1 << 6,
Reset = 1 << 7,
Fetch = 1 << 8,
Pull = 1 << 9,
Push = 1 << 10,
Sync = 1 << 11,
Show = 1 << 12,
Stage = 1 << 13,
GetCommitTemplate = 1 << 14,
DeleteBranch = 1 << 15,
Merge = 1 << 16,
Ignore = 1 << 17,
Tag = 1 << 18,
Stash = 1 << 19
}
// function getOperationName(operation: Operation): string {
// switch (operation) {
// case Operation.Status: return 'Status';
// case Operation.Add: return 'Add';
// case Operation.RevertFiles: return 'RevertFiles';
// case Operation.Commit: return 'Commit';
// case Operation.Clean: return 'Clean';
// case Operation.Branch: return 'Branch';
// case Operation.Checkout: return 'Checkout';
// case Operation.Reset: return 'Reset';
// case Operation.Fetch: return 'Fetch';
// case Operation.Pull: return 'Pull';
// case Operation.Push: return 'Push';
// case Operation.Sync: return 'Sync';
// case Operation.Init: return 'Init';
// case Operation.Show: return 'Show';
// case Operation.Stage: return 'Stage';
// case Operation.GetCommitTemplate: return 'GetCommitTemplate';
// default: return 'unknown';
// }
// }
function isReadOnly(operation: Operation): boolean {
switch (operation) {
case Operation.Show:
case Operation.GetCommitTemplate:
return true;
default:
return false;
}
}
function shouldShowProgress(operation: Operation): boolean {
switch (operation) {
case Operation.Fetch:
return false;
default:
return true;
}
}
export interface Operations {
isIdle(): boolean;
isRunning(operation: Operation): boolean;
}
class OperationsImpl implements Operations {
constructor(private readonly operations: number = 0) {
// noop
}
start(operation: Operation): OperationsImpl {
return new OperationsImpl(this.operations | operation);
}
end(operation: Operation): OperationsImpl {
return new OperationsImpl(this.operations & ~operation);
}
isRunning(operation: Operation): boolean {
return (this.operations & operation) !== 0;
}
isIdle(): boolean {
return this.operations === 0;
}
}
export interface CommitOptions {
all?: boolean;
amend?: boolean;
signoff?: boolean;
signCommit?: boolean;
}
export interface GitResourceGroup extends SourceControlResourceGroup {
resourceStates: Resource[];
}
export class Repository implements Disposable {
private _onDidChangeRepository = new EventEmitter<Uri>();
readonly onDidChangeRepository: Event<Uri> = this._onDidChangeRepository.event;
private _onDidChangeState = new EventEmitter<RepositoryState>();
readonly onDidChangeState: Event<RepositoryState> = this._onDidChangeState.event;
private _onDidChangeStatus = new EventEmitter<void>();
readonly onDidChangeStatus: Event<void> = this._onDidChangeStatus.event;
private _onRunOperation = new EventEmitter<Operation>();
readonly onRunOperation: Event<Operation> = this._onRunOperation.event;
private _onDidRunOperation = new EventEmitter<Operation>();
readonly onDidRunOperation: Event<Operation> = this._onDidRunOperation.event;
@memoize
get onDidChangeOperations(): Event<void> {
return anyEvent(this.onRunOperation as Event<any>, this.onDidRunOperation as Event<any>);
}
private _sourceControl: SourceControl;
get sourceControl(): SourceControl { return this._sourceControl; }
get inputBox(): SourceControlInputBox { return this._sourceControl.inputBox; }
private _mergeGroup: SourceControlResourceGroup;
get mergeGroup(): GitResourceGroup { return this._mergeGroup as GitResourceGroup; }
private _indexGroup: SourceControlResourceGroup;
get indexGroup(): GitResourceGroup { return this._indexGroup as GitResourceGroup; }
private _workingTreeGroup: SourceControlResourceGroup;
get workingTreeGroup(): GitResourceGroup { return this._workingTreeGroup as GitResourceGroup; }
private _HEAD: Branch | undefined;
get HEAD(): Branch | undefined {
return this._HEAD;
}
private _refs: Ref[] = [];
get refs(): Ref[] {
return this._refs;
}
private _remotes: Remote[] = [];
get remotes(): Remote[] {
return this._remotes;
}
private _operations = new OperationsImpl();
get operations(): Operations { return this._operations; }
private _state = RepositoryState.Idle;
get state(): RepositoryState { return this._state; }
set state(state: RepositoryState) {
this._state = state;
this._onDidChangeState.fire(state);
this._HEAD = undefined;
this._refs = [];
this._remotes = [];
this.mergeGroup.resourceStates = [];
this.indexGroup.resourceStates = [];
this.workingTreeGroup.resourceStates = [];
this._sourceControl.count = 0;
}
get root(): string {
return this.repository.root;
}
private isRepositoryHuge = false;
private didWarnAboutLimit = false;
private disposables: Disposable[] = [];
constructor(
private readonly repository: BaseRepository
) {
const fsWatcher = workspace.createFileSystemWatcher('**');
this.disposables.push(fsWatcher);
const onWorkspaceChange = anyEvent(fsWatcher.onDidChange, fsWatcher.onDidCreate, fsWatcher.onDidDelete);
const onRepositoryChange = filterEvent(onWorkspaceChange, uri => !/^\.\./.test(path.relative(repository.root, uri.fsPath)));
const onRelevantRepositoryChange = 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);
const label = `${path.basename(repository.root)} (Git)`;
this._sourceControl = scm.createSourceControl('git', label);
this._sourceControl.acceptInputCommand = { command: 'git.commitWithInput', title: localize('commit', "Commit"), arguments: [this._sourceControl] };
this._sourceControl.quickDiffProvider = this;
this.disposables.push(this._sourceControl);
this._mergeGroup = this._sourceControl.createResourceGroup('merge', localize('merge changes', "Merge Changes"));
this._indexGroup = this._sourceControl.createResourceGroup('index', localize('staged changes', "Staged Changes"));
this._workingTreeGroup = this._sourceControl.createResourceGroup('workingTree', localize('changes', "Changes"));
this.mergeGroup.hideWhenEmpty = true;
this.indexGroup.hideWhenEmpty = true;
this.disposables.push(this.mergeGroup);
this.disposables.push(this.indexGroup);
this.disposables.push(this.workingTreeGroup);
this.disposables.push(new AutoFetcher(this));
const statusBar = new StatusBarCommands(this);
this.disposables.push(statusBar);
statusBar.onDidChange(() => this._sourceControl.statusBarCommands = statusBar.commands, null, this.disposables);
this._sourceControl.statusBarCommands = statusBar.commands;
this.updateCommitTemplate();
this.status();
}
provideOriginalResource(uri: Uri): Uri | undefined {
if (uri.scheme !== 'file') {
return;
}
return toGitUri(uri, '', true);
}
private async updateCommitTemplate(): Promise<void> {
try {
this._sourceControl.commitTemplate = await this.repository.getCommitTemplate();
} catch (e) {
// noop
}
}
// @throttle
// async init(): Promise<void> {
// if (this.state !== State.NotAGitRepository) {
// return;
// }
// await this.git.init(this.workspaceRoot.fsPath);
// await this.status();
// }
@throttle
async status(): Promise<void> {
await this.run(Operation.Status);
}
async add(resources: Uri[]): Promise<void> {
await this.run(Operation.Add, () => this.repository.add(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));
}
async revert(resources: Uri[]): Promise<void> {
await this.run(Operation.RevertFiles, () => this.repository.revert('HEAD', resources.map(r => r.fsPath)));
}
async commit(message: string, opts: CommitOptions = Object.create(null)): Promise<void> {
await this.run(Operation.Commit, async () => {
if (opts.all) {
await this.repository.add([]);
}
await this.repository.commit(message, opts);
});
}
async clean(resources: Uri[]): Promise<void> {
await this.run(Operation.Clean, async () => {
const toClean: string[] = [];
const toCheckout: string[] = [];
resources.forEach(r => {
const raw = r.toString();
const scmResource = find(this.workingTreeGroup.resourceStates, sr => sr.resourceUri.toString() === raw);
if (!scmResource) {
return;
}
switch (scmResource.type) {
case Status.UNTRACKED:
case Status.IGNORED:
toClean.push(r.fsPath);
break;
default:
toCheckout.push(r.fsPath);
break;
}
});
const promises: Promise<void>[] = [];
if (toClean.length > 0) {
promises.push(this.repository.clean(toClean));
}
if (toCheckout.length > 0) {
promises.push(this.repository.checkout('', toCheckout));
}
await Promise.all(promises);
});
}
async branch(name: string): Promise<void> {
await this.run(Operation.Branch, () => this.repository.branch(name, true));
}
async deleteBranch(name: string, force?: boolean): Promise<void> {
await this.run(Operation.DeleteBranch, () => this.repository.deleteBranch(name, force));
}
async merge(ref: string): Promise<void> {
await this.run(Operation.Merge, () => this.repository.merge(ref));
}
async tag(name: string, message?: string): Promise<void> {
await this.run(Operation.Tag, () => this.repository.tag(name, message));
}
async checkout(treeish: string): Promise<void> {
await this.run(Operation.Checkout, () => this.repository.checkout(treeish, []));
}
async getCommit(ref: string): Promise<Commit> {
return await this.repository.getCommit(ref);
}
async reset(treeish: string, hard?: boolean): Promise<void> {
await this.run(Operation.Reset, () => this.repository.reset(treeish, hard));
}
@throttle
async fetch(): Promise<void> {
try {
await this.run(Operation.Fetch, () => this.repository.fetch());
} catch (err) {
// noop
}
}
@throttle
async pullWithRebase(): Promise<void> {
await this.run(Operation.Pull, () => this.repository.pull(true));
}
@throttle
async pull(rebase?: boolean, remote?: string, name?: string): Promise<void> {
await this.run(Operation.Pull, () => this.repository.pull(rebase, remote, name));
}
@throttle
async push(): Promise<void> {
await this.run(Operation.Push, () => this.repository.push());
}
async pullFrom(rebase?: boolean, remote?: string, branch?: string): Promise<void> {
await this.run(Operation.Pull, () => this.repository.pull(rebase, remote, branch));
}
async pushTo(remote?: string, name?: string, setUpstream: boolean = false): Promise<void> {
await this.run(Operation.Push, () => this.repository.push(remote, name, setUpstream));
}
async pushTags(remote?: string): Promise<void> {
await this.run(Operation.Push, () => this.repository.push(remote, undefined, false, true));
}
@throttle
async sync(): Promise<void> {
await this.run(Operation.Sync, async () => {
await this.repository.pull();
const shouldPush = this.HEAD && typeof this.HEAD.ahead === 'number' ? this.HEAD.ahead > 0 : true;
if (shouldPush) {
await this.repository.push();
}
});
}
async show(ref: string, filePath: string): Promise<string> {
return await this.run(Operation.Show, async () => {
const relativePath = path.relative(this.repository.root, filePath).replace(/\\/g, '/');
const configFiles = workspace.getConfiguration('files');
const encoding = configFiles.get<string>('encoding');
return await this.repository.buffer(`${ref}:${relativePath}`, encoding);
});
}
async getStashes(): Promise<Stash[]> {
return await this.repository.getStashes();
}
async createStash(message?: string): Promise<void> {
return await this.run(Operation.Stash, () => this.repository.createStash(message));
}
async popStash(index?: number): Promise<void> {
return await this.run(Operation.Stash, () => this.repository.popStash(index));
}
async getCommitTemplate(): Promise<string> {
return await this.run(Operation.GetCommitTemplate, async () => this.repository.getCommitTemplate());
}
async ignore(files: Uri[]): Promise<void> {
return await this.run(Operation.Ignore, async () => {
const ignoreFile = `${this.repository.root}${path.sep}.gitignore`;
const textToAppend = files
.map(uri => path.relative(this.repository.root, uri.fsPath).replace(/\\/g, '/'))
.join('\n');
const document = await new Promise(c => fs.exists(ignoreFile, c))
? await workspace.openTextDocument(ignoreFile)
: await workspace.openTextDocument(Uri.file(ignoreFile).with({ scheme: 'untitled' }));
await window.showTextDocument(document);
const edit = new WorkspaceEdit();
const lastLine = document.lineAt(document.lineCount - 1);
const text = lastLine.isEmptyOrWhitespace ? `${textToAppend}\n` : `\n${textToAppend}\n`;
edit.insert(document.uri, lastLine.range.end, text);
workspace.applyEdit(edit);
});
}
private async run<T>(operation: Operation, runOperation: () => Promise<T> = () => Promise.resolve<any>(null)): Promise<T> {
if (this.state !== RepositoryState.Idle) {
throw new Error('Repository not initialized');
}
const run = async () => {
this._operations = this._operations.start(operation);
this._onRunOperation.fire(operation);
try {
const result = await this.retryRun(runOperation);
if (!isReadOnly(operation)) {
await this.updateModelState();
}
return result;
} catch (err) {
if (err.gitErrorCode === GitErrorCodes.NotAGitRepository) {
this.state = RepositoryState.Disposed;
}
throw err;
} finally {
this._operations = this._operations.end(operation);
this._onDidRunOperation.fire(operation);
}
};
return shouldShowProgress(operation)
? window.withProgress({ location: ProgressLocation.SourceControl }, run)
: run();
}
private async retryRun<T>(runOperation: () => Promise<T> = () => Promise.resolve<any>(null)): Promise<T> {
let attempt = 0;
while (true) {
try {
attempt++;
return await runOperation();
} catch (err) {
if (err.gitErrorCode === GitErrorCodes.RepositoryIsLocked && attempt <= 10) {
// quatratic backoff
await timeout(Math.pow(attempt, 2) * 50);
} else {
throw err;
}
}
}
}
@throttle
private async updateModelState(): Promise<void> {
const { status, didHitLimit } = await this.repository.getStatus();
const config = workspace.getConfiguration('git');
const shouldIgnore = config.get<boolean>('ignoreLimitWarning') === true;
this.isRepositoryHuge = didHitLimit;
if (didHitLimit && !shouldIgnore && !this.didWarnAboutLimit) {
const ok = { title: localize('ok', "OK"), isCloseAffordance: true };
const neverAgain = { title: localize('neveragain', "Never Show Again") };
window.showWarningMessage(localize('huge', "The git repository at '{0}' has too many active changes, only a subset of Git features will be enabled.", this.repository.root), ok, neverAgain).then(result => {
if (result === neverAgain) {
config.update('ignoreLimitWarning', true, false);
}
});
this.didWarnAboutLimit = true;
}
let HEAD: Branch | undefined;
try {
HEAD = await this.repository.getHEAD();
if (HEAD.name) {
try {
HEAD = await this.repository.getBranch(HEAD.name);
} catch (err) {
// noop
}
}
} catch (err) {
// noop
}
const [refs, remotes] = await Promise.all([this.repository.getRefs(), this.repository.getRemotes()]);
this._HEAD = HEAD;
this._refs = refs;
this._remotes = remotes;
const index: Resource[] = [];
const workingTree: Resource[] = [];
const merge: Resource[] = [];
status.forEach(raw => {
const uri = Uri.file(path.join(this.repository.root, raw.path));
const renameUri = raw.rename ? Uri.file(path.join(this.repository.root, raw.rename)) : undefined;
switch (raw.x + raw.y) {
case '??': return workingTree.push(new Resource(ResourceGroupType.WorkingTree, uri, Status.UNTRACKED));
case '!!': return workingTree.push(new Resource(ResourceGroupType.WorkingTree, uri, Status.IGNORED));
case 'DD': return merge.push(new Resource(ResourceGroupType.Merge, uri, Status.BOTH_DELETED));
case 'AU': return merge.push(new Resource(ResourceGroupType.Merge, uri, Status.ADDED_BY_US));
case 'UD': return merge.push(new Resource(ResourceGroupType.Merge, uri, Status.DELETED_BY_THEM));
case 'UA': return merge.push(new Resource(ResourceGroupType.Merge, uri, Status.ADDED_BY_THEM));
case 'DU': return merge.push(new Resource(ResourceGroupType.Merge, uri, Status.DELETED_BY_US));
case 'AA': return merge.push(new Resource(ResourceGroupType.Merge, uri, Status.BOTH_ADDED));
case 'UU': return merge.push(new Resource(ResourceGroupType.Merge, uri, Status.BOTH_MODIFIED));
}
let isModifiedInIndex = false;
switch (raw.x) {
case 'M': index.push(new Resource(ResourceGroupType.Index, uri, Status.INDEX_MODIFIED)); isModifiedInIndex = true; break;
case 'A': index.push(new Resource(ResourceGroupType.Index, uri, Status.INDEX_ADDED)); break;
case 'D': index.push(new Resource(ResourceGroupType.Index, uri, Status.INDEX_DELETED)); break;
case 'R': index.push(new Resource(ResourceGroupType.Index, uri, Status.INDEX_RENAMED, renameUri)); break;
case 'C': index.push(new Resource(ResourceGroupType.Index, uri, Status.INDEX_COPIED, renameUri)); break;
}
switch (raw.y) {
case 'M': workingTree.push(new Resource(ResourceGroupType.WorkingTree, uri, Status.MODIFIED, renameUri)); break;
case 'D': workingTree.push(new Resource(ResourceGroupType.WorkingTree, uri, Status.DELETED, renameUri)); break;
}
});
// set resource groups
this.mergeGroup.resourceStates = merge;
this.indexGroup.resourceStates = index;
this.workingTreeGroup.resourceStates = workingTree;
// set count badge
const countBadge = workspace.getConfiguration('git').get<string>('countBadge');
let count = merge.length + index.length + workingTree.length;
switch (countBadge) {
case 'off': count = 0; break;
case 'tracked': count = count - workingTree.filter(r => r.type === Status.UNTRACKED || r.type === Status.IGNORED).length; break;
}
this._sourceControl.count = count;
// set context key
let stateContextKey = '';
switch (this.state) {
case RepositoryState.Idle: stateContextKey = 'idle'; break;
case RepositoryState.Disposed: stateContextKey = 'norepo'; break;
}
this._onDidChangeStatus.fire();
}
private onFSChange(uri: Uri): void {
const config = workspace.getConfiguration('git');
const autorefresh = config.get<boolean>('autorefresh');
if (!autorefresh) {
return;
}
if (this.isRepositoryHuge) {
return;
}
if (!this.operations.isIdle()) {
return;
}
this.eventuallyUpdateWhenIdleAndWait();
}
@debounce(1000)
private eventuallyUpdateWhenIdleAndWait(): void {
this.updateWhenIdleAndWait();
}
@throttle
private async updateWhenIdleAndWait(): Promise<void> {
await this.whenIdleAndFocused();
await this.status();
await timeout(5000);
}
private async whenIdleAndFocused(): Promise<void> {
while (true) {
if (!this.operations.isIdle()) {
await eventToPromise(this.onDidRunOperation);
continue;
}
if (!window.state.focused) {
const onDidFocusWindow = filterEvent(window.onDidChangeWindowState, e => e.focused);
await eventToPromise(onDidFocusWindow);
continue;
}
return;
}
}
dispose(): void {
this.disposables = dispose(this.disposables);
}
}

View File

@@ -0,0 +1,108 @@
/*---------------------------------------------------------------------------------------------
* 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 { TextDocument, Range, LineChange, Selection } from 'vscode';
export function applyLineChanges(original: TextDocument, modified: TextDocument, diffs: LineChange[]): string {
const result: string[] = [];
let currentLine = 0;
for (let diff of diffs) {
const isInsertion = diff.originalEndLineNumber === 0;
const isDeletion = diff.modifiedEndLineNumber === 0;
result.push(original.getText(new Range(currentLine, 0, isInsertion ? diff.originalStartLineNumber : diff.originalStartLineNumber - 1, 0)));
if (!isDeletion) {
let fromLine = diff.modifiedStartLineNumber - 1;
let fromCharacter = 0;
if (isInsertion && diff.originalStartLineNumber === original.lineCount) {
fromLine = original.lineCount - 1;
fromCharacter = original.lineAt(fromLine).range.end.character;
}
result.push(modified.getText(new Range(fromLine, fromCharacter, diff.modifiedEndLineNumber, 0)));
}
currentLine = isInsertion ? diff.originalStartLineNumber : diff.originalEndLineNumber;
}
result.push(original.getText(new Range(currentLine, 0, original.lineCount, 0)));
return result.join('');
}
export function toLineRanges(selections: Selection[], textDocument: TextDocument): Range[] {
const lineRanges = selections.map(s => {
const startLine = textDocument.lineAt(s.start.line);
const endLine = textDocument.lineAt(s.end.line);
return new Range(startLine.range.start, endLine.range.end);
});
lineRanges.sort((a, b) => a.start.line - b.start.line);
const result = lineRanges.reduce((result, l) => {
if (result.length === 0) {
result.push(l);
return result;
}
const [last, ...rest] = result;
const intersection = l.intersection(last);
if (intersection) {
return [intersection, ...rest];
}
if (l.start.line === last.end.line + 1) {
const merge = new Range(last.start, l.end);
return [merge, ...rest];
}
return [l, ...result];
}, [] as Range[]);
result.reverse();
return result;
}
function getModifiedRange(textDocument: TextDocument, diff: LineChange): Range {
return diff.modifiedEndLineNumber === 0
? new Range(textDocument.lineAt(diff.modifiedStartLineNumber - 1).range.end, textDocument.lineAt(diff.modifiedStartLineNumber).range.start)
: new Range(textDocument.lineAt(diff.modifiedStartLineNumber - 1).range.start, textDocument.lineAt(diff.modifiedEndLineNumber - 1).range.end);
}
export function intersectDiffWithRange(textDocument: TextDocument, diff: LineChange, range: Range): LineChange | null {
const modifiedRange = getModifiedRange(textDocument, diff);
const intersection = range.intersection(modifiedRange);
if (!intersection) {
return null;
}
if (diff.modifiedEndLineNumber === 0) {
return diff;
} else {
return {
originalStartLineNumber: diff.originalStartLineNumber,
originalEndLineNumber: diff.originalEndLineNumber,
modifiedStartLineNumber: intersection.start.line + 1,
modifiedEndLineNumber: intersection.end.line + 1
};
}
}
export function invertLineChange(diff: LineChange): LineChange {
return {
modifiedStartLineNumber: diff.originalStartLineNumber,
modifiedEndLineNumber: diff.originalEndLineNumber,
originalStartLineNumber: diff.modifiedStartLineNumber,
originalEndLineNumber: diff.modifiedEndLineNumber
};
}

View File

@@ -0,0 +1,189 @@
/*---------------------------------------------------------------------------------------------
* 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 { Disposable, Command, EventEmitter, Event } from 'vscode';
import { RefType, Branch } from './git';
import { Repository, Operation } from './repository';
import { anyEvent, dispose } from './util';
import * as nls from 'vscode-nls';
const localize = nls.loadMessageBundle();
class CheckoutStatusBar {
private _onDidChange = new EventEmitter<void>();
get onDidChange(): Event<void> { return this._onDidChange.event; }
private disposables: Disposable[] = [];
constructor(private repository: Repository) {
repository.onDidChangeStatus(this._onDidChange.fire, this._onDidChange, this.disposables);
}
get command(): Command | undefined {
const HEAD = this.repository.HEAD;
if (!HEAD) {
return undefined;
}
const tag = this.repository.refs.filter(iref => iref.type === RefType.Tag && iref.commit === HEAD.commit)[0];
const tagName = tag && tag.name;
const head = HEAD.name || tagName || (HEAD.commit || '').substr(0, 8);
const title = '$(git-branch) '
+ head
+ (this.repository.workingTreeGroup.resourceStates.length > 0 ? '*' : '')
+ (this.repository.indexGroup.resourceStates.length > 0 ? '+' : '')
+ (this.repository.mergeGroup.resourceStates.length > 0 ? '!' : '');
return {
command: 'git.checkout',
tooltip: localize('checkout', 'Checkout...'),
title,
arguments: [this.repository.sourceControl]
};
}
dispose(): void {
this.disposables.forEach(d => d.dispose());
}
}
interface SyncStatusBarState {
isSyncRunning: boolean;
hasRemotes: boolean;
HEAD: Branch | undefined;
}
class SyncStatusBar {
private static StartState: SyncStatusBarState = {
isSyncRunning: false,
hasRemotes: false,
HEAD: undefined
};
private _onDidChange = new EventEmitter<void>();
get onDidChange(): Event<void> { return this._onDidChange.event; }
private disposables: Disposable[] = [];
private _state: SyncStatusBarState = SyncStatusBar.StartState;
private get state() { return this._state; }
private set state(state: SyncStatusBarState) {
this._state = state;
this._onDidChange.fire();
}
constructor(private repository: Repository) {
repository.onDidChangeStatus(this.onModelChange, this, this.disposables);
repository.onDidChangeOperations(this.onOperationsChange, this, this.disposables);
this._onDidChange.fire();
}
private onOperationsChange(): void {
this.state = {
...this.state,
isSyncRunning: this.repository.operations.isRunning(Operation.Sync)
};
}
private onModelChange(): void {
this.state = {
...this.state,
hasRemotes: this.repository.remotes.length > 0,
HEAD: this.repository.HEAD
};
}
get command(): Command | undefined {
if (!this.state.hasRemotes) {
return undefined;
}
const HEAD = this.state.HEAD;
let icon = '$(sync)';
let text = '';
let command = '';
let tooltip = '';
if (HEAD && HEAD.name && HEAD.commit) {
if (HEAD.upstream) {
if (HEAD.ahead || HEAD.behind) {
text += `${HEAD.behind}${HEAD.ahead}`;
}
command = 'git.sync';
tooltip = localize('sync changes', "Synchronize Changes");
} else {
icon = '$(cloud-upload)';
command = 'git.publish';
tooltip = localize('publish changes', "Publish Changes");
}
} else {
command = '';
tooltip = '';
}
if (this.state.isSyncRunning) {
icon = '$(sync~spin)';
command = '';
tooltip = localize('syncing changes', "Synchronizing Changes...");
}
return {
command,
title: [icon, text].join(' ').trim(),
tooltip,
arguments: [this.repository.sourceControl]
};
}
dispose(): void {
this.disposables.forEach(d => d.dispose());
}
}
export class StatusBarCommands {
private syncStatusBar: SyncStatusBar;
private checkoutStatusBar: CheckoutStatusBar;
private disposables: Disposable[] = [];
constructor(repository: Repository) {
this.syncStatusBar = new SyncStatusBar(repository);
this.checkoutStatusBar = new CheckoutStatusBar(repository);
}
get onDidChange(): Event<void> {
return anyEvent(
this.syncStatusBar.onDidChange,
this.checkoutStatusBar.onDidChange
);
}
get commands(): Command[] {
const result: Command[] = [];
const checkout = this.checkoutStatusBar.command;
if (checkout) {
result.push(checkout);
}
const sync = this.syncStatusBar.command;
if (sync) {
result.push(sync);
}
return result;
}
dispose(): void {
this.syncStatusBar.dispose();
this.checkoutStatusBar.dispose();
this.disposables = dispose(this.disposables);
}
}

View File

@@ -0,0 +1,137 @@
/*---------------------------------------------------------------------------------------------
* 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 { GitStatusParser } from '../git';
import * as assert from 'assert';
suite('git', () => {
suite('GitStatusParser', () => {
test('empty parser', () => {
const parser = new GitStatusParser();
assert.deepEqual(parser.status, []);
});
test('empty parser 2', () => {
const parser = new GitStatusParser();
parser.update('');
assert.deepEqual(parser.status, []);
});
test('simple', () => {
const parser = new GitStatusParser();
parser.update('?? file.txt\0');
assert.deepEqual(parser.status, [
{ path: 'file.txt', rename: undefined, x: '?', y: '?' }
]);
});
test('simple 2', () => {
const parser = new GitStatusParser();
parser.update('?? file.txt\0');
parser.update('?? file2.txt\0');
parser.update('?? file3.txt\0');
assert.deepEqual(parser.status, [
{ path: 'file.txt', rename: undefined, x: '?', y: '?' },
{ path: 'file2.txt', rename: undefined, x: '?', y: '?' },
{ path: 'file3.txt', rename: undefined, x: '?', y: '?' }
]);
});
test('empty lines', () => {
const parser = new GitStatusParser();
parser.update('');
parser.update('?? file.txt\0');
parser.update('');
parser.update('');
parser.update('?? file2.txt\0');
parser.update('');
parser.update('?? file3.txt\0');
parser.update('');
assert.deepEqual(parser.status, [
{ path: 'file.txt', rename: undefined, x: '?', y: '?' },
{ path: 'file2.txt', rename: undefined, x: '?', y: '?' },
{ path: 'file3.txt', rename: undefined, x: '?', y: '?' }
]);
});
test('combined', () => {
const parser = new GitStatusParser();
parser.update('?? file.txt\0?? file2.txt\0?? file3.txt\0');
assert.deepEqual(parser.status, [
{ path: 'file.txt', rename: undefined, x: '?', y: '?' },
{ path: 'file2.txt', rename: undefined, x: '?', y: '?' },
{ path: 'file3.txt', rename: undefined, x: '?', y: '?' }
]);
});
test('split 1', () => {
const parser = new GitStatusParser();
parser.update('?? file.txt\0?? file2');
parser.update('.txt\0?? file3.txt\0');
assert.deepEqual(parser.status, [
{ path: 'file.txt', rename: undefined, x: '?', y: '?' },
{ path: 'file2.txt', rename: undefined, x: '?', y: '?' },
{ path: 'file3.txt', rename: undefined, x: '?', y: '?' }
]);
});
test('split 2', () => {
const parser = new GitStatusParser();
parser.update('?? file.txt');
parser.update('\0?? file2.txt\0?? file3.txt\0');
assert.deepEqual(parser.status, [
{ path: 'file.txt', rename: undefined, x: '?', y: '?' },
{ path: 'file2.txt', rename: undefined, x: '?', y: '?' },
{ path: 'file3.txt', rename: undefined, x: '?', y: '?' }
]);
});
test('split 3', () => {
const parser = new GitStatusParser();
parser.update('?? file.txt\0?? file2.txt\0?? file3.txt');
parser.update('\0');
assert.deepEqual(parser.status, [
{ path: 'file.txt', rename: undefined, x: '?', y: '?' },
{ path: 'file2.txt', rename: undefined, x: '?', y: '?' },
{ path: 'file3.txt', rename: undefined, x: '?', y: '?' }
]);
});
test('rename', () => {
const parser = new GitStatusParser();
parser.update('R newfile.txt\0file.txt\0?? file2.txt\0?? file3.txt\0');
assert.deepEqual(parser.status, [
{ path: 'file.txt', rename: 'newfile.txt', x: 'R', y: ' ' },
{ path: 'file2.txt', rename: undefined, x: '?', y: '?' },
{ path: 'file3.txt', rename: undefined, x: '?', y: '?' }
]);
});
test('rename split', () => {
const parser = new GitStatusParser();
parser.update('R newfile.txt\0fil');
parser.update('e.txt\0?? file2.txt\0?? file3.txt\0');
assert.deepEqual(parser.status, [
{ path: 'file.txt', rename: 'newfile.txt', x: 'R', y: ' ' },
{ path: 'file2.txt', rename: undefined, x: '?', y: '?' },
{ path: 'file3.txt', rename: undefined, x: '?', y: '?' }
]);
});
test('rename split 3', () => {
const parser = new GitStatusParser();
parser.update('?? file2.txt\0R new');
parser.update('file.txt\0fil');
parser.update('e.txt\0?? file3.txt\0');
assert.deepEqual(parser.status, [
{ path: 'file2.txt', rename: undefined, x: '?', y: '?' },
{ path: 'file.txt', rename: 'newfile.txt', x: 'R', y: ' ' },
{ path: 'file3.txt', rename: undefined, x: '?', y: '?' }
]);
});
});
});

9
extensions/git/src/typings/refs.d.ts vendored Normal file
View File

@@ -0,0 +1,9 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/// <reference path='../../../../src/vs/vscode.d.ts'/>
/// <reference path='../../../../src/vs/vscode.proposed.d.ts'/>
/// <reference types='@types/node'/>
/// <reference types='@types/mocha'/>

26
extensions/git/src/uri.ts Normal file
View File

@@ -0,0 +1,26 @@
/*---------------------------------------------------------------------------------------------
* 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 { Uri } from 'vscode';
export function fromGitUri(uri: Uri): { path: string; ref: string; } {
return JSON.parse(uri.query);
}
// As a mitigation for extensions like ESLint showing warnings and errors
// for git URIs, let's change the file extension of these uris to .git,
// when `replaceFileExtension` is true.
export function toGitUri(uri: Uri, ref: string, replaceFileExtension = false): Uri {
return uri.with({
scheme: 'git',
path: replaceFileExtension ? `${uri.path}.git` : uri.path,
query: JSON.stringify({
path: uri.fsPath,
ref
})
});
}

191
extensions/git/src/util.ts Normal file
View File

@@ -0,0 +1,191 @@
/*---------------------------------------------------------------------------------------------
* 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 { Event } from 'vscode';
import { dirname } from 'path';
import * as fs from 'fs';
export function log(...args: any[]): void {
console.log.apply(console, ['git:', ...args]);
}
export interface IDisposable {
dispose(): void;
}
export function dispose<T extends IDisposable>(disposables: T[]): T[] {
disposables.forEach(d => d.dispose());
return [];
}
export function toDisposable(dispose: () => void): IDisposable {
return { dispose };
}
export function combinedDisposable(disposables: IDisposable[]): IDisposable {
return toDisposable(() => dispose(disposables));
}
export const EmptyDisposable = toDisposable(() => null);
export function mapEvent<I, O>(event: Event<I>, map: (i: I) => O): Event<O> {
return (listener, thisArgs = null, disposables?) => event(i => listener.call(thisArgs, map(i)), null, disposables);
}
export function filterEvent<T>(event: Event<T>, filter: (e: T) => boolean): Event<T> {
return (listener, thisArgs = null, disposables?) => event(e => filter(e) && listener.call(thisArgs, e), null, disposables);
}
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))));
if (disposables) {
disposables.push(result);
}
return result;
};
}
export function done<T>(promise: Promise<T>): Promise<void> {
return promise.then<void>(() => void 0);
}
export function onceEvent<T>(event: Event<T>): Event<T> {
return (listener, thisArgs = null, disposables?) => {
const result = event(e => {
result.dispose();
return listener.call(thisArgs, e);
}, null, disposables);
return result;
};
}
export function eventToPromise<T>(event: Event<T>): Promise<T> {
return new Promise<T>(c => onceEvent(event)(c));
}
export function once(fn: (...args: any[]) => any): (...args: any[]) => any {
let didRun = false;
return (...args) => {
if (didRun) {
return;
}
return fn(...args);
};
}
export function assign<T>(destination: T, ...sources: any[]): T {
for (const source of sources) {
Object.keys(source).forEach(key => destination[key] = source[key]);
}
return destination;
}
export function uniqBy<T>(arr: T[], fn: (el: T) => string): T[] {
const seen = Object.create(null);
return arr.filter(el => {
const key = fn(el);
if (seen[key]) {
return false;
}
seen[key] = true;
return true;
});
}
export function groupBy<T>(arr: T[], fn: (el: T) => string): { [key: string]: T[] } {
return arr.reduce((result, el) => {
const key = fn(el);
result[key] = [...(result[key] || []), el];
return result;
}, Object.create(null));
}
export function denodeify<R>(fn: Function): (...args) => Promise<R> {
return (...args) => new Promise<R>((c, e) => fn(...args, (err, r) => err ? e(err) : c(r)));
}
export function nfcall<R>(fn: Function, ...args): Promise<R> {
return new Promise<R>((c, e) => fn(...args, (err, r) => err ? e(err) : c(r)));
}
export async function mkdirp(path: string, mode?: number): Promise<boolean> {
const mkdir = async () => {
try {
await nfcall(fs.mkdir, path, mode);
} catch (err) {
if (err.code === 'EEXIST') {
const stat = await nfcall<fs.Stats>(fs.stat, path);
if (stat.isDirectory) {
return;
}
throw new Error(`'${path}' exists and is not a directory.`);
}
throw err;
}
};
// is root?
if (path === dirname(path)) {
return true;
}
try {
await mkdir();
} catch (err) {
if (err.code !== 'ENOENT') {
throw err;
}
await mkdirp(dirname(path), mode);
await mkdir();
}
return true;
}
export function uniqueFilter<T>(keyFn: (t: T) => string): (t: T) => boolean {
const seen: { [key: string]: boolean; } = Object.create(null);
return element => {
const key = keyFn(element);
if (seen[key]) {
return false;
}
seen[key] = true;
return true;
};
}
export function find<T>(array: T[], fn: (t: T) => boolean): T | undefined {
let result: T | undefined = undefined;
array.some(e => {
if (fn(e)) {
result = e;
return true;
}
return false;
});
return result;
}

View File

@@ -0,0 +1 @@
--ui tdd out/test

View File

@@ -0,0 +1,15 @@
{
"compilerOptions": {
"target": "es6",
"lib": [
"es2016"
],
"module": "commonjs",
"outDir": "./out",
"strictNullChecks": true,
"experimentalDecorators": true
},
"include": [
"src/**/*"
]
}