├── .github └── assignment.yml ├── .gitignore ├── .vscode ├── launch.json ├── settings.json └── tasks.json ├── .vscodeignore ├── LICENSE ├── README.md ├── images ├── icon.png └── in_action.gif ├── package-lock.json ├── package.json ├── resources ├── dark │ └── refresh.svg └── light │ └── refresh.svg ├── src ├── extension.ts ├── git-credential-node.d.ts ├── github-issues-prs.ts └── utils.ts ├── thirdparty └── octicons │ ├── LICENSE │ ├── README │ ├── dark │ ├── bug.svg │ ├── git-pull-request.svg │ └── plus-small.svg │ └── light │ ├── bug.svg │ ├── git-pull-request.svg │ └── plus-small.svg ├── thirdpartynotices.txt ├── tsconfig.json └── tslint.json /.github/assignment.yml: -------------------------------------------------------------------------------- 1 | { 2 | perform: true, 3 | assignees: [ chrmarti ] 4 | } 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | out 2 | node_modules 3 | *.vsix -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | // A launch configuration that compiles the extension and then opens it inside a new window 2 | { 3 | "version": "0.1.0", 4 | "configurations": [ 5 | { 6 | "name": "Launch Extension", 7 | "type": "extensionHost", 8 | "request": "launch", 9 | "runtimeExecutable": "${execPath}", 10 | "args": ["--extensionDevelopmentPath=${workspaceRoot}"], 11 | "stopOnEntry": false, 12 | "sourceMaps": true, 13 | "outFiles": ["${workspaceRoot}/out/src/**/*.js"], 14 | "preLaunchTask": "npm" 15 | }, 16 | { 17 | "name": "Launch Tests", 18 | "type": "extensionHost", 19 | "request": "launch", 20 | "runtimeExecutable": "${execPath}", 21 | "args": ["--extensionDevelopmentPath=${workspaceRoot}", "--extensionTestsPath=${workspaceRoot}/out/test" ], 22 | "stopOnEntry": false, 23 | "sourceMaps": true, 24 | "outFiles": ["${workspaceRoot}/out/test/**/*.js"], 25 | "preLaunchTask": "npm" 26 | }, 27 | { 28 | "type": "node", 29 | "request": "attach", 30 | "name": "Attach to Extension Host", 31 | "protocol": "legacy", 32 | "port": 5870, 33 | "sourceMaps": true, 34 | "restart": true, 35 | "outDir": "${workspaceRoot}/out/src" 36 | } 37 | ] 38 | } 39 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | "files.exclude": { 4 | "out": false // set this to true to hide the "out" folder with the compiled JS files 5 | }, 6 | "search.exclude": { 7 | "out": true // set this to false to include "out" folder in search results 8 | }, 9 | "typescript.tsdk": "./node_modules/typescript/lib" // we want to use the TS server from our node_modules folder to control its version 10 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | // Available variables which can be used inside of strings. 2 | // ${workspaceRoot}: the root folder of the team 3 | // ${file}: the current opened file 4 | // ${fileBasename}: the current opened file's basename 5 | // ${fileDirname}: the current opened file's dirname 6 | // ${fileExtname}: the current opened file's extension 7 | // ${cwd}: the current working directory of the spawned process 8 | 9 | // A task runner that calls a custom npm script that compiles the extension. 10 | { 11 | "version": "0.1.0", 12 | 13 | // we want to run npm 14 | "command": "npm", 15 | 16 | // the command is a shell script 17 | "isShellCommand": true, 18 | 19 | // show the output window only if unrecognized errors occur. 20 | "showOutput": "silent", 21 | 22 | // we run the custom script "compile" as defined in package.json 23 | "args": ["run", "compile", "--loglevel", "silent"], 24 | 25 | // The tsc compiler is started in watching mode 26 | "isWatching": true, 27 | 28 | // use the standard tsc in watch mode problem matcher to find compile problems in the output. 29 | "problemMatcher": "$tsc-watch" 30 | } -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | .vscode/** 2 | .vscode-test/** 3 | out/test/** 4 | test/** 5 | src/** 6 | **/*.map 7 | .gitignore 8 | tsconfig.json 9 | vsc-extension-quickstart.md 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) Microsoft Corporation 2 | 3 | All rights reserved. 4 | 5 | MIT License 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation 8 | files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, 9 | modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 15 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 16 | BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT 17 | OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GitHub Issues 2 | 3 | View the issues assigned to you in the Explorer viewlet. Currently the username and repository are taken from the Git configuration and only issues assigned to that user and to the next two milestones are shown. 4 | 5 | ![GitHub Issues in Action](images/in_action.gif) 6 | 7 | ## Release Notes 8 | 9 | ### 0.9.3 10 | 11 | - Ignore folder casing for owner and repo (#44). 12 | - Update username and host settings to application scope. 13 | - Update to use @types/vscode. 14 | 15 | ### 0.9.2 16 | 17 | - Update https-proxy-agent (https://www.npmjs.com/advisories/1184). 18 | 19 | ### 0.9.1 20 | 21 | - Avoid naming overlap with PR extension. 22 | 23 | ### 0.9.0 24 | 25 | - Support for GitHub Enterprise with the `"github.host"` setting (@Ikuyadeu) 26 | 27 | ### 0.8.0 28 | 29 | - Show all milestones and improve sorting 30 | - Open single page for Open Milestone 31 | 32 | ### 0.7.0 33 | 34 | - Checkout Pull Request: Improve finding existing remote and branch 35 | - 'Multi-root ready' keyword 36 | 37 | ### 0.6.0 38 | 39 | - Add Copy Url command (@Ikuyadeu) 40 | - Fix tslint semicolon setting (@Ikuyadeu) 41 | - Add Open Milestone command 42 | - Add Checkout Pull Request command 43 | 44 | ### 0.5.0 45 | 46 | - Add multiple workspace folder support 47 | - Add setting for additional GitHub repositories 48 | 49 | ### 0.4.0 50 | 51 | - Add action for creating issues (@jens1o) 52 | - Fix parsing of repository names with dots (@wraith13) 53 | 54 | ### 0.3.x 55 | 56 | - Bugfixes 57 | 58 | ### 0.2.0 59 | 60 | - Support for private repositories (using Git credentials manager). 61 | - Add `github.username` setting. 62 | 63 | ### 0.1.0 64 | 65 | Initial release. 66 | 67 | ## Contributing 68 | 69 | File bugs and feature requests in [GitHub Issues](https://github.com/Microsoft/vscode-github-issues-prs/issues). 70 | 71 | Checkout the source code in the [GitHub Repository](https://github.com/Microsoft/vscode-github-issues-prs). 72 | 73 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. 74 | 75 | ## License 76 | [MIT](LICENSE) 77 | -------------------------------------------------------------------------------- /images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/vscode-github-issues-prs/7b5bd4e050c210c89b7197709db75313cfa10a0c/images/icon.png -------------------------------------------------------------------------------- /images/in_action.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/vscode-github-issues-prs/7b5bd4e050c210c89b7197709db75313cfa10a0c/images/in_action.gif -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "github-issues-prs", 3 | "version": "0.9.3", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@octokit/rest": { 8 | "version": "15.18.1", 9 | "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-15.18.1.tgz", 10 | "integrity": "sha512-g2tecjp2TEtYV8bKAFvfQtu+W29HM7ektmWmw8zrMy9/XCKDEYRErR2YvvhN9+IxkLC4O3lDqYP4b6WgsL6Utw==", 11 | "requires": { 12 | "before-after-hook": "^1.1.0", 13 | "btoa-lite": "^1.0.0", 14 | "debug": "^3.1.0", 15 | "http-proxy-agent": "^2.1.0", 16 | "https-proxy-agent": "^2.2.0", 17 | "lodash": "^4.17.4", 18 | "node-fetch": "^2.1.1", 19 | "universal-user-agent": "^2.0.0", 20 | "url-template": "^2.0.8" 21 | } 22 | }, 23 | "@types/node": { 24 | "version": "10.3.0", 25 | "resolved": "https://registry.npmjs.org/@types/node/-/node-10.3.0.tgz", 26 | "integrity": "sha512-hWzNviaVFIr1TqcRA8ou49JaSHp+Rfabmnqg2kNvusKqLhPU0rIsGPUj5WJJ7ld4Bb7qdgLmIhLfCD1qS08IVA==", 27 | "dev": true 28 | }, 29 | "@types/vscode": { 30 | "version": "1.53.0", 31 | "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.53.0.tgz", 32 | "integrity": "sha512-XjFWbSPOM0EKIT2XhhYm3D3cx3nn3lshMUcWNy1eqefk+oqRuBq8unVb6BYIZqXy9lQZyeUl7eaBCOZWv+LcXQ==", 33 | "dev": true 34 | }, 35 | "agent-base": { 36 | "version": "4.2.1", 37 | "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.2.1.tgz", 38 | "integrity": "sha512-JVwXMr9nHYTUXsBFKUqhJwvlcYU/blreOEUkhNR2eXZIvwd+c+o5V4MgDPKWnMS/56awN3TRzIP+KoPn+roQtg==", 39 | "requires": { 40 | "es6-promisify": "^5.0.0" 41 | } 42 | }, 43 | "before-after-hook": { 44 | "version": "1.4.0", 45 | "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-1.4.0.tgz", 46 | "integrity": "sha512-l5r9ir56nda3qu14nAXIlyq1MmUSs0meCIaFAh8HwkFwP1F8eToOuS3ah2VAHHcY04jaYD7FpJC5JTXHYRbkzg==" 47 | }, 48 | "btoa-lite": { 49 | "version": "1.0.0", 50 | "resolved": "https://registry.npmjs.org/btoa-lite/-/btoa-lite-1.0.0.tgz", 51 | "integrity": "sha1-M3dm2hWAEhD92VbCLpxokaudAzc=" 52 | }, 53 | "cross-spawn": { 54 | "version": "5.1.0", 55 | "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", 56 | "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", 57 | "requires": { 58 | "lru-cache": "^4.0.1", 59 | "shebang-command": "^1.2.0", 60 | "which": "^1.2.9" 61 | } 62 | }, 63 | "debug": { 64 | "version": "3.1.0", 65 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", 66 | "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", 67 | "requires": { 68 | "ms": "2.0.0" 69 | } 70 | }, 71 | "es6-promise": { 72 | "version": "4.2.6", 73 | "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.6.tgz", 74 | "integrity": "sha512-aRVgGdnmW2OiySVPUC9e6m+plolMAJKjZnQlCwNSuK5yQ0JN61DZSO1X1Ufd1foqWRAlig0rhduTCHe7sVtK5Q==" 75 | }, 76 | "es6-promisify": { 77 | "version": "5.0.0", 78 | "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", 79 | "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=", 80 | "requires": { 81 | "es6-promise": "^4.0.3" 82 | } 83 | }, 84 | "execa": { 85 | "version": "0.6.3", 86 | "resolved": "https://registry.npmjs.org/execa/-/execa-0.6.3.tgz", 87 | "integrity": "sha1-V7aaWU8IF1nGnlNw8NF7nLEWWP4=", 88 | "requires": { 89 | "cross-spawn": "^5.0.1", 90 | "get-stream": "^3.0.0", 91 | "is-stream": "^1.1.0", 92 | "npm-run-path": "^2.0.0", 93 | "p-finally": "^1.0.0", 94 | "signal-exit": "^3.0.0", 95 | "strip-eof": "^1.0.0" 96 | } 97 | }, 98 | "get-stream": { 99 | "version": "3.0.0", 100 | "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", 101 | "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=" 102 | }, 103 | "git-credential-node": { 104 | "version": "1.1.0", 105 | "resolved": "https://registry.npmjs.org/git-credential-node/-/git-credential-node-1.1.0.tgz", 106 | "integrity": "sha1-JQdUSsAx0Ags2eD4M6rHWPFf2A0=", 107 | "requires": { 108 | "execa": "^0.6.0" 109 | } 110 | }, 111 | "http-proxy-agent": { 112 | "version": "2.1.0", 113 | "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz", 114 | "integrity": "sha512-qwHbBLV7WviBl0rQsOzH6o5lwyOIvwp/BdFnvVxXORldu5TmjFfjzBcWUWS5kWAZhmv+JtiDhSuQCp4sBfbIgg==", 115 | "requires": { 116 | "agent-base": "4", 117 | "debug": "3.1.0" 118 | } 119 | }, 120 | "https-proxy-agent": { 121 | "version": "2.2.4", 122 | "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz", 123 | "integrity": "sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==", 124 | "requires": { 125 | "agent-base": "^4.3.0", 126 | "debug": "^3.1.0" 127 | }, 128 | "dependencies": { 129 | "agent-base": { 130 | "version": "4.3.0", 131 | "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz", 132 | "integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==", 133 | "requires": { 134 | "es6-promisify": "^5.0.0" 135 | } 136 | } 137 | } 138 | }, 139 | "is-stream": { 140 | "version": "1.1.0", 141 | "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", 142 | "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" 143 | }, 144 | "isexe": { 145 | "version": "2.0.0", 146 | "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", 147 | "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" 148 | }, 149 | "lodash": { 150 | "version": "4.17.21", 151 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", 152 | "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" 153 | }, 154 | "lru-cache": { 155 | "version": "4.1.1", 156 | "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.1.tgz", 157 | "integrity": "sha512-q4spe4KTfsAS1SUHLO0wz8Qiyf1+vMIAgpRYioFYDMNqKfHQbg+AVDH3i4fvpl71/P1L0dBl+fQi+P37UYf0ew==", 158 | "requires": { 159 | "pseudomap": "^1.0.2", 160 | "yallist": "^2.1.2" 161 | } 162 | }, 163 | "macos-release": { 164 | "version": "2.1.0", 165 | "resolved": "https://registry.npmjs.org/macos-release/-/macos-release-2.1.0.tgz", 166 | "integrity": "sha512-8TCbwvN1mfNxbBv0yBtfyIFMo3m1QsNbKHv7PYIp/abRBKVQBXN7ecu3aeGGgT18VC/Tf397LBDGZF9KBGJFFw==" 167 | }, 168 | "moment": { 169 | "version": "2.24.0", 170 | "resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz", 171 | "integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==" 172 | }, 173 | "ms": { 174 | "version": "2.0.0", 175 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 176 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 177 | }, 178 | "nice-try": { 179 | "version": "1.0.5", 180 | "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", 181 | "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==" 182 | }, 183 | "node-fetch": { 184 | "version": "2.6.1", 185 | "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", 186 | "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==" 187 | }, 188 | "npm-run-path": { 189 | "version": "2.0.2", 190 | "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", 191 | "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", 192 | "requires": { 193 | "path-key": "^2.0.0" 194 | } 195 | }, 196 | "os-name": { 197 | "version": "3.0.0", 198 | "resolved": "https://registry.npmjs.org/os-name/-/os-name-3.0.0.tgz", 199 | "integrity": "sha512-7c74tib2FsdFbQ3W+qj8Tyd1R3Z6tuVRNNxXjJcZ4NgjIEQU9N/prVMqcW29XZPXGACqaXN3jq58/6hoaoXH6g==", 200 | "requires": { 201 | "macos-release": "^2.0.0", 202 | "windows-release": "^3.1.0" 203 | } 204 | }, 205 | "p-finally": { 206 | "version": "1.0.0", 207 | "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", 208 | "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=" 209 | }, 210 | "path-key": { 211 | "version": "2.0.1", 212 | "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", 213 | "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=" 214 | }, 215 | "pseudomap": { 216 | "version": "1.0.2", 217 | "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", 218 | "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=" 219 | }, 220 | "semver": { 221 | "version": "5.5.1", 222 | "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.1.tgz", 223 | "integrity": "sha512-PqpAxfrEhlSUWge8dwIp4tZnQ25DIOthpiaHNIthsjEFQD6EvqUKUDM7L8O2rShkFccYo1VjJR0coWfNkCubRw==" 224 | }, 225 | "shebang-command": { 226 | "version": "1.2.0", 227 | "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", 228 | "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", 229 | "requires": { 230 | "shebang-regex": "^1.0.0" 231 | } 232 | }, 233 | "shebang-regex": { 234 | "version": "1.0.0", 235 | "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", 236 | "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=" 237 | }, 238 | "signal-exit": { 239 | "version": "3.0.2", 240 | "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", 241 | "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" 242 | }, 243 | "strip-eof": { 244 | "version": "1.0.0", 245 | "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", 246 | "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=" 247 | }, 248 | "typescript": { 249 | "version": "3.3.4000", 250 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.3.4000.tgz", 251 | "integrity": "sha512-jjOcCZvpkl2+z7JFn0yBOoLQyLoIkNZAs/fYJkUG6VKy6zLPHJGfQJYFHzibB6GJaF/8QrcECtlQ5cpvRHSMEA==", 252 | "dev": true 253 | }, 254 | "universal-user-agent": { 255 | "version": "2.0.3", 256 | "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-2.0.3.tgz", 257 | "integrity": "sha512-eRHEHhChCBHrZsA4WEhdgiOKgdvgrMIHwnwnqD0r5C6AO8kwKcG7qSku3iXdhvHL3YvsS9ZkSGN8h/hIpoFC8g==", 258 | "requires": { 259 | "os-name": "^3.0.0" 260 | } 261 | }, 262 | "url-template": { 263 | "version": "2.0.8", 264 | "resolved": "https://registry.npmjs.org/url-template/-/url-template-2.0.8.tgz", 265 | "integrity": "sha1-/FZaPMy/93MMd19WQflVV5FDnyE=" 266 | }, 267 | "which": { 268 | "version": "1.2.14", 269 | "resolved": "https://registry.npmjs.org/which/-/which-1.2.14.tgz", 270 | "integrity": "sha1-mofEN48D6CfOyvGs31bHNsAcFOU=", 271 | "requires": { 272 | "isexe": "^2.0.0" 273 | } 274 | }, 275 | "windows-release": { 276 | "version": "3.1.0", 277 | "resolved": "https://registry.npmjs.org/windows-release/-/windows-release-3.1.0.tgz", 278 | "integrity": "sha512-hBb7m7acFgQPQc222uEQTmdcGLeBmQLNLFIh0rDk3CwFOBrfjefLzEfEfmpMq8Af/n/GnFf3eYf203FY1PmudA==", 279 | "requires": { 280 | "execa": "^0.10.0" 281 | }, 282 | "dependencies": { 283 | "cross-spawn": { 284 | "version": "6.0.5", 285 | "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", 286 | "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", 287 | "requires": { 288 | "nice-try": "^1.0.4", 289 | "path-key": "^2.0.1", 290 | "semver": "^5.5.0", 291 | "shebang-command": "^1.2.0", 292 | "which": "^1.2.9" 293 | } 294 | }, 295 | "execa": { 296 | "version": "0.10.0", 297 | "resolved": "https://registry.npmjs.org/execa/-/execa-0.10.0.tgz", 298 | "integrity": "sha512-7XOMnz8Ynx1gGo/3hyV9loYNPWM94jG3+3T3Y8tsfSstFmETmENCMU/A/zj8Lyaj1lkgEepKepvd6240tBRvlw==", 299 | "requires": { 300 | "cross-spawn": "^6.0.0", 301 | "get-stream": "^3.0.0", 302 | "is-stream": "^1.1.0", 303 | "npm-run-path": "^2.0.0", 304 | "p-finally": "^1.0.0", 305 | "signal-exit": "^3.0.0", 306 | "strip-eof": "^1.0.0" 307 | } 308 | } 309 | } 310 | }, 311 | "yallist": { 312 | "version": "2.1.2", 313 | "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", 314 | "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" 315 | } 316 | } 317 | } 318 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "github-issues-prs", 3 | "displayName": "GitHub Issues", 4 | "description": "View the issues assigned to you in the Explorer viewlet.", 5 | "version": "0.9.3", 6 | "publisher": "ms-vscode", 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/Microsoft/vscode-github-issues-prs.git" 10 | }, 11 | "license": "MIT", 12 | "bugs": { 13 | "url": "https://github.com/Microsoft/vscode-github-issues-prs/issues" 14 | }, 15 | "engines": { 16 | "vscode": "^1.31.0" 17 | }, 18 | "categories": [ 19 | "Other" 20 | ], 21 | "keywords": [ 22 | "multi-root ready" 23 | ], 24 | "activationEvents": [ 25 | "onView:githubIssuesPrs" 26 | ], 27 | "main": "./out/src/extension", 28 | "icon": "images/icon.png", 29 | "contributes": { 30 | "views": { 31 | "explorer": [ 32 | { 33 | "id": "githubIssuesPrs", 34 | "name": "GitHub Issues" 35 | } 36 | ] 37 | }, 38 | "commands": [ 39 | { 40 | "command": "githubIssuesPrs.refresh", 41 | "title": "Refresh", 42 | "icon": { 43 | "light": "resources/light/refresh.svg", 44 | "dark": "resources/dark/refresh.svg" 45 | } 46 | }, 47 | { 48 | "command": "githubIssuesPrs.createIssue", 49 | "title": "Create Issue", 50 | "icon": { 51 | "light": "thirdparty/octicons/light/plus-small.svg", 52 | "dark": "thirdparty/octicons/dark/plus-small.svg" 53 | } 54 | }, 55 | { 56 | "command": "githubIssuesPrs.openMilestone", 57 | "title": "Open Milestone" 58 | }, 59 | { 60 | "command": "githubIssuesPrs.openIssue", 61 | "title": "Open Issue" 62 | }, 63 | { 64 | "command": "githubIssuesPrs.openPullRequest", 65 | "title": "Open Pull Request" 66 | }, 67 | { 68 | "command": "githubIssuesPrs.checkoutPullRequest", 69 | "title": "Checkout Pull Request" 70 | }, 71 | { 72 | "command": "githubIssuesPrs.copyNumber", 73 | "title": "Copy Number" 74 | }, 75 | { 76 | "command": "githubIssuesPrs.copyText", 77 | "title": "Copy Text" 78 | }, 79 | { 80 | "command": "githubIssuesPrs.copyMarkdown", 81 | "title": "Copy Markdown" 82 | }, 83 | { 84 | "command": "githubIssuesPrs.copyUrl", 85 | "title": "Copy Url" 86 | } 87 | ], 88 | "menus": { 89 | "view/title": [ 90 | { 91 | "command": "githubIssuesPrs.refresh", 92 | "when": "view == githubIssuesPrs", 93 | "group": "navigation" 94 | }, 95 | { 96 | "command": "githubIssuesPrs.createIssue", 97 | "when": "view == githubIssuesPrs", 98 | "group": "navigation" 99 | } 100 | ], 101 | "view/item/context": [ 102 | { 103 | "command": "githubIssuesPrs.openMilestone", 104 | "when": "view == githubIssuesPrs && viewItem == milestone", 105 | "group": "1_navigation" 106 | }, 107 | { 108 | "command": "githubIssuesPrs.openIssue", 109 | "when": "view == githubIssuesPrs && viewItem == issue", 110 | "group": "1_navigation" 111 | }, 112 | { 113 | "command": "githubIssuesPrs.copyNumber", 114 | "when": "view == githubIssuesPrs && viewItem == issue", 115 | "group": "9_cutcopypaste" 116 | }, 117 | { 118 | "command": "githubIssuesPrs.copyText", 119 | "when": "view == githubIssuesPrs && viewItem == issue", 120 | "group": "9_cutcopypaste" 121 | }, 122 | { 123 | "command": "githubIssuesPrs.copyMarkdown", 124 | "when": "view == githubIssuesPrs && viewItem == issue", 125 | "group": "9_cutcopypaste" 126 | }, 127 | { 128 | "command": "githubIssuesPrs.copyUrl", 129 | "when": "view == githubIssuesPrs && viewItem == issue", 130 | "group": "9_cutcopypaste" 131 | }, 132 | { 133 | "command": "githubIssuesPrs.openPullRequest", 134 | "when": "view == githubIssuesPrs && viewItem == pull_request", 135 | "group": "1_navigation" 136 | }, 137 | { 138 | "command": "githubIssuesPrs.checkoutPullRequest", 139 | "when": "view == githubIssuesPrs && viewItem == pull_request", 140 | "group": "1_navigation" 141 | }, 142 | { 143 | "command": "githubIssuesPrs.copyNumber", 144 | "when": "view == githubIssuesPrs && viewItem == pull_request", 145 | "group": "9_cutcopypaste" 146 | }, 147 | { 148 | "command": "githubIssuesPrs.copyText", 149 | "when": "view == githubIssuesPrs && viewItem == pull_request", 150 | "group": "9_cutcopypaste" 151 | }, 152 | { 153 | "command": "githubIssuesPrs.copyMarkdown", 154 | "when": "view == githubIssuesPrs && viewItem == pull_request", 155 | "group": "9_cutcopypaste" 156 | }, 157 | { 158 | "command": "githubIssuesPrs.copyUrl", 159 | "when": "view == githubIssuesPrs && viewItem == pull_request", 160 | "group": "9_cutcopypaste" 161 | } 162 | ] 163 | }, 164 | "configuration": { 165 | "type": "object", 166 | "title": "GitHub configuration", 167 | "properties": { 168 | "github.username": { 169 | "scope": "application", 170 | "type": [ 171 | "string", 172 | "null" 173 | ], 174 | "default": null, 175 | "description": "The username to use when accessing GitHub. The default is to consult the Git credential manager." 176 | }, 177 | "github.repositories": { 178 | "type": "array", 179 | "default": [], 180 | "description": "A list of repositories to query for issues." 181 | }, 182 | "github.host": { 183 | "scope": "application", 184 | "type": "string", 185 | "default": "github.com", 186 | "description": "The host name to access GitHub. Change this to your GitHub Enterprise host." 187 | } 188 | } 189 | } 190 | }, 191 | "scripts": { 192 | "vscode:prepublish": "tsc -p ./", 193 | "compile": "tsc -watch -p ./" 194 | }, 195 | "devDependencies": { 196 | "@types/node": "10.3.0", 197 | "@types/vscode": "^1.31.0", 198 | "typescript": "3.3.4000" 199 | }, 200 | "dependencies": { 201 | "@octokit/rest": "15.18.1", 202 | "git-credential-node": "1.1.0", 203 | "moment": "2.24.0" 204 | } 205 | } 206 | -------------------------------------------------------------------------------- /resources/dark/refresh.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/light/refresh.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/extension.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import { ExtensionContext, window } from 'vscode'; 4 | 5 | import { GitHubIssuesPrsProvider } from './github-issues-prs'; 6 | 7 | export function activate(context: ExtensionContext) { 8 | window.registerTreeDataProvider('githubIssuesPrs', new GitHubIssuesPrsProvider(context)); 9 | } 10 | -------------------------------------------------------------------------------- /src/git-credential-node.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'git-credential-node' { 2 | 3 | interface Credentials { 4 | username: string; 5 | password: string; 6 | } 7 | 8 | function fill(url: string): Promise; 9 | } -------------------------------------------------------------------------------- /src/github-issues-prs.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path'; 2 | 3 | import * as GitHub from '@octokit/rest'; 4 | import { fill } from 'git-credential-node'; 5 | import * as moment from 'moment'; 6 | 7 | import { EventEmitter, TreeDataProvider, TreeItem, ExtensionContext, QuickPickItem, Uri, TreeItemCollapsibleState, WorkspaceFolder, window, workspace, commands, env } from 'vscode'; 8 | 9 | import { exec, allMatches, fetchAll } from './utils'; 10 | 11 | interface GitRemote { 12 | url: string; 13 | owner: string; 14 | repo: string; 15 | username: string | null; 16 | password: string | null; 17 | folders: WorkspaceFolder[]; 18 | } 19 | 20 | class Milestone extends TreeItem { 21 | 22 | public issues: Issue[] = []; 23 | 24 | constructor(label: string, public item: any | undefined) { 25 | super(label, TreeItemCollapsibleState.Collapsed); 26 | this.contextValue = 'milestone'; 27 | } 28 | } 29 | 30 | class Issue extends TreeItem { 31 | constructor(label: string, public query: { remote: GitRemote; assignee: string | undefined; }, public item: any) { 32 | super(label); 33 | } 34 | } 35 | 36 | interface RemoteQuickPickItem extends QuickPickItem { 37 | remote: GitRemote; 38 | } 39 | 40 | export class GitHubIssuesPrsProvider implements TreeDataProvider { 41 | 42 | private _onDidChangeTreeData = new EventEmitter(); 43 | readonly onDidChangeTreeData = this._onDidChangeTreeData.event; 44 | 45 | private fetching = false; 46 | private lastFetch: number | undefined; 47 | private children: Promise | undefined; 48 | 49 | private username: string | undefined; 50 | private host: string; 51 | private repositories: string[]; 52 | 53 | constructor(private context: ExtensionContext) { 54 | const subscriptions = context.subscriptions; 55 | subscriptions.push(commands.registerCommand('githubIssuesPrs.refresh', this.refresh, this)); 56 | subscriptions.push(commands.registerCommand('githubIssuesPrs.createIssue', this.createIssue, this)); 57 | subscriptions.push(commands.registerCommand('githubIssuesPrs.openMilestone', this.openMilestone, this)); 58 | subscriptions.push(commands.registerCommand('githubIssuesPrs.openIssue', this.openIssue, this)); 59 | subscriptions.push(commands.registerCommand('githubIssuesPrs.openPullRequest', this.openIssue, this)); 60 | subscriptions.push(commands.registerCommand('githubIssuesPrs.checkoutPullRequest', this.checkoutPullRequest, this)); 61 | subscriptions.push(commands.registerCommand('githubIssuesPrs.copyNumber', this.copyNumber, this)); 62 | subscriptions.push(commands.registerCommand('githubIssuesPrs.copyText', this.copyText, this)); 63 | subscriptions.push(commands.registerCommand('githubIssuesPrs.copyMarkdown', this.copyMarkdown, this)); 64 | subscriptions.push(commands.registerCommand('githubIssuesPrs.copyUrl', this.copyUrl, this)); 65 | 66 | subscriptions.push(window.onDidChangeActiveTextEditor(this.poll, this)); 67 | 68 | const config = workspace.getConfiguration('github'); 69 | this.username = config.get('username'); 70 | this.repositories = config.get('repositories') || []; 71 | this.host = config.get('host') || 'github.com'; 72 | subscriptions.push(workspace.onDidChangeConfiguration(() => { 73 | const config = workspace.getConfiguration('github'); 74 | const newUsername = config.get('username'); 75 | const newRepositories = config.get('repositories') || []; 76 | const newHost = config.get('host'); 77 | if (newUsername !== this.username || JSON.stringify(newRepositories) !== JSON.stringify(this.repositories) || newHost !== this.host) { 78 | this.username = newUsername; 79 | this.repositories = newRepositories; 80 | this.host = newHost || 'github.com'; 81 | this.refresh(); 82 | } 83 | })); 84 | 85 | subscriptions.push(workspace.onDidChangeWorkspaceFolders(this.refresh, this)); 86 | } 87 | 88 | getTreeItem(element: TreeItem): TreeItem { 89 | return element; 90 | } 91 | 92 | async getChildren(element?: TreeItem): Promise { 93 | if (element instanceof Milestone) { 94 | return element.issues; 95 | } 96 | 97 | if (!this.children) { 98 | try { 99 | this.fetching = true; 100 | this.lastFetch = Date.now(); 101 | await (this.children = this.fetchChildren()); 102 | } finally { 103 | this.fetching = false; 104 | } 105 | } 106 | 107 | return this.children; 108 | } 109 | 110 | private async refresh() { 111 | if (!this.fetching) { 112 | this.children = undefined; 113 | await this.getChildren(); 114 | this._onDidChangeTreeData.fire(undefined); 115 | } 116 | } 117 | 118 | private async createIssue() { 119 | 120 | const remotes = await this.getGitHubRemotes(); 121 | if (!remotes.length) { 122 | return false; 123 | } 124 | 125 | let urls: RemoteQuickPickItem[] = remotes.map(remote => { 126 | let remoteItem: RemoteQuickPickItem = { 127 | label: remote.owner + '/' + remote.repo, 128 | description: '', 129 | remote: remote 130 | }; 131 | 132 | return remoteItem; 133 | }); 134 | 135 | if (!urls.length) { 136 | window.showInformationMessage('There is no remote to get data from!'); 137 | return; 138 | } 139 | 140 | const callback = async (selectedRemote: RemoteQuickPickItem | undefined) => { 141 | if (!selectedRemote) { 142 | return; 143 | } 144 | 145 | const github = new GitHub(this.getAPIOption()); 146 | 147 | if (selectedRemote.remote.username && selectedRemote.remote.password) { 148 | github.authenticate({ 149 | type: 'basic', 150 | username: selectedRemote.remote.username, 151 | password: selectedRemote.remote.password 152 | }); 153 | } 154 | 155 | const data = await github.repos.get({ 156 | owner: selectedRemote.remote.owner, 157 | repo: selectedRemote.remote.repo 158 | }); 159 | // TODO: Store in cache 160 | await env.openExternal(Uri.parse(data.data.html_url + '/issues/new')); 161 | 162 | }; 163 | 164 | // shortcut when there is just one remote 165 | if (urls.length === 1) { 166 | callback(urls[0]); 167 | return; 168 | } 169 | 170 | const pick = await window.showQuickPick( 171 | urls, 172 | { 173 | placeHolder: 'Select the remote you want to create an issue on' 174 | } 175 | ); 176 | callback(pick); 177 | } 178 | 179 | private async poll() { 180 | if (!this.lastFetch || this.lastFetch + 30 * 60 * 1000 < Date.now()) { 181 | return this.refresh(); 182 | } 183 | } 184 | 185 | private async fetchChildren(element?: TreeItem): Promise { 186 | const remotes = await this.getGitHubRemotes(); 187 | if (!remotes.length) { 188 | return [new TreeItem('No GitHub repositories found')]; 189 | } 190 | 191 | const { username, password } = remotes[0]; 192 | const assignee = this.username || username || undefined; 193 | if (!assignee) { 194 | const configure = new TreeItem('Configure "github.username" in settings'); 195 | configure.command = { 196 | title: 'Open Settings', 197 | command: 'workbench.action.openGlobalSettings' 198 | }; 199 | return [configure]; 200 | } 201 | 202 | let issues: Issue[]; 203 | try { 204 | issues = await this.fetchAllIssues(remotes, assignee, username || undefined, password || undefined); 205 | } catch (err) { 206 | if (err.code === 401 && password) { 207 | issues = await this.fetchAllIssues(remotes, assignee, username || undefined, undefined); 208 | } else { 209 | throw err; 210 | } 211 | } 212 | 213 | if (!issues.length) { 214 | return [new TreeItem(`No issues found for @${assignee}`)]; 215 | } 216 | 217 | const milestoneIndex: { [title: string]: Milestone; } = {}; 218 | const milestones: Milestone[] = []; 219 | for (const issue of issues) { 220 | const m = issue.item.milestone; 221 | const milestoneLabel = m && m.title || 'No Milestone'; 222 | let milestone = milestoneIndex[milestoneLabel]; 223 | if (!milestone) { 224 | milestone = new Milestone(milestoneLabel, m); 225 | milestoneIndex[milestoneLabel] = milestone; 226 | milestones.push(milestone); 227 | } else if (m && m.due_on && !(milestone.item && milestone.item.due_on)) { 228 | milestone.item = m; 229 | } 230 | milestone.issues.push(issue); 231 | } 232 | 233 | for (const milestone of milestones) { 234 | milestone.label = `${milestone.label} (${milestone.issues.length})`; 235 | } 236 | 237 | milestones.sort((a, b) => { 238 | // No Milestone 239 | if (!a.item) { 240 | return 1; 241 | } else if (!b.item) { 242 | return -1; 243 | } 244 | 245 | const t1 = this.parseDueOn(a); 246 | const t2 = this.parseDueOn(b); 247 | if (t1 && t2) { 248 | if (!t1.isSame(t2)) { 249 | return t1.isBefore(t2) ? -1 : 1; 250 | } 251 | } else if (t1) { 252 | return -1; 253 | } else if (t2) { 254 | return 1; 255 | } 256 | 257 | return a.item.title.localeCompare(b.item.title); 258 | }); 259 | 260 | if (milestones.length) { 261 | milestones[0].collapsibleState = TreeItemCollapsibleState.Expanded; 262 | } 263 | 264 | return milestones; 265 | } 266 | 267 | private parseDueOn(m: Milestone) { 268 | if (!m.item) { 269 | return; 270 | } 271 | 272 | if (m.item.due_on) { 273 | const t = moment.utc(m.item.due_on, 'YYYY-MM-DDTHH:mm:ssZ'); 274 | if (t.isValid()) { 275 | return t; 276 | } 277 | } 278 | 279 | if (m.item.title) { 280 | const t = moment.utc(m.item.title, 'MMMM YYYY'); 281 | if (t.isValid()) { 282 | return t.add(14, 'days'); // "best guess" 283 | } 284 | } 285 | } 286 | 287 | private async fetchAllIssues(remotes: GitRemote[], assignee: string, username?: string, password?: string) { 288 | const github = new GitHub(this.getAPIOption()); 289 | if (username && password) { 290 | github.authenticate({ 291 | type: 'basic', 292 | username, 293 | password 294 | }); 295 | } 296 | 297 | const params = { 298 | q: `is:open assignee:${assignee}`, 299 | sort: 'created', 300 | order: 'asc', 301 | per_page: 100 302 | }; 303 | const items = await fetchAll(github, github.search.issues(params)); 304 | 305 | return items 306 | .map((item: any) => ({ 307 | item, 308 | remote: remotes.find(remote => item.repository_url.toLowerCase().endsWith(`/${remote.owner.toLowerCase()}/${remote.repo.toLowerCase()}`)) 309 | })) 310 | .filter(({ remote }) => !!remote) 311 | .map(({ item, remote }) => { 312 | const issue = new Issue(`${item.title} (#${item.number})`, { remote: remote!, assignee }, item); 313 | const icon = item.pull_request ? 'git-pull-request.svg' : 'bug.svg'; 314 | issue.iconPath = { 315 | light: this.context.asAbsolutePath(path.join('thirdparty', 'octicons', 'light', icon)), 316 | dark: this.context.asAbsolutePath(path.join('thirdparty', 'octicons', 'dark', icon)) 317 | }; 318 | issue.command = { 319 | title: 'Open', 320 | command: item.pull_request ? 'githubIssuesPrs.openPullRequest' : 'githubIssuesPrs.openIssue', 321 | arguments: [issue] 322 | }; 323 | issue.contextValue = item.pull_request ? 'pull_request' : 'issue'; 324 | return issue; 325 | }); 326 | } 327 | 328 | private async openMilestone(milestone: Milestone) { 329 | const issue = milestone.issues[0]; 330 | const item = issue.item; 331 | const assignee = issue.query.assignee; 332 | const url = `https://github.com/issues?utf8=%E2%9C%93&q=is%3Aopen+${item.milestone ? `milestone%3A%22${item.milestone.title}%22` : 'no%3Amilestone'}${assignee ? '+assignee%3A' + assignee : ''}`; 333 | return env.openExternal(Uri.parse(url)); 334 | } 335 | 336 | private async openIssue(issue: Issue) { 337 | return env.openExternal(Uri.parse(issue.item.html_url)); 338 | } 339 | 340 | private async checkoutPullRequest(issue: Issue) { 341 | 342 | const remote = issue.query.remote; 343 | const folder = remote.folders[0]; 344 | if (!folder) { 345 | return window.showInformationMessage(`The repository '${remote.owner}/${remote.repo}' is not checked out in any open workspace folder.`); 346 | } 347 | 348 | const status = await exec(`git status --short --porcelain`, { cwd: folder.uri.fsPath }); 349 | if (status.stdout) { 350 | return window.showInformationMessage(`There are local changes in the workspace folder. Commit or stash them before checking out the pull request.`); 351 | } 352 | 353 | const github = new GitHub(this.getAPIOption()); 354 | const p = Uri.parse(issue.item.repository_url).path; 355 | const repo = path.basename(p); 356 | const owner = path.basename(path.dirname(p)); 357 | const pr = await github.pullRequests.get({ owner, repo, number: issue.item.number }); 358 | const repo_login = pr.data.head!.repo.owner.login; 359 | const user_login = pr.data.user!.login; 360 | const clone_url = pr.data.head!.repo.clone_url; 361 | const remoteBranch = pr.data.head!.ref; 362 | try { 363 | let remote: string | undefined = undefined; 364 | const remotes = await exec(`git remote -v`, { cwd: folder.uri.fsPath }); 365 | let m: RegExpExecArray | null; 366 | const r = /^([^\s]+)\s+([^\s]+)\s+\(fetch\)/gm; 367 | while (m = r.exec(remotes.stdout)) { 368 | let fetch_url = m[2]; 369 | if (!fetch_url.endsWith('.git')) { 370 | fetch_url += '.git'; 371 | } 372 | if (fetch_url === clone_url) { 373 | remote = m[1]; 374 | break; 375 | } 376 | } 377 | if (!remote) { 378 | remote = await window.showInputBox({ 379 | prompt: 'Name for the remote to add', 380 | value: repo_login 381 | }); 382 | if (!remote) { 383 | return; 384 | } 385 | await exec(`git remote add ${remote} ${clone_url}`, { cwd: folder.uri.fsPath }); 386 | } 387 | try { 388 | await exec(`git fetch ${remote} ${remoteBranch}`, { cwd: folder.uri.fsPath }); 389 | } catch (err) { 390 | console.error(err); 391 | // git fetch prints to stderr, continue 392 | } 393 | const localBranch = await window.showInputBox({ 394 | prompt: 'Name for the local branch to checkout', 395 | value: remoteBranch.startsWith(`${user_login}/`) ? remoteBranch : `${user_login}/${remoteBranch}` 396 | }); 397 | if (!localBranch) { 398 | return; 399 | } 400 | await exec(`git checkout -b ${localBranch} ${remote}/${remoteBranch}`, { cwd: folder.uri.fsPath }); 401 | } catch (err) { 402 | console.error(err); 403 | // git checkout prints to stderr, continue 404 | } 405 | } 406 | 407 | private async copyNumber(issue: Issue) { 408 | return env.clipboard.writeText(`#${issue.item.number}`); 409 | } 410 | 411 | private async copyText(issue: Issue) { 412 | return env.clipboard.writeText(issue.label as string); 413 | } 414 | 415 | private async copyMarkdown(issue: Issue) { 416 | return env.clipboard.writeText(`[#${issue.item.number}](${issue.item.html_url})`); 417 | } 418 | 419 | private async copyUrl(issue: Issue) { 420 | return env.clipboard.writeText(issue.item.html_url); 421 | } 422 | 423 | private async getGitHubRemotes() { 424 | const remotes: Record = {}; 425 | for (const folder of workspace.workspaceFolders || []) { 426 | try { 427 | const { stdout } = await exec('git remote -v', { cwd: folder.uri.fsPath }); 428 | for (const url of new Set(allMatches(/^[^\s]+\s+([^\s]+)/gm, stdout, 1))) { 429 | const m = new RegExp(`[^\\s]*${this.host.replace(/\./g, '\\.')}[/:]([^/]+)\/([^ ]+)[^\\s]*`).exec(url); 430 | 431 | if (m) { 432 | const [url, owner, rawRepo] = m; 433 | const repo = rawRepo.replace(/\.git$/, ''); 434 | let remote = remotes[`${owner}/${repo}`]; 435 | if (!remote) { 436 | const data = await fill(url); 437 | remote = { url, owner, repo, username: data && data.username, password: data && data.password, folders: [] }; 438 | remotes[`${owner}/${repo}`] = remote; 439 | } 440 | remote.folders.push(folder); 441 | } 442 | } 443 | } catch (e) { 444 | // ignore 445 | } 446 | } 447 | for (const rawRepo of this.repositories) { 448 | const m = /^\s*([^/\s]+)\/([^/\s]+)\s*$/.exec(rawRepo); 449 | if (m) { 450 | const [, owner, repo] = m; 451 | let remote = remotes[`${owner}/${repo}`]; 452 | if (!remote) { 453 | const url = `https://${this.host}/${owner}/${repo}.git`; 454 | const data = await fill(url); 455 | remote = { url, owner, repo, username: data && data.username, password: data && data.password, folders: [] }; 456 | remotes[`${owner}/${repo}`] = remote; 457 | } 458 | } 459 | } 460 | return Object.keys(remotes) 461 | .map(key => remotes[key]); 462 | } 463 | 464 | private getAPIOption() { 465 | if (this.host === 'github.com') { 466 | return { host: 'api.github.com' }; 467 | } else { 468 | return { host: this.host, pathPrefix: '/api/v3' }; 469 | } 470 | } 471 | } 472 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | import * as cp from 'child_process'; 2 | import * as GitHub from '@octokit/rest'; 3 | 4 | export interface ExecResult { 5 | error: Error | null; 6 | stdout: string; 7 | stderr: string; 8 | } 9 | 10 | export function exec(command: string, options: cp.ExecOptions) { 11 | return new Promise((resolve, reject) => { 12 | cp.exec(command, options, (error, stdout, stderr) => { 13 | (error || stderr ? reject : resolve)({ error, stdout, stderr }); 14 | }); 15 | }); 16 | } 17 | 18 | export function sleep(millis: number) { 19 | return new Promise(resolve => { 20 | setTimeout(resolve, millis); 21 | }); 22 | } 23 | 24 | export function allMatches(regex: RegExp, string: string, group: number) { 25 | return { 26 | [Symbol.iterator]: function* () { 27 | let m: RegExpExecArray | null; 28 | while (m = regex.exec(string)) { 29 | yield m[group]; 30 | if (regex.lastIndex === m.index) { 31 | regex.lastIndex++; 32 | } 33 | } 34 | } 35 | }; 36 | } 37 | 38 | export async function fetchAll(github: GitHub, first: Promise) { 39 | const all = []; 40 | 41 | let res = await first; 42 | all.push(...res.data.items); 43 | 44 | while (github.hasNextPage(res)) { 45 | res = await github.getNextPage(res); 46 | all.push(...res.data.items); 47 | } 48 | 49 | return all; 50 | } -------------------------------------------------------------------------------- /thirdparty/octicons/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2012-2016 GitHub, Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /thirdparty/octicons/README: -------------------------------------------------------------------------------- 1 | From https://github.com/primer/octicons/. -------------------------------------------------------------------------------- /thirdparty/octicons/dark/bug.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | bug 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /thirdparty/octicons/dark/git-pull-request.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | git-pull-request 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /thirdparty/octicons/dark/plus-small.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | plus-small 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /thirdparty/octicons/light/bug.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | bug 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /thirdparty/octicons/light/git-pull-request.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | git-pull-request 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /thirdparty/octicons/light/plus-small.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | plus-small 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /thirdpartynotices.txt: -------------------------------------------------------------------------------- 1 | THIRD-PARTY SOFTWARE NOTICES AND INFORMATION 2 | For Microsoft vscode-github-issues-prs 3 | 4 | This project incorporates material from the projects listed below. The original copyright notice 5 | and the license under which Microsoft received such material are set out below. Microsoft reserves 6 | all other rights not expressly granted, whether by implication, estoppel or otherwise. 7 | 8 | 9 | 1. copy-paste, version 1.3.0, https://github.com/xavi-/node-copy-paste 10 | 11 | Developed by: Xavi Ramirez 12 | License: This project is released under The MIT License. 13 | Links to: https://opensource.org/licenses/mit-license.php 14 | 15 | 16 | 2. git-credential-node, version 1.1.0, https://github.com/parro-it/git-credential-node 17 | 18 | Copyright (C) 2017 Andrea Parodi 19 | 20 | Permission is hereby granted, free of charge, to any person obtaining a copy 21 | of this software and associated documentation files (the "Software"), to deal 22 | in the Software without restriction, including without limitation the rights 23 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 24 | copies of the Software, and to permit persons to whom the Software is 25 | furnished to do so, subject to the following conditions: 26 | 27 | The above copyright notice and this permission notice shall be included in 28 | all copies or substantial portions of the Software. 29 | 30 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 31 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 32 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 33 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 34 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 35 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 36 | THE SOFTWARE. 37 | 38 | 39 | 3. github, version 9.2.0, https://github.com/mikedeboer/node-github 40 | 41 | The MIT License 42 | 43 | Copyright (c) 2012 Cloud9 IDE, Inc. (Mike de Boer) 44 | 45 | Permission is hereby granted, free of charge, to any person obtaining a copy 46 | of this software and associated documentation files (the "Software"), to deal 47 | in the Software without restriction, including without limitation the rights 48 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 49 | copies of the Software, and to permit persons to whom the Software is 50 | furnished to do so, subject to the following conditions: 51 | 52 | The above copyright notice and this permission notice shall be included in 53 | all copies or substantial portions of the Software. 54 | 55 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 56 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 57 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 58 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 59 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 60 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 61 | THE SOFTWARE. 62 | 63 | 64 | 4. octicons, version 5.0.1, https://github.com/primer/octicons 65 | 66 | MIT License 67 | 68 | Copyright (c) 2012-2016 GitHub, Inc. 69 | 70 | Permission is hereby granted, free of charge, to any person obtaining a copy 71 | of this software and associated documentation files (the "Software"), to deal 72 | in the Software without restriction, including without limitation the rights 73 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 74 | copies of the Software, and to permit persons to whom the Software is 75 | furnished to do so, subject to the following conditions: 76 | 77 | The above copyright notice and this permission notice shall be included in all 78 | copies or substantial portions of the Software. 79 | 80 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 81 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 82 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 83 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 84 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 85 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 86 | SOFTWARE. 87 | 88 | 89 | 5. node-open, version 0.0.5, https://github.com/pwnall/node-open 90 | 91 | Copyright (c) 2012 Jay Jordan 92 | 93 | Permission is hereby granted, free of charge, to any person 94 | obtaining a copy of this software and associated documentation 95 | files (the "Software"), to deal in the Software without 96 | restriction, including without limitation the rights to use, 97 | copy, modify, merge, publish, distribute, sublicense, and/or sell 98 | copies of the Software, and to permit persons to whom the 99 | Software is furnished to do so, subject to the following 100 | conditions: 101 | 102 | The above copyright notice and this permission notice shall be 103 | included in all copies or substantial portions of the Software. 104 | 105 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 106 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 107 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 108 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 109 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 110 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 111 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 112 | OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es6", 5 | "strict": true, 6 | "noUnusedLocals": true, 7 | "outDir": "out", 8 | "lib": [ 9 | "es6" 10 | ], 11 | "sourceMap": true, 12 | "rootDir": "." 13 | }, 14 | "exclude": [ 15 | "node_modules", 16 | ".vscode-test" 17 | ] 18 | } -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "no-unused-expression": true, 4 | "no-duplicate-variable": true, 5 | "curly": true, 6 | "class-name": true, 7 | "semicolon": true, 8 | "triple-equals": true 9 | } 10 | } --------------------------------------------------------------------------------