Compare commits
445 Commits
0.0.3
...
v3.5.0-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
23db83832c | ||
|
|
807b1f1ea3 | ||
|
|
6232b8087f | ||
|
|
b783bfdf10 | ||
|
|
b8322f19cd | ||
|
|
8623e661ca | ||
|
|
c3bd17edee | ||
|
|
b17640ba57 | ||
|
|
f6a18458df | ||
|
|
e1b1b737d9 | ||
|
|
19e523d6e4 | ||
|
|
ff1597d64f | ||
|
|
49cc681520 | ||
|
|
6d1a1ca346 | ||
|
|
49fa9b5078 | ||
|
|
ec6ca8188c | ||
|
|
034a01c7d1 | ||
|
|
15ebde445d | ||
|
|
f184feda2c | ||
|
|
d35074ecf8 | ||
|
|
4d62e5cdad | ||
|
|
ce5ff1eb47 | ||
|
|
3a38f6ae39 | ||
|
|
3e403a2d5c | ||
|
|
83fff1590d | ||
|
|
27425e3deb | ||
|
|
dbdb77c2c1 | ||
|
|
263bdc728a | ||
|
|
0ef1e509a6 | ||
|
|
9a35fc6b47 | ||
|
|
078f3fd482 | ||
|
|
33ef9687f2 | ||
|
|
361789f859 | ||
|
|
1acc183621 | ||
|
|
ee29596d45 | ||
|
|
90245b1111 | ||
|
|
a69c83f64f | ||
|
|
6f04514a27 | ||
|
|
6eccb2a8da | ||
|
|
d86b18bc7a | ||
|
|
3f5798357e | ||
|
|
23065e1177 | ||
|
|
dba7c2ea52 | ||
|
|
8f40ba8542 | ||
|
|
b477b34278 | ||
|
|
a8b0aaa3e3 | ||
|
|
071bfdb82e | ||
|
|
474741aeb4 | ||
|
|
187f7881ca | ||
|
|
14b9d93807 | ||
|
|
ba346da54b | ||
|
|
564856853a | ||
|
|
3e0ad47698 | ||
|
|
675050d923 | ||
|
|
6af753c0ae | ||
|
|
bb59f2899a | ||
|
|
cfafa8c020 | ||
|
|
f99ba89a4b | ||
|
|
8f2ec85c6b | ||
|
|
b58c48841f | ||
|
|
d3800a1ae8 | ||
|
|
4a379511d5 | ||
|
|
e5a94bb2ff | ||
|
|
224200e32a | ||
|
|
a285fb5505 | ||
|
|
cd5faa1336 | ||
|
|
c09deb54b1 | ||
|
|
bc747e9d5f | ||
|
|
ce0842f41c | ||
|
|
0cf107a817 | ||
|
|
5fc567372c | ||
|
|
0875cc12d5 | ||
|
|
04a6574f16 | ||
|
|
25696ffa94 | ||
|
|
feea0e62fc | ||
|
|
c5993fe145 | ||
|
|
67dfcb6226 | ||
|
|
7c0bc78574 | ||
|
|
8fb61e7ab5 | ||
|
|
b38f357bc2 | ||
|
|
6db6eff6b7 | ||
|
|
c05fcdc1bd | ||
|
|
d7b28e1a56 | ||
|
|
065300be06 | ||
|
|
9afa06c00d | ||
|
|
99aa4ee74b | ||
|
|
89474a2aa6 | ||
|
|
35694303c4 | ||
|
|
a5be6f0533 | ||
|
|
e5653d1c66 | ||
|
|
7643e338c5 | ||
|
|
0854e0bcfb | ||
|
|
2a9b274920 | ||
|
|
82bd510593 | ||
|
|
b334d5ff66 | ||
|
|
9fed706fd2 | ||
|
|
4707b0640d | ||
|
|
3aab904aaf | ||
|
|
0d19224a97 | ||
|
|
a7dc29a9aa | ||
|
|
7cb1b9d0f1 | ||
|
|
b70ffbdeee | ||
|
|
9292fc23a8 | ||
|
|
6a3428e6d2 | ||
|
|
4c4d21741e | ||
|
|
d59f4ef6dc | ||
|
|
0c13050387 | ||
|
|
3b195b6be2 | ||
|
|
b961646c95 | ||
|
|
d3af67b21b | ||
|
|
7ce5a396a1 | ||
|
|
c36bc2692d | ||
|
|
276c24ac24 | ||
|
|
aff8aff011 | ||
|
|
ab2bf893e1 | ||
|
|
d8564c215c | ||
|
|
e3c9fd53ca | ||
|
|
640a11b3cb | ||
|
|
9aadf73407 | ||
|
|
7b63d4f437 | ||
|
|
0686c882c8 | ||
|
|
0dda92cf8d | ||
|
|
dcbf70682e | ||
|
|
2a70e5b4cb | ||
|
|
9945ee6d94 | ||
|
|
19fe22f061 | ||
|
|
8b0748608d | ||
|
|
aa39792843 | ||
|
|
073353dcda | ||
|
|
296e562d9f | ||
|
|
d0b4c2fd5c | ||
|
|
8a0e27b7df | ||
|
|
a724244914 | ||
|
|
851522f593 | ||
|
|
ab417eadbe | ||
|
|
9071b55026 | ||
|
|
6d8a37a10f | ||
|
|
9d176db16a | ||
|
|
e7604ed863 | ||
|
|
e1190d50a4 | ||
|
|
5c2712bcb7 | ||
|
|
61f002420a | ||
|
|
f632829822 | ||
|
|
de8ce58e09 | ||
|
|
46ff70e969 | ||
|
|
758d331e69 | ||
|
|
ace0ac7018 | ||
|
|
81cfec1cac | ||
|
|
29ef718f64 | ||
|
|
4e67a84531 | ||
|
|
291c53cd19 | ||
|
|
e0b3dcd484 | ||
|
|
c500293032 | ||
|
|
e1349bb1c6 | ||
|
|
e33fd00119 | ||
|
|
3856cfd110 | ||
|
|
8b5eed4714 | ||
|
|
c10a79a7ee | ||
|
|
ee40dc6325 | ||
|
|
0aab16357c | ||
|
|
e859f697ec | ||
|
|
e906bfcb9e | ||
|
|
a90d3f98de | ||
|
|
964cf2d5dd | ||
|
|
ea2adcd919 | ||
|
|
0feaf285cd | ||
|
|
4d1cfd6413 | ||
|
|
4f84c03275 | ||
|
|
ba69a19eeb | ||
|
|
5a0a2f3b61 | ||
|
|
0a7a88c302 | ||
|
|
1fb2826f5c | ||
|
|
b9fb4bea56 | ||
|
|
9867e7065d | ||
|
|
43e4337358 | ||
|
|
671f1ca6c1 | ||
|
|
dd0762152f | ||
|
|
823e93080e | ||
|
|
33debb6bb2 | ||
|
|
0c348cdb0f | ||
|
|
2f0a25720a | ||
|
|
4e3ccd9581 | ||
|
|
97f88489a4 | ||
|
|
3e84ec88e2 | ||
|
|
a5d1d74d7b | ||
|
|
d53caa2137 | ||
|
|
49a8896571 | ||
|
|
7a51946eda | ||
|
|
ef74ae0950 | ||
|
|
14eebbba15 | ||
|
|
1c5e627f8e | ||
|
|
343d2f9be1 | ||
|
|
73bbbc1d5f | ||
|
|
164cb2bfe0 | ||
|
|
b51d25829b | ||
|
|
9a0ce83260 | ||
|
|
036f0639aa | ||
|
|
14604435db | ||
|
|
7c01f40b3c | ||
|
|
dad85b3c0a | ||
|
|
332b2c3b91 | ||
|
|
f6bde72baf | ||
|
|
6074a06fc9 | ||
|
|
937c56e554 | ||
|
|
b6ce03b806 | ||
|
|
4898c11eb5 | ||
|
|
9c2269b9f1 | ||
|
|
2ef6c37c89 | ||
|
|
564a0b8f64 | ||
|
|
b61e714110 | ||
|
|
a58d643ca1 | ||
|
|
3656d4e8a4 | ||
|
|
3e815f6bf8 | ||
|
|
487fb5197c | ||
|
|
89a2471736 | ||
|
|
b40ad7eced | ||
|
|
7aefd178c2 | ||
|
|
a2a3f1a81e | ||
|
|
f499bffbc6 | ||
|
|
3435ada188 | ||
|
|
1b21893d72 | ||
|
|
a37a80d704 | ||
|
|
e7fedb3c51 | ||
|
|
762fa545c7 | ||
|
|
df838e883a | ||
|
|
0480477136 | ||
|
|
88c09bec6d | ||
|
|
53da45fe4f | ||
|
|
edcb13aaaa | ||
|
|
eaea44872c | ||
|
|
1c29fa3f33 | ||
|
|
037446f38e | ||
|
|
c05fd0b976 | ||
|
|
f1ec0ec0b2 | ||
|
|
0958c152fe | ||
|
|
9f91f851c9 | ||
|
|
aeab246e0a | ||
|
|
4da21c3cc1 | ||
|
|
a3a4a5bc49 | ||
|
|
c5c1e24c62 | ||
|
|
bddf89bc35 | ||
|
|
1519898dfa | ||
|
|
d389a7b588 | ||
|
|
2e2462dd46 | ||
|
|
cf04a982e4 | ||
|
|
628f0c3cc1 | ||
|
|
d502694ba6 | ||
|
|
098d356652 | ||
|
|
6bff9a8f7b | ||
|
|
796730c22f | ||
|
|
5bdb5a1847 | ||
|
|
f837de7430 | ||
|
|
3caeb2ca9b | ||
|
|
5e19cc6adf | ||
|
|
74845f979b | ||
|
|
a98f400375 | ||
|
|
1a244c6296 | ||
|
|
dfd0f8c365 | ||
|
|
226e98dd04 | ||
|
|
3c8fe9efb8 | ||
|
|
41fd06e491 | ||
|
|
ccd0ad67a3 | ||
|
|
aff36efeca | ||
|
|
0f0a653c4c | ||
|
|
73c58bc923 | ||
|
|
5ffd4753bd | ||
|
|
05ff9804bc | ||
|
|
00e9660227 | ||
|
|
66d11c37e2 | ||
|
|
780423b195 | ||
|
|
a552332234 | ||
|
|
0a4cdd81eb | ||
|
|
bcfb0cd24d | ||
|
|
a2f4c4c953 | ||
|
|
237e464c44 | ||
|
|
7100c8f938 | ||
|
|
85826eb091 | ||
|
|
b530c3d6a2 | ||
|
|
b81f2f2fa0 | ||
|
|
add76fc5fc | ||
|
|
ecaf7cd45c | ||
|
|
7bdeae8d83 | ||
|
|
b637678d34 | ||
|
|
84bc19318d | ||
|
|
9da3d26fc6 | ||
|
|
a6d321590d | ||
|
|
de98e6bef4 | ||
|
|
f21a228ed5 | ||
|
|
7600a8e86e | ||
|
|
1263064150 | ||
|
|
cf8c07f49e | ||
|
|
4a88edd5ca | ||
|
|
fc09e3a700 | ||
|
|
dda00c75c9 | ||
|
|
1062188733 | ||
|
|
dc26d90ebc | ||
|
|
217e349ac1 | ||
|
|
9adb14aec8 | ||
|
|
5eb4a778e8 | ||
|
|
46039dbf24 | ||
|
|
d8d221a624 | ||
|
|
120f5ae6fc | ||
|
|
360c38e536 | ||
|
|
29c16b13bf | ||
|
|
66039e8dbe | ||
|
|
9c74224387 | ||
|
|
c4c8aa3bd3 | ||
|
|
28fced1b80 | ||
|
|
a4d55bcbcc | ||
|
|
ca5845018f | ||
|
|
0147645dc9 | ||
|
|
46e355e02e | ||
|
|
d9e638df01 | ||
|
|
1a47935719 | ||
|
|
9ac9a83fbf | ||
|
|
305674d71a | ||
|
|
afe8d38fdb | ||
|
|
cd581f5c56 | ||
|
|
af5b8b7e09 | ||
|
|
bf176301af | ||
|
|
51ef25f366 | ||
|
|
b17c43ba9a | ||
|
|
4d0c18f330 | ||
|
|
0d7633c78a | ||
|
|
8594a5dd38 | ||
|
|
beb6740120 | ||
|
|
6504b7822c | ||
|
|
25aa1fe3ca | ||
|
|
9b92c48759 | ||
|
|
46f325e9a0 | ||
|
|
48b33d47ca | ||
|
|
5f147f6262 | ||
|
|
d5d0c3a28d | ||
|
|
3ef538b713 | ||
|
|
310a547581 | ||
|
|
6641233284 | ||
|
|
aaa1b78d29 | ||
|
|
f66ba92e3c | ||
|
|
4e9e6000ec | ||
|
|
6d4703affb | ||
|
|
9a8045b6f2 | ||
|
|
b0fbbc718c | ||
|
|
e12ec7093c | ||
|
|
615f55574b | ||
|
|
8111190d84 | ||
|
|
80aa43a84b | ||
|
|
3389024a2a | ||
|
|
62c5094b06 | ||
|
|
e1cf928992 | ||
|
|
f53ecf8a7e | ||
|
|
30248a7f97 | ||
|
|
c259f94d3e | ||
|
|
9a394de681 | ||
|
|
a18eaaaee6 | ||
|
|
d181065d03 | ||
|
|
964cfd363e | ||
|
|
b0257ca040 | ||
|
|
9edf629c3b | ||
|
|
dfacc2bf6e | ||
|
|
589ec6cef3 | ||
|
|
50cf8861cd | ||
|
|
15c81ef36f | ||
|
|
7352a70662 | ||
|
|
79ad799511 | ||
|
|
2b907788dd | ||
|
|
25007f7b69 | ||
|
|
ea375f478f | ||
|
|
6c536e9360 | ||
|
|
ca7f39629d | ||
|
|
6fefab6d64 | ||
|
|
ff8b184bef | ||
|
|
d2ad2b6a0f | ||
|
|
79e5370a13 | ||
|
|
a6f5fa59e4 | ||
|
|
680d31d43d | ||
|
|
567533622c | ||
|
|
a52b0c9b73 | ||
|
|
4e65eb6344 | ||
|
|
72ef5e2902 | ||
|
|
1a208b8691 | ||
|
|
39aa6180ca | ||
|
|
a8ab3a5af4 | ||
|
|
615485cd21 | ||
|
|
a91afffbb2 | ||
|
|
5f0acc2693 | ||
|
|
d3ffabd76b | ||
|
|
5cb0053a05 | ||
|
|
968eb6e7eb | ||
|
|
438dad437f | ||
|
|
60b464ca22 | ||
|
|
354b823d7d | ||
|
|
e9db04f0c7 | ||
|
|
e31a1a3473 | ||
|
|
46e2378779 | ||
|
|
fee9562dd1 | ||
|
|
9c0a38958e | ||
|
|
a3895d27ab | ||
|
|
638a6dc838 | ||
|
|
7ace9cb152 | ||
|
|
f4410be30a | ||
|
|
562afeaaad | ||
|
|
409be335f9 | ||
|
|
8df6b80725 | ||
|
|
d2d72f0d54 | ||
|
|
23b2c679a9 | ||
|
|
157928311e | ||
|
|
b047fbc394 | ||
|
|
7a4dcae8c7 | ||
|
|
a734ffe9ed | ||
|
|
834b4904db | ||
|
|
c4b8637946 | ||
|
|
20df7732be | ||
|
|
30b1fba8d4 | ||
|
|
7b4dd77fbc | ||
|
|
14e9c5b4fa | ||
|
|
69c4d44b49 | ||
|
|
7ebdaa0775 | ||
|
|
ff01054f90 | ||
|
|
c69a160ea5 | ||
|
|
05865d014e | ||
|
|
cb81a823e6 | ||
|
|
7a604191a7 | ||
|
|
b617a90c5b | ||
|
|
2be76ed8a8 | ||
|
|
dcb789f58d | ||
|
|
f34686de36 | ||
|
|
0ee09d9d87 | ||
|
|
7fa0b9d01b | ||
|
|
fba6def3e4 | ||
|
|
dfd17a8f17 | ||
|
|
d70a47201c | ||
|
|
e0cf335811 | ||
|
|
87215850b7 | ||
|
|
a2eee23d7f | ||
|
|
19c0e46729 | ||
|
|
26ce5f7d53 | ||
|
|
67e1a6b78f | ||
|
|
fbcd0a9cd6 | ||
|
|
e929db0106 | ||
|
|
b7920f3c3d | ||
|
|
d04696ac1d | ||
|
|
ebb1085562 | ||
|
|
47ce5c5132 | ||
|
|
f08339335d | ||
|
|
f4d3d1718d |
8
.vscode/launch.json
vendored
@@ -10,8 +10,8 @@
|
|||||||
"args": ["--extensionDevelopmentPath=${workspaceRoot}" ],
|
"args": ["--extensionDevelopmentPath=${workspaceRoot}" ],
|
||||||
"stopOnEntry": false,
|
"stopOnEntry": false,
|
||||||
"sourceMaps": true,
|
"sourceMaps": true,
|
||||||
"outDir": "${workspaceRoot}/out/src",
|
"outFiles": ["${workspaceRoot}/out/src/**/*.js"],
|
||||||
"preLaunchTask": "npm"
|
"preLaunchTask": "compile"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Launch Tests",
|
"name": "Launch Tests",
|
||||||
@@ -21,8 +21,8 @@
|
|||||||
"args": ["--extensionDevelopmentPath=${workspaceRoot}", "--extensionTestsPath=${workspaceRoot}/out/test" ],
|
"args": ["--extensionDevelopmentPath=${workspaceRoot}", "--extensionTestsPath=${workspaceRoot}/out/test" ],
|
||||||
"stopOnEntry": false,
|
"stopOnEntry": false,
|
||||||
"sourceMaps": true,
|
"sourceMaps": true,
|
||||||
"outDir": "${workspaceRoot}/out/test",
|
"outFiles": ["${workspaceRoot}/out/test/**/*.js"],
|
||||||
"preLaunchTask": "npm"
|
"preLaunchTask": "compile"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
12
.vscode/settings.json
vendored
@@ -1,12 +1,14 @@
|
|||||||
// Place your settings in this file to overwrite default and user settings.
|
|
||||||
{
|
{
|
||||||
|
"editor.insertSpaces": true,
|
||||||
"files.exclude": {
|
"files.exclude": {
|
||||||
"out": false, // set this to true to hide the "out" folder with the compiled JS files
|
"node_modules": true,
|
||||||
"node_modules": false
|
"out": true
|
||||||
},
|
},
|
||||||
|
"files.trimTrailingWhitespace": true,
|
||||||
"search.exclude": {
|
"search.exclude": {
|
||||||
"out": true, // set this to false to include "out" folder in search results
|
"node_modules": true,
|
||||||
"node_modules": true
|
"out": true
|
||||||
},
|
},
|
||||||
|
"javascript.format.insertSpaceAfterFunctionKeywordForAnonymousFunctions": false,
|
||||||
"typescript.tsdk": "./node_modules/typescript/lib" // we want to use the TS server from our node_modules folder to control its version
|
"typescript.tsdk": "./node_modules/typescript/lib" // we want to use the TS server from our node_modules folder to control its version
|
||||||
}
|
}
|
||||||
43
.vscode/tasks.json
vendored
@@ -8,23 +8,28 @@
|
|||||||
|
|
||||||
// A task runner that calls a custom npm script that compiles the extension.
|
// A task runner that calls a custom npm script that compiles the extension.
|
||||||
{
|
{
|
||||||
"version": "0.1.0",
|
"version": "2.0.0",
|
||||||
|
"showOutput": "always",
|
||||||
// we want to run npm
|
"tasks": [
|
||||||
"command": "npm",
|
{
|
||||||
|
"taskName": "compile",
|
||||||
// the command is a shell script
|
"command": "npm run compile",
|
||||||
"isShellCommand": true,
|
"isBuildCommand": true,
|
||||||
|
"isShellCommand": true,
|
||||||
// show the output window only if unrecognized errors occur.
|
"problemMatcher": [ "$tsc", "$tslint5" ]
|
||||||
"showOutput": "silent",
|
},
|
||||||
|
{
|
||||||
// we run the custom script "compile" as defined in package.json
|
"taskName": "lint",
|
||||||
"args": ["run", "compile", "--loglevel", "silent"],
|
"command": "npm run lint",
|
||||||
|
"isShellCommand": true,
|
||||||
// The tsc compiler is started in watching mode
|
"problemMatcher": "$tslint5"
|
||||||
"isWatching": true,
|
},
|
||||||
|
{
|
||||||
// use the standard tsc in watch mode problem matcher to find compile problems in the output.
|
"taskName": "watch",
|
||||||
"problemMatcher": "$tsc-watch"
|
"command": "npm run watch",
|
||||||
|
"isBackground": true,
|
||||||
|
"isShellCommand": true,
|
||||||
|
"problemMatcher": "$tsc-watch"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
@@ -1,9 +1,11 @@
|
|||||||
|
!images/*.svg
|
||||||
|
images/**
|
||||||
.vscode/**
|
.vscode/**
|
||||||
typings/**
|
.vscode-test/**
|
||||||
out/test/**
|
out/test/**
|
||||||
test/**
|
test/**
|
||||||
src/**
|
src/**
|
||||||
**/*.map
|
**/*.map
|
||||||
.gitignore
|
.gitignore
|
||||||
tsconfig.json
|
tsconfig.json
|
||||||
vsc-extension-quickstart.md
|
tslint.json
|
||||||
685
CHANGELOG.md
Normal file
@@ -0,0 +1,685 @@
|
|||||||
|
# Change Log
|
||||||
|
|
||||||
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
|
The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/).
|
||||||
|
|
||||||
|
## [3.5.0-beta] - 2017-05-23
|
||||||
|
### Added
|
||||||
|
- Improves performance
|
||||||
|
- Reduces the number of git calls on known "untrackables"
|
||||||
|
- Caches many more git commands to reduce git command roundtrips and parsing
|
||||||
|
- Increases the debounce (delay) on cursor movement to reduce lag when navigating around a file
|
||||||
|
- Adds diff information (previous line's code) into the active line hover when the current line is uncommitted
|
||||||
|
- Adds `gitlens.statusBar.alignment` settings to control the alignment of the status bar -- thanks to Zack Schuster (@zackschuster)!
|
||||||
|
- Adds `Open Branch in Remote` command (`gitlens.openBranchInRemote`) - opens the current branch commits in the supported remote service
|
||||||
|
- Adds `Open Repository in Remote` command (`gitlens.openRepoInRemote`) - opens the repository in the supported remote service
|
||||||
|
- Adds support to the `Search commits` command (`gitlens.showCommitSearch`) to work without any active editor
|
||||||
|
- Adds commit search pre-population -- if there is an active editor it will use the commit sha of the current line commit, otherwise it will use the current clipboard
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Changes the active line hover to only show at the beginning and end of a line if `gitlens.blame.annotation.activeLine` is `both`
|
||||||
|
- Changes `alt+f` shortcut to `alt+/` for the `Search commits` command (`gitlens.showCommitSearch`)
|
||||||
|
- Changes `alt+right` on commit details quick pick menu to execute the `Compare File with Previous Commit` command (`gitlens.diffWithPrevious`) when a file is selected
|
||||||
|
- Changes `alt+right` on repository status quick pick menu to execute the `Compare File with Previous Commit` command (`gitlens.diffWithPrevious`) when a file is selected
|
||||||
|
- Refactors command argument passing to allow for future inclusion into the SCM menus
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Fixes [#73](https://github.com/eamodio/vscode-gitlens/issues/73) - GitLens doesn't work with Chinese filenames
|
||||||
|
- Fixes incorrect file selection from the commit details quick pick menu
|
||||||
|
- Fixes incorrect command execution when using `"gitlens.statusBar.command": "gitlens.showQuickRepoHistory"`
|
||||||
|
- Fixes a bunch of issues that were revealed by enabling Typescript `strict` mode
|
||||||
|
|
||||||
|
## [3.4.9] - 2017-05-03
|
||||||
|
### Added
|
||||||
|
- Adds better support for deleted files when choosing `Open Changed Files` via in quick pick menus - now opens the file revision from the previous commit
|
||||||
|
- Adds better support for deleted files when using `alt+right arrow` shortcut on the commit details quick pick menu - now opens the file revision from the previous commit
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Removes deleted files when choosing `Open Working Changed Files` via in quick pick menus
|
||||||
|
|
||||||
|
## [3.4.8] - 2017-05-02
|
||||||
|
### Changed
|
||||||
|
- Changes display name in the marketplace to `Git Lens` because of the marketplace search ranking algorithm
|
||||||
|
|
||||||
|
## [3.4.6] - 2017-05-01
|
||||||
|
### Added
|
||||||
|
- Adds better support for deleted files when choosing `Open File` via in quick pick menus - now opens the file revision from the previous commit
|
||||||
|
- Adds better support for deleted files when choosing `Open File in Remote` via in quick pick menus - now opens the file revision from the previous commit
|
||||||
|
- Improves performance by caching the git path to avoid lookups on every git command
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Renames `gitlens.advanced.codeLens.debug` setting to `gitlens.codeLens.debug`
|
||||||
|
- Renames `gitlens.advanced.debug` setting to `gitlens.debug`
|
||||||
|
- Renames `gitlens.output.level` setting to `gitlens.outputLevel`
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Fixes incorrect file selection when showing commit details quick pick menu
|
||||||
|
- Fixes timing error on startup
|
||||||
|
|
||||||
|
## [3.4.5] - 2017-04-13
|
||||||
|
### Added
|
||||||
|
- Completely overhauls the [GitLens documentation](https://github.com/eamodio/vscode-gitlens/blob/master/README.md) and messaging -- make sure to check it out to see all the powerful features GitLen provides!
|
||||||
|
- Adds `gitlens.blame.annotation.activeLineDarkColor` & `gitlens.blame.annotation.activeLineLightColor` settings to control the colors of the active line blame annotation
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Changes `Toggle Git Code Lens` command to work when `gitlens.codeLens.visibility` is set to `auto` (the default)
|
||||||
|
- Renames `Compare with...` command to `Compare File with...`
|
||||||
|
- Renames `Compare with Next Commit` command to `Compare File with Next Commit`
|
||||||
|
- Renames `Compare with Previous Commit` command to `Compare File with Previous Commit`
|
||||||
|
- Renames `Compare with Previous Commit` command to `Compare File with Previous Commit`
|
||||||
|
- Renames `Compare with Working Tree` command to `Compare File with Working Tree`
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Fixes issue with `Open Commit in Remote` not working
|
||||||
|
- Fixes issue with many commands missing from the `Command Palette`
|
||||||
|
|
||||||
|
## [3.3.3] - 2017-04-10
|
||||||
|
### Fixed
|
||||||
|
- Fixes issue with newlines in commit messages in the file/branch/stash history quick pick menus (truncates and adds an ellipse icon)
|
||||||
|
|
||||||
|
## [3.3.2] - 2017-04-10
|
||||||
|
### Removed
|
||||||
|
- Removes `gitlens.blame.annotation.characters.*` settings since they were added to deal with unicode bugs in a previous version of vscode
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Closes [#63](https://github.com/eamodio/vscode-gitlens/issues/63) - Switch commit message and author in commit pick list. Also reduces clutter in the commit quick pick menus
|
||||||
|
|
||||||
|
## [3.3.1] - 2017-04-09
|
||||||
|
### Changed
|
||||||
|
- Changes commit search prefixes -- no prefix for message search, `@` for author, `:` for file pattern, `#` for commit id
|
||||||
|
- Changes `sha` terminology to `commit id` in the UI
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Fixes issues with author searching
|
||||||
|
|
||||||
|
## [3.3.0] - 2017-04-09
|
||||||
|
### Added
|
||||||
|
- Adds `Search commits` command (`gitlens.showCommitSearch`) to allow commit searching by message, author, file pattern, or sha
|
||||||
|
- Adds `alt+f` shortcut for the `Search commits` command (`gitlens.showCommitSearch`)
|
||||||
|
- Adds `Show Commit Search` command to the branch history quick pick menu
|
||||||
|
- Adds `Show Stashed Changes` command to the repository status quick pick menu
|
||||||
|
- Adds a `Don't Show Again` option to the GitLen update notification
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Changes `Open x in Remote` commands to be no longer hidden behind the `gitlens.insiders` setting
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Fixes [#59](https://github.com/eamodio/vscode-gitlens/issues/59) - Context menu shows gitlens commands even if folder/file is not under git
|
||||||
|
|
||||||
|
## [3.2.1]
|
||||||
|
### Fixed
|
||||||
|
- Fixes [#57](https://github.com/eamodio/vscode-gitlens/issues/57) - No more blank message if `diff.tool` is missing
|
||||||
|
|
||||||
|
## [3.2.0]
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Adds support for single files opened in vscode -- you are no longer required to open a folder for GitLens to work
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Fixes [#57](https://github.com/eamodio/vscode-gitlens/issues/57) - Warn on directory compare when there is no diff tool configured
|
||||||
|
- Fixes [#58](https://github.com/eamodio/vscode-gitlens/issues/58) - Work with git sub-modules
|
||||||
|
- Fixes issue with `Open * in Remote` commands with nested repositories and non-git workspace root folder
|
||||||
|
|
||||||
|
## [3.1.0]
|
||||||
|
### Added
|
||||||
|
- Adds `Show Stashed Changes` command (`gitlens.showQuickStashList`) to open a quick pick menu of all the stashed changes
|
||||||
|
- Adds insiders `Stash Changes` option to stashed changes quick pick menu -- enabled via `"gitlens.insiders": true`
|
||||||
|
- Adds insiders `Stash Unstaged Changes` option to stashed changes quick pick menu
|
||||||
|
- Adds insiders `Apply Stashed Changes` command (`gitlens.stashApply`) to apply the selected stashed changes to the working tree
|
||||||
|
- Adds insiders `Stash Changes` command (`gitlens.stashSave`) to stash any working tree changes
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Fixes incorrect counts in upstream status
|
||||||
|
|
||||||
|
## [3.0.5]
|
||||||
|
### Added
|
||||||
|
- Adds additional insiders support for GitLab, Bitbucket, and Visual Studio Team Services to the `Open x in Remote` commands and quick pick menus -- enabled via `"gitlens.insiders": true`
|
||||||
|
- Adds insiders line support to `Open File in Remote` command (`gitlens.openFileInRemote`)
|
||||||
|
- Adds original file name for renamed files to the repository status and commit details quick pick menu
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Fixes [#56](https://github.com/eamodio/vscode-gitlens/issues/56) - Handle file names with spaces
|
||||||
|
|
||||||
|
## [3.0.4]
|
||||||
|
### Changed
|
||||||
|
- Changes telemetry a bit to reduce noise
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Fixes common telemetry error by switching to non-strict iso dates (since they are only available in later git versions)
|
||||||
|
|
||||||
|
## [3.0.3]
|
||||||
|
### Added
|
||||||
|
- Adds a fallback to work with Git version prior to `2.11.0` -- terribly sorry for the inconvenience :(
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Fixes [#55](https://github.com/eamodio/vscode-gitlens/issues/55) - reverts Git requirement back to `2.2.0`
|
||||||
|
- Fixes issues with parsing merge commits
|
||||||
|
|
||||||
|
## [3.0.2]
|
||||||
|
### Changed
|
||||||
|
- Changes required Git version to `2.11.0`
|
||||||
|
|
||||||
|
## [3.0.1]
|
||||||
|
### Added
|
||||||
|
- Adds basic telemetry -- honors the vscode telemetry configuration setting
|
||||||
|
|
||||||
|
## [3.0.0]
|
||||||
|
### Added
|
||||||
|
- Adds insiders support for `Open in GitHub` to the relevant quick pick menus -- enabled via `"gitlens.insiders": true`
|
||||||
|
- Adds insiders `Open Line Commit in Remote` command (`gitlens.openCommitInRemote`) to open the current commit in the remote service (currently only GitHub)
|
||||||
|
- Adds insiders `Open File in Remote` command (`gitlens.openFileInRemote`) to open the current file in the remote service (currently only GitHub)
|
||||||
|
- Adds an update notification for feature releases
|
||||||
|
- Adds `Show Branch History` command (`gitlens.showQuickBranchHistory`) to show the history of the selected branch
|
||||||
|
- Adds `Show Last Opened Quick Pick` command (`gitlens.showLastQuickPick`) to re-open the previously opened quick pick menu - helps to get back to previous context
|
||||||
|
- Adds `alt+-` shortcut for the `Show Last Opened Quick Pick` command (`gitlens.showLastQuickPick`)
|
||||||
|
- Adds upstream status information (if available) to the repository status pick pick
|
||||||
|
- Adds file status rollup information to the repository status pick pick
|
||||||
|
- Adds file status rollup information to the commit details quick pick menu
|
||||||
|
- Adds `Compare with...` (`gitlens.diffWithBranch`) command to compare working file to another branch (via branch quick pick menu)
|
||||||
|
- Adds branch quick pick menu to `Directory Compare` (`gitlens.diffDirectory`) command
|
||||||
|
- Adds support for `gitlens.showQuickFileHistory` command execution via code lens to limit results to the code lens block
|
||||||
|
- Adds current branch to branch quick pick menu placeholder
|
||||||
|
- Adds `Show Branch History` command to the branch history quick pick menu when showing only limited commits (e.g. starting at a specified commit)
|
||||||
|
- Adds `Show File History` command to the file history quick pick menu when showing only limited commits (e.g. starting at a specified commit)
|
||||||
|
- Adds `Don't Show Again` option to the unsupported git version notification
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Changes `Show Repository History` command to `Show Current Branch History`
|
||||||
|
- Changes `Repository History` terminology to `Branch History`
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Fixes issue with `gitlens.diffWithPrevious` command execution via code lens when the code lens was not at the document/file level
|
||||||
|
- Fixes issue where full shas were displayed on the file/blame history explorers
|
||||||
|
- Fixes [#30](https://github.com/eamodio/vscode-gitlens/issues/30) - Diff with Working Tree fails from repo/commit quickpick list if file was renamed (and the commit was before the rename)
|
||||||
|
- Fixes various other quick pick menu command issues when a file was renamed
|
||||||
|
- Fixes various issues when caching is disabled
|
||||||
|
- Fixes issues with parsing commits history
|
||||||
|
- Fixes various issues with merge commits
|
||||||
|
|
||||||
|
## [2.12.2]
|
||||||
|
### Fixed
|
||||||
|
- Fixes [#50](https://github.com/eamodio/vscode-gitlens/issues/50) - excludes container-level code lens from `html` and `vue` language files
|
||||||
|
|
||||||
|
## [2.12.1]
|
||||||
|
### Added
|
||||||
|
- Adds `gitlens.advanced.codeLens.debug` setting to control whether or not to show debug information in code lens
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Fixes issue where `gitlens.showQuickRepoHistory` command fails to open when there is no active editor
|
||||||
|
|
||||||
|
## [2.12.0]
|
||||||
|
### Added
|
||||||
|
- Adds progress indicator for the `gitlens.showQuickFileHistory` & `gitlens.showQuickRepoHistory` quick pick menus
|
||||||
|
- Adds paging support to the `gitlens.showQuickFileHistory` & `gitlens.showQuickRepoHistory` quick pick menus
|
||||||
|
- Adds `Show Previous Commits` command
|
||||||
|
- Adds `Show Next Commits` command
|
||||||
|
- Adds keyboard page navigation via `alt+,` (previous) & `alt+.` (next) on the `gitlens.showQuickFileHistory` & `gitlens.showQuickRepoHistory` quick pick menus
|
||||||
|
- Adds keyboard commit navigation via `alt+,` (previous) & `alt+.` (next) on the `gitlens.showQuickCommitDetails` & `gitlens.showQuickCommitFileDetails` quick pick menus
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Changes behavior of `gitlens.showQuickFileHistory` & `gitlens.showFileHistory` to no longer show merge commits
|
||||||
|
- Changes `gitlens.copyShaToClipboard` to copy the full sha, rather than short sha
|
||||||
|
- Changes internal tracking to use full sha (rather than short sha)
|
||||||
|
|
||||||
|
## [2.11.2]
|
||||||
|
### Added
|
||||||
|
- Adds `gitlens.diffWithNext` command to open a diff with the next commit
|
||||||
|
- Adds `alt+.` shortcut for the `gitlens.diffWithNext` command
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Changes `shift+alt+p` shortcut to `alt+,` for the `gitlens.diffWithPrevious` command
|
||||||
|
- Changes `alt+p` shortcut to `shift+alt+,` for the `gitlens.diffLineWithPrevious` command
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
- Removes `gitlens.toggleCodeLens` from Command Palette when not available
|
||||||
|
- Removes `gitlens.toggleCodeLens` shortcut key when not available
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Fixes (#45)[https://github.com/eamodio/vscode-gitlens/issues/45] - Keyboard Shortcut collision with Project Manager
|
||||||
|
|
||||||
|
## [2.11.1]
|
||||||
|
### Added
|
||||||
|
- Adds blame and active line annotation support to git diff split view (right side)
|
||||||
|
- Adds command (compare, copy sha/message, etc) support to git diff split view (right side)
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Fixes intermittent issues when toggling whitespace for blame annotations
|
||||||
|
|
||||||
|
## [2.11.0]
|
||||||
|
### Added
|
||||||
|
- Adds `gitlens.showQuickCommitFileDetails` command to show a quick pick menu of details for a file commit
|
||||||
|
- Adds `gitlens.showQuickCommitFileDetails` command to code lens
|
||||||
|
- Adds `gitlens.showQuickCommitFileDetails` command to the status bar
|
||||||
|
- Adds `gitlens.closeUnchangedFiles` command to close any editors that don't have uncommitted changes
|
||||||
|
- Adds `gitlens.openChangedFiles` command to open all files that have uncommitted changes
|
||||||
|
- Adds `Directory Compare` (`gitlens.diffDirectory`) command to open the configured git difftool to compare directory versions
|
||||||
|
- Adds `Directory Compare with Previous Commit` command on the `gitlens.showQuickCommitDetails` quick pick menu
|
||||||
|
- Adds `Directory Compare with Working Tree` command on the `gitlens.showQuickCommitDetails` quick pick menu
|
||||||
|
- Adds a `Changed Files` grouping on the `gitlens.showQuickCommitDetails` quick pick menu
|
||||||
|
- Adds a `Close Unchanged Files` command on the `gitlens.showQuickRepoStatus` quick pick menu
|
||||||
|
- Adds a contextual description to the `go back` command in quick pick menus
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Changes layout of the `gitlens.showQuickRepoStatus` quick pick menu for better clarity
|
||||||
|
- Changes behavior of `gitlens.showQuickCommitDetails` to show commit a quick pick menu of details for a commit
|
||||||
|
- Changes default of `gitlens.codeLens.recentChange.command` to be `gitlens.showQuickCommitFileDetails` (though there is no visible behavior change)
|
||||||
|
- Renames `Open Files` to `Open Changed Files` on the `gitlens.showQuickCommitDetails` quick pick menu
|
||||||
|
- Renames `Open Working Files` to `Open Changed Working Files` on the `gitlens.showQuickCommitDetails` quick pick menu
|
||||||
|
- Renames `Show Changed Files` to `Show Commit Details` on the `gitlens.showQuickCommitFileDetails` quick pick menu
|
||||||
|
- Renames `Open Files` to `Open Changed Files` on the `gitlens.showQuickRepoStatus` quick pick menu
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Fixes [#44](https://github.com/eamodio/vscode-gitlens/issues/43) by adding a warning message about Git version requirements
|
||||||
|
- Fixes intermittent errors when adding active line annotations
|
||||||
|
- Fixes intermittent errors when opening multiple files via quick pick menus
|
||||||
|
|
||||||
|
## [2.10.1]
|
||||||
|
### Fixed
|
||||||
|
- Fixes [#43](https://github.com/eamodio/vscode-gitlens/issues/43) - File-level code lens isn't using the blame of the whole file as it should
|
||||||
|
- Fixes issue with single quotes (') in annotations
|
||||||
|
- Fixes output channel logging (also adds more debug information to code lens -- when enabled)
|
||||||
|
|
||||||
|
## [2.10.0]
|
||||||
|
### Added
|
||||||
|
- Adds blame and active line annotation support to git diff split view
|
||||||
|
- Adds command (compare, copy sha/message, etc) support to git diff split view
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Fixes startup failure if caching was disabled
|
||||||
|
- Fixes missing `Compare Line with Previous Commit` context menu item
|
||||||
|
- Fixes [#41](https://github.com/eamodio/vscode-gitlens/issues/41) - Toggle Blame annotations on compare files page
|
||||||
|
- Fixes issue with undo (to a saved state) not causing annotations to reappear properly
|
||||||
|
- Attempts to fix [#42](https://github.com/eamodio/vscode-gitlens/issues/42) - Cursor on Uncommitted message
|
||||||
|
|
||||||
|
## [2.9.0]
|
||||||
|
### Changed
|
||||||
|
- To accomodate the realization that blame information is invalid when a file has unsaved changes, the following behavior changes have been made
|
||||||
|
- Status bar blame information will hide
|
||||||
|
- Code lens change to a `Cannot determine...` message and become unclickable
|
||||||
|
- Many menu choices and commands will hide
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Fixes [#38](https://github.com/eamodio/vscode-gitlens/issues/38) - Toggle Blame Annotation button shows even when it isn't valid
|
||||||
|
- Fixes [#36](https://github.com/eamodio/vscode-gitlens/issues/36) - Blame information is invalid when a file has unsaved changes
|
||||||
|
|
||||||
|
## [2.8.2]
|
||||||
|
### Added
|
||||||
|
- Adds `gitlens.blame.annotation.dateFormat` to specify how absolute commit dates will be shown in the blame annotations
|
||||||
|
- Adds `gitlens.statusBar.date` to specify whether and how the commit date will be shown in the blame status bar
|
||||||
|
- Adds `gitlens.statusBar.dateFormat` to specify how absolute commit dates will be shown in the blame status bar
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Fixes [#39](https://github.com/eamodio/vscode-gitlens/issues/39) - Add date format options for status bar blame
|
||||||
|
|
||||||
|
## [2.8.1]
|
||||||
|
### Fixed
|
||||||
|
- Fixes issue where `Compare with *` commands fail to open when there is no active editor
|
||||||
|
|
||||||
|
## [2.8.0]
|
||||||
|
### Added
|
||||||
|
- Adds new `Open File` command on the `gitlens.showQuickCommitDetails` quick pick menu to open the commit version of the file
|
||||||
|
- Adds new `Open File` command on the `gitlens.showQuickCommitDetails` quick pick menu to open the commit version of the files
|
||||||
|
- Adds `alt+left` keyboard shortcut in quick pick menus to `go back`
|
||||||
|
- Adds `alt+right` keyboard shortcut in quick pick menus to execute the currently selected item while keeping the quick pick menu open (in most cases)
|
||||||
|
- `alt+right` keyboard shortcut on commit details file name, will open the commit version of the file
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Indents the file statuses on the `gitlens.showQuickCommitDetails` quick pick menu
|
||||||
|
- Renames `Open File` to `Open Working File` on the `gitlens.showQuickCommitDetails` quick pick menu
|
||||||
|
- Renames `Open File` and `Open Working Files` on the `gitlens.showQuickCommitDetails` quick pick menu
|
||||||
|
- Reorders some quick pick menus
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Fixes [#34](https://github.com/eamodio/vscode-gitlens/issues/34) - Open file should open the selected version of the file
|
||||||
|
- Fixes some issue where some editors opened by the quickpick would not be opened in preview tabs
|
||||||
|
- Fixes issue where copy to clipboard commands would fail if there was no active editor
|
||||||
|
- Fixes issue where active line annotations would show for opened versioned files
|
||||||
|
- Fixes issue where code lens compare commands on opened versioned files would fail
|
||||||
|
|
||||||
|
## [2.7.1]
|
||||||
|
### Added
|
||||||
|
- Adds proper support for multi-line commit messages
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Fixes [#33](https://github.com/eamodio/vscode-gitlens/issues/33) - Commit message styled as title in popup, when message starts with hash symbol
|
||||||
|
|
||||||
|
## [2.7.0]
|
||||||
|
### Added
|
||||||
|
- Adds file status icons (added, modified, deleted, etc) to the `gitlens.showQuickCommitDetails` quick pick menu
|
||||||
|
- Adds `Copy Commit Sha to Clipboard` command to commit files quick pick menu
|
||||||
|
- Adds `Copy Commit Message to Clipboard` command to commit files quick pick menu
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Changes `Show Commit History` to `Show File History` on the `gitlens.showQuickCommitDetails` quick pick menu
|
||||||
|
- Changes `Show Previous Commit History` to `Show Previous File History` on the `gitlens.showQuickCommitDetails` quick pick menu
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Fixes issue with repository status when there are no changes
|
||||||
|
- Fixes issue with `.` showing in the path of quick pick menus
|
||||||
|
- Fixes logging to clean up on extension deactivate
|
||||||
|
|
||||||
|
## [2.6.0]
|
||||||
|
### Added
|
||||||
|
- Adds `gitlens.showQuickRepoStatus` command to show a quick pick menu of files changed including status icons (added, modified, deleted, etc)
|
||||||
|
- Adds `alt+s` shortcut for the `gitlens.showQuickRepoStatus` command
|
||||||
|
|
||||||
|
## [2.5.6]
|
||||||
|
### Fixed
|
||||||
|
- Fixes [#32](https://github.com/eamodio/vscode-gitlens/issues/32) - 00000000 Uncommitted changes distracting
|
||||||
|
|
||||||
|
## [2.5.5]
|
||||||
|
### Fixed
|
||||||
|
- Fixes [#25](https://github.com/eamodio/vscode-gitlens/issues/25) - Blame information isn't updated after git operations (commit, reset, etc)
|
||||||
|
|
||||||
|
## [2.5.4]
|
||||||
|
### Fixed
|
||||||
|
- Fixes extra spacing in annotations
|
||||||
|
|
||||||
|
## [2.5.3]
|
||||||
|
### Fixed
|
||||||
|
- Fixes [#27](https://github.com/eamodio/vscode-gitlens/issues/27) - Annotations are broken in vscode insider build
|
||||||
|
|
||||||
|
## [2.5.2]
|
||||||
|
### Added
|
||||||
|
- Adds `Open File` command to `gitlens.showQuickCommitDetails` quick pick menu
|
||||||
|
- Adds `Open Files` command to `gitlens.showQuickCommitDetails` quick pick menu
|
||||||
|
- Improves performance of git-log operations in `gitlens.diffWithPrevious` and `gitlens.diffWithWorking` commands
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Changes `Not Committed Yet` author for uncommitted changes to `Uncommitted`
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Fixes showing `gitlens.showQuickCommitDetails` quick pick menu for uncommitted changes -- now shows the previous commit details
|
||||||
|
|
||||||
|
## [2.5.1]
|
||||||
|
### Added
|
||||||
|
- Adds `gitlens.copyMessageToClipboard` command to copy commit message to the clipboard
|
||||||
|
- Adds `gitlens.copyMessageToClipboard` to the editor content menu
|
||||||
|
- Adds `Copy Commit Message to Clipboard` command to `gitlens.showQuickCommitDetails` quick pick menu
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Changes behavior of `gitlens.copyShaToClipboard` to copy the sha of the most recent commit to the repository if there is no active editor
|
||||||
|
- Changes behavior of `gitlens.showQuickFileHistory` to execute `gitlens.showQuickRepoHistory` if there is no active editor
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Fixes issue where shortcut keys weren't disabled if GitLens was disabled
|
||||||
|
|
||||||
|
## [2.5.0]
|
||||||
|
### Added
|
||||||
|
- Overhauls the `gitlens.showQuickRepoHistory`, `gitlens.showQuickFileHistory`, and `gitlens.showQuickCommitDetails` quick pick menus
|
||||||
|
- Adds `Show Repository History` command to `gitlens.showQuickFileHistory` quick pick menu
|
||||||
|
- Adds `Show Previous Commits History` command to `gitlens.showQuickCommitDetails` quick pick menu
|
||||||
|
- Adds `Show Commits History` command to `gitlens.showQuickCommitDetails` quick pick menu
|
||||||
|
- Adds `Copy Commit Sha to Clipboard` command to `gitlens.showQuickCommitDetails` quick pick menu
|
||||||
|
- Adds `Show Changed Files` command to `gitlens.showQuickCommitDetails` quick pick menu
|
||||||
|
- Adds more robust `go back` navigation in quick pick menus
|
||||||
|
- Adds commit message to placeholder text of many quick pick menus
|
||||||
|
- Adds icons for some commands
|
||||||
|
- Adds `gitlens.diffWithPrevious` command to the editor content menu
|
||||||
|
- Adds `gitlens.diffWithWorking` command to the editor content menu
|
||||||
|
- Adds `gitlens.showQuickRepoHistory` and `gitlens.showQuickCommitDetails` commands to code lens
|
||||||
|
- Adds `gitlens.showQuickRepoHistory` and `gitlens.showQuickCommitDetails` commands to the status bar
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Changes the default command of `gitlens.codeLens.recentChange.command` to `gitlens.showQuickCommitDetails`
|
||||||
|
- Changes the default command of `gitlens.statusBar.command` to `gitlens.showQuickCommitDetails`
|
||||||
|
- Changes behavior of `gitlens.showQuickCommitDetails` to show commit commands rather than file set (use `Show Changed Files` command to get to the file set)
|
||||||
|
- Changes `gitlens.diffWithPrevious` command to behave as `gitlens.diffWithWorking` if the file has uncommitted changes
|
||||||
|
- Renames `gitlens.diffWithPrevious` command from `Diff Commit with Previous` to `Compare with Previous Commit`
|
||||||
|
- Renames `gitlens.diffLineWithPrevious` command from `Diff Commit (line) with Previous` to `Compare Line with Previous Commit`
|
||||||
|
- Renames `gitlens.diffWithWorking` command from `Diff Commit with Working Tree` to `Compare with Working Tree`
|
||||||
|
- Renames `gitlens.diffLineWithWorking` command from `Diff Commit (line) with Working Tree` to `Compare Line with Working Tree`
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Fixes issues with certain git commands not working on Windows
|
||||||
|
- Fixes [#31](https://github.com/eamodio/vscode-gitlens/issues/31) - Disable gitlens if the project does not have `.git` folder
|
||||||
|
- Fixes issue where quick pick menus could fail if there was no active editor
|
||||||
|
- Fixes code lens not updating in response to configuration changes
|
||||||
|
|
||||||
|
## [2.1.1]
|
||||||
|
### Fixed
|
||||||
|
- Fixes overzealous active line annotation updating on document changes
|
||||||
|
|
||||||
|
## [2.1.0]
|
||||||
|
### Added
|
||||||
|
- Adds a new GitLens logo and changes all images to svg
|
||||||
|
- Adds `alt+p` shortcut for the `gitlens.diffLineWithPrevious` command
|
||||||
|
- Adds `shift+alt+p` shortcut for the `gitlens.diffWithPrevious` command
|
||||||
|
- Adds `alt+w` shortcut for the `gitlens.diffLineWithWorking` command
|
||||||
|
- Adds `shift+alt+w` shortcut for the `gitlens.diffWithWorking` command
|
||||||
|
- Adds `gitlens.copyShaToClipboard` command to copy commit sha to the clipboard ([#28](https://github.com/eamodio/vscode-gitlens/issues/28))
|
||||||
|
- Adds `gitlens.showQuickCommitDetails` command to show a quick pick menu of details for a commit
|
||||||
|
- Adds `go back` choice to `gitlens.showQuickCommitDetails`, `gitlens.showQuickFileHistory`, and `gitlens.showQuickRepoHistory` quick pick menus
|
||||||
|
- Adds `gitlens.blame.annotation.highlight` to specify whether and how to highlight blame annotations ([#24](https://github.com/eamodio/vscode-gitlens/issues/24))
|
||||||
|
- Greatly improves performance of line navigation when either active line annotations or status bar blame is enabled
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Fixes [#29](https://github.com/eamodio/vscode-gitlens/issues/29) - Commit info tooltip duplicated for current line when blame is enabled
|
||||||
|
- Fixes issue where sometimes the commit history shown wasn't complete
|
||||||
|
- Fixes issues with `gitlens.diffLineWithPrevious` and `gitlens.diffWithPrevious` not following renames properly
|
||||||
|
- Fixes issues with `gitlens.diffLineWithPrevious` and `gitlens.diffWithPrevious` not always grabbing the correct commit
|
||||||
|
|
||||||
|
## [2.0.2]
|
||||||
|
### Added
|
||||||
|
- Adds auto-enable of whitespace toggling when using font-ligatures because of [vscode issue](https://github.com/Microsoft/vscode/issues/11485)
|
||||||
|
- Adds `gitlens.blame.annotation.characters.*` settings to provide some control over how annotations are displayed
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Fixes [#22](https://github.com/eamodio/vscode-gitlens/issues/22) - Cannot read property 'sha' of undefined
|
||||||
|
|
||||||
|
## [2.0.1]
|
||||||
|
### Fixed
|
||||||
|
- Fixes [#26](https://github.com/eamodio/vscode-gitlens/issues/26) - Active line annotation doesn't disappear properly after delete
|
||||||
|
|
||||||
|
## [2.0.0]
|
||||||
|
### Added
|
||||||
|
- Adds `gitlens.blame.annotation.activeLine` to specify whether and how to show blame annotations on the active line
|
||||||
|
- Adds full commit message (rather than just summary) to active line hover if `gitlens.blame.annotation.activeLine` is not `off`
|
||||||
|
- Adds new `trailing` blame annotation style -- adds annotations after the code lines rather than before
|
||||||
|
- Adds `gitlens.blame.annotation.message` to show the commit message in `expanded` and `trailing` blame annotation styles
|
||||||
|
- Adds support for relative dates in blame annotations. Use `gitlens.blame.annotation.date`
|
||||||
|
- Re-adds context menu for `gitlens.diffLineWithPrevious` -- since [vscode issue](https://github.com/Microsoft/vscode/issues/15395)
|
||||||
|
- Re-adds context menu for `gitlens.diffLineWithWorking` -- since [vscode issue](https://github.com/Microsoft/vscode/issues/15395)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Changes the design of hover annotations -- much cleaner now
|
||||||
|
- Disables automatic whitespace toggling by default as it is seemingly no longer needed as [vscode issue](https://github.com/Microsoft/vscode/issues/11485) seems fixed. It can be re-enabled with `gitlens.advanced.toggleWhitespace.enabled`
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Fixes issue where the status bar blame would get stuck switching between editors
|
||||||
|
- Fixes issue where code lens aren't updated properly after a file is saved
|
||||||
|
|
||||||
|
## [1.4.3]
|
||||||
|
### Added
|
||||||
|
- Adds some logging to hopefully trap [#22](https://github.com/eamodio/vscode-gitlens/issues/22) - Cannot read property 'sha' of undefined
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Fixes issue with the latest insiders build (1.9.0-insider f67f87c5498d9361c0b29781c341fd032815314b) where there is a collision of document schemes
|
||||||
|
|
||||||
|
## [1.4.2]
|
||||||
|
### Fixed
|
||||||
|
- Fixes issue where file history wouldn't compare correctly to working tree if the filename had changed
|
||||||
|
|
||||||
|
## [1.4.1]
|
||||||
|
### Added
|
||||||
|
- Adds `gitlens.advanced.gitignore.enabled` to enable/disable .gitignore parsing. Addresses [#20](https://github.com/eamodio/vscode-gitlens/issues/20) - Nested .gitignore files can cause blame to fail with a repo within another repo
|
||||||
|
|
||||||
|
## [1.4.0]
|
||||||
|
### Added
|
||||||
|
- Adds `alt+h` shortcut for the `gitlens.showQuickFileHistory` command
|
||||||
|
- Adds `shift+alt+h` shortcut for the `gitlens.showQuickRepoHistory` command
|
||||||
|
- Adds `gitlens.advanced.maxQuickHistory` to limit the number of quick history entries to show (for better performance); Defaults to 200
|
||||||
|
- Adds `gitlens.diffLineWithPrevious` as `alt` context menu item for `gitlens.diffWithPrevious`
|
||||||
|
- Adds `gitlens.diffLineWithWorking` as `alt` context menu item for `gitlens.diffWithWorking`
|
||||||
|
- Adds `gitlens.showFileHistory` as `alt` context menu item for `gitlens.showQuickFileHistory`
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
- Removes context menu for `gitlens.diffLineWithPrevious` -- since it is now the `alt` of `gitlens.diffWithPrevious`
|
||||||
|
- Removes context menu for `gitlens.diffLineWithWorking` -- since it is now the `alt` of `gitlens.diffWithWorking`
|
||||||
|
- Replaces `gitlens.menus.fileDiff.enabled` and `gitlens.menus.lineDiff.enabled` with `gitlens.menus.diff.enabled` -- since the switch between file and line diff is now controlled by the `alt` key
|
||||||
|
|
||||||
|
## [1.3.1]
|
||||||
|
### Added
|
||||||
|
- Adds `Diff Commit with Working Tree` to the explorer context menu (assuming `gitlens.menus.fileDiff.enabled` is `true`)
|
||||||
|
- Adds `Diff Commit with Working Tree` & `Diff Commit with Previous` to the editor title context menu (assuming `gitlens.menus.fileDiff.enabled` is `true`)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Renames `Diff` commands for better clarity
|
||||||
|
- Removes `Git` from the commands as it feels unnecessary
|
||||||
|
- Reorders the context menu commands
|
||||||
|
|
||||||
|
## [1.3.0]
|
||||||
|
### Added
|
||||||
|
- Adds support for blame and history (log) on files opened via compare commands -- allows for deep navigation through git history
|
||||||
|
|
||||||
|
## [1.2.0]
|
||||||
|
### Added
|
||||||
|
- Adds compare (working vs previous) options to repository history
|
||||||
|
- Adds compare (working vs previous) options to file history
|
||||||
|
### Fixed
|
||||||
|
- Fixes issue with repository history compare with commits with multiple files
|
||||||
|
|
||||||
|
## [1.1.1]
|
||||||
|
### Added
|
||||||
|
- Adds logging for tracking [#18](https://github.com/eamodio/vscode-gitlens/issues/18) - GitLens only displayed for some files
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Changes `gitlens.showQuickRepoHistory` command to run without an open editor (falls back to the folder repository)
|
||||||
|
|
||||||
|
## [1.1.0]
|
||||||
|
### Added
|
||||||
|
- Adds new `gitlens.showQuickFileHistory` command to show the file history in a quick-pick list (palette)
|
||||||
|
- Adds new `gitlens.showQuickRepoHistory` command to show the repository history in a quick-pick list (palette)
|
||||||
|
- Adds `gitlens.showQuickFileHistory` option to the `gitlens.codeLens.recentChange.command`, `gitlens.codeLens.authors.command`, and `gitlens.statusBar.command` settings
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Changes the `gitlens.statusBar.command` settings default to `gitlens.showQuickFileHistory` instead of `gitlens.toggleBlame`
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
- Removes `git.viewFileHistory` option from the `gitlens.codeLens.recentChange.command`, `gitlens.codeLens.authors.command`, and `gitlens.statusBar.command` settings
|
||||||
|
|
||||||
|
## [1.0.2]
|
||||||
|
### Fixed
|
||||||
|
- Fixes [#16](https://github.com/eamodio/vscode-gitlens/issues/16) - incorrect 'Unable to find Git' message
|
||||||
|
|
||||||
|
## [1.0.0]
|
||||||
|
### Added
|
||||||
|
- Adds support for git history (log)!
|
||||||
|
- Adds support for blame annotations and git commands on file revisions
|
||||||
|
- Adds ability to show multiple blame annotation at the same time (one per vscode editor)
|
||||||
|
- Adds new `gitlens.showFileHistory` command to open the history explorer
|
||||||
|
- Adds new `gitlens.showFileHistory` option to the `gitlens.codeLens.recentChange.command`, `gitlens.codeLens.authors.command`, and `gitlens.statusBar.command` settings
|
||||||
|
- Adds per-language code lens location customization using the `gitlens.codeLens.languageLocations` setting
|
||||||
|
- Adds new `gitlens.diffLineWithPrevious` command for line sensitive diffs
|
||||||
|
- Adds new `gitlens.diffLineWithWorking` command for line sensitive diffs
|
||||||
|
- Adds `gitlens.diffWithPrevious` command to the explorer context menu
|
||||||
|
- Adds output channel logging, controlled by the `gitlens.advanced.output.level` setting
|
||||||
|
- Improves performance of the code lens support
|
||||||
|
- Improves performance (significantly) when only showing code lens at the document level
|
||||||
|
- Improves performance of status bar blame support
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Switches on-demand code lens to be a global toggle (rather than per file)
|
||||||
|
- Complete rewrite of the blame annotation provider to reduce overhead and provide better performance
|
||||||
|
- Changes `gitlens.diffWithPrevious` command to always be file sensitive diffs
|
||||||
|
- Changes `gitlens.diffWithWorking` command to always be file sensitive diffs
|
||||||
|
- Removes all debug logging, unless the `gitlens.advanced.debug` settings it on
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Fixes many (most?) issues with whitespace toggling (required because of https://github.com/Microsoft/vscode/issues/11485)
|
||||||
|
- Fixes issue where blame annotations would not be cleared properly when switching between open files
|
||||||
|
|
||||||
|
## [0.5.5]
|
||||||
|
### Fixed
|
||||||
|
- Fixes another off-by-one issue when diffing with caching
|
||||||
|
|
||||||
|
## [0.5.4]
|
||||||
|
### Fixed
|
||||||
|
- Fixes off-by-one issues with blame annotations without caching and when diffing with a previous version
|
||||||
|
|
||||||
|
## [0.5.3]
|
||||||
|
### Added
|
||||||
|
- Adds better uncommitted hover message in blame annotations
|
||||||
|
- Adds more protection for dealing with uncommitted lines
|
||||||
|
|
||||||
|
## [0.5.2]
|
||||||
|
### Fixed
|
||||||
|
- Fixes loading issue on Linux
|
||||||
|
|
||||||
|
## [0.5.1]
|
||||||
|
### Added
|
||||||
|
- Adds blame information in the status bar
|
||||||
|
- Add new status bar settings -- see **Extension Settings** for details
|
||||||
|
- Adds new `gitlens.diffWithPrevious` option to the `gitlens.codeLens.recentChange.command` & `gitlens.codeLens.authors.command` settings
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Renames the `gitlens.codeLens.recentChange.command` & `gitlens.codeLens.authors.command` settings options (to align with command names)
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
- Removes `gitlens.blame.annotation.useCodeActions` setting and behavior
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Fixes Diff with Previous when the selection is uncommitted
|
||||||
|
|
||||||
|
## [0.3.3]
|
||||||
|
### Fixed
|
||||||
|
- Fixes [#7](https://github.com/eamodio/vscode-gitlens/issues/7) - missing spawn-rx dependency (argh!)
|
||||||
|
|
||||||
|
## [0.3.2]
|
||||||
|
### Fixed
|
||||||
|
- Fixes [#7](https://github.com/eamodio/vscode-gitlens/issues/7) - missing lodash dependency
|
||||||
|
|
||||||
|
## [0.3.1]
|
||||||
|
### Added
|
||||||
|
- Adds new code lens visibility & location settings -- see **Extension Settings** for details
|
||||||
|
- Adds new command to toggle code lens on and off when `gitlens.codeLens.visibility` is set to `ondemand`
|
||||||
|
|
||||||
|
## [0.2.0]
|
||||||
|
### Changed
|
||||||
|
- Replaces blame regex parsing with a more robust parser
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Fixes [#1](https://github.com/eamodio/vscode-gitlens/issues/1) - Support blame on files outside the workspace repository
|
||||||
|
- Fixes failures with Diff with Previous command
|
||||||
|
- Fixes issues with blame explorer code lens when dealing with previous commits
|
||||||
|
- Fixes display issues with compact blame annotations (now skips blank lines)
|
||||||
|
|
||||||
|
## [0.1.3]
|
||||||
|
### Added
|
||||||
|
- Improved blame annotations, now with sha and author by default
|
||||||
|
- Add new blame annotation styles -- compact and expanded (default)
|
||||||
|
- Adds many new configuration settings; see **Extension Settings** for details
|
||||||
|
|
||||||
|
## [0.0.7]
|
||||||
|
### Added
|
||||||
|
- Adds .gitignore checks to reduce the number of blame calls
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Fixes [#4](https://github.com/eamodio/vscode-gitlens/issues/4) - Absolute paths fail on Windows due to backslash (Really!)
|
||||||
|
- Fixes [#5](https://github.com/eamodio/vscode-gitlens/issues/5) - Finding first non-white-space fails sometimes
|
||||||
|
|
||||||
|
## [0.0.6]
|
||||||
|
### Added
|
||||||
|
- Adds attempt to scroll to the correct position when opening a diff
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Fixes [#2](https://github.com/eamodio/vscode-gitlens/issues/2) - [request] Provide some debug info when things fail
|
||||||
|
- Fixes [#4](https://github.com/eamodio/vscode-gitlens/issues/4) - Absolute paths fail on Windows due to backslash
|
||||||
|
|
||||||
|
## [0.0.5]
|
||||||
|
### Changed
|
||||||
|
- Removes code lens from fields and single-line properties to reduce visual noise
|
||||||
|
- Automatically turns off blame only when required now
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Fixes issues where filename changes in history would cause diffs to fails
|
||||||
|
- Fixes some issues with uncommitted blames
|
||||||
|
|
||||||
|
## [0.0.4]
|
||||||
|
### Added
|
||||||
|
- Candidate for preview release on the vscode marketplace.
|
||||||
|
|
||||||
|
## [0.0.1]
|
||||||
|
### Added
|
||||||
|
- Initial release but still heavily a work in progress.
|
||||||
12
ISSUE_TEMPLATE.md
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<!--
|
||||||
|
If you are encountering an issue that says `See output channel for more details`, please enable output channel logging by setting `"gitlens.outputLevel": "verbose"` in your settings.json. This will enable logging to the GitLens channel in the Output pane. Once enabled, please attempt to reproduce the issue (if possible) and attach the relevant log lines from the GitLens channel.
|
||||||
|
-->
|
||||||
|
|
||||||
|
- GitLens Version:
|
||||||
|
- VSCode Version:
|
||||||
|
- OS Version:
|
||||||
|
|
||||||
|
Steps to Reproduce:
|
||||||
|
|
||||||
|
1.
|
||||||
|
2.
|
||||||
2
LICENSE
@@ -1,6 +1,6 @@
|
|||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2016 Eric Amodio
|
Copyright (c) 2016-2017 Eric Amodio
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|||||||
252
README.md
@@ -1,31 +1,221 @@
|
|||||||
# GitLens
|
[](https://marketplace.visualstudio.com/items?itemName=eamodio.gitlens)
|
||||||
|
[](https://marketplace.visualstudio.com/items?itemName=eamodio.gitlens)
|
||||||
Provides Git blame (and history eventually) CodeLens for many supported Visual Studio Code languages (in theory -- the language must support symbol searching).
|
[](https://marketplace.visualstudio.com/items?itemName=eamodio.gitlens)
|
||||||
|
[](https://gitter.im/vscode-gitlens/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||||
## Features
|
|
||||||
|
# GitLens
|
||||||
Provides CodeLens with the author and date of the last check-in.
|
|
||||||
|
GitLens **supercharges** the built-in Visual Studio Code Git capabilities. It helps you to **visualize code authorship** at a glance via inline Git blame annotations and code lens, **seamlessly navigate and explore** the history of a file or branch, **gain valuable insights** via powerful comparision commands, and so much more.
|
||||||
> 
|
|
||||||
|
GitLens provides an unobtrusive blame annotation at the end of the selected line, a status bar item showing the commit author and date of the selected line, code lens showing the most recent commit and # of authors of the file and/or code block, and many commands for exploring commits and histories, comparing and navigating revisions, stash access, repository status, and more. GitLens is also [highly customizable](#extension-settings) to meet your specific needs — find code lens intrusive or the selected line blame annotation distracting — no problem, it is easy to [turn them off or change how they behave](#extension-settings).
|
||||||
Clicking on a CodeLens opens a blame "explorer" with the commits and changed lines in the right pane and the commit (file) contents on the left.
|
|
||||||
|
## Previews
|
||||||
> 
|
#### Featuring code lens, whole file inline blame annotations, and navigation and exploration via quick pick menus
|
||||||
|

|
||||||
## Requirements
|
|
||||||
|
#### Featuring selected line blame annotation and hovers, status bar commit details, quick pick menus, compare with previous, and more
|
||||||
Must be using Git and it must be in your path.
|

|
||||||
|
|
||||||
## Extension Settings
|
## Features
|
||||||
|
|
||||||
None yet.
|
#### Git Blame Annotations
|
||||||
|
|
||||||
## Known Issues
|
- Adds a **blame annotation** to the end of the selected line showing the commit id and message, with more details in a hover popup ([optional](#extension-settings), on by default)
|
||||||
|
|
||||||
Too many to count -- this is still very much a work in progress.
|
- Adds a `Toggle Blame Annotations` command (`gitlens.toggleBlame`) with a shortcut of `alt+b` to toggle **inline Git blame annotations** for a whole file with multiple styles — compact, expanded, and trailing
|
||||||
|
- Also adds a `Show Blame Annotations` command (`gitlens.showBlame`)
|
||||||
## Release Notes
|
|
||||||
|
- Adds **author and date blame information** about the selected line to the **status bar** ([optional](#extension-settings), on by default)
|
||||||
### 0.0.1
|
- By default clicking on the status bar shows a **commit details quick pick menu** with commands for comparing, navigating and exploring commits, and more
|
||||||
|
|
||||||
Initial release but still heavily a work in progress.
|
- Provides [customizable](#extension-settings) click behavior of the status bar — choose between one of the following
|
||||||
|
- Toggle whole file blame annotations on and off
|
||||||
|
- Toggle code lens on and off — only available if [`"gitlens.codeLens.visibility": "ondemand"`](#extension-settings) is set
|
||||||
|
- Compare the file with the previous commit
|
||||||
|
- Show a quick pick menu with details and commands for the commit
|
||||||
|
- Show a quick pick menu with file details and commands for the commit
|
||||||
|
- Show a quick pick menu with the commit history of the file
|
||||||
|
- Show a quick pick menu with the commit history of the current branch
|
||||||
|
|
||||||
|
#### Git Code Lens
|
||||||
|
|
||||||
|
- Adds **code lens** to the top of the file and on code blocks ([optional](#extension-settings), on by default)
|
||||||
|
- **Recent Change** — author and date of the most recent commit for the file or code block
|
||||||
|
- By default, clicking on the code lens shows a **commit file details quick pick menu** with commands for comparing, navigating and exploring commits, and more
|
||||||
|
- **Authors** — number of authors of the file or code block and the most prominent author (if there is more than one)
|
||||||
|
- By default, clicking on the code lens toggles the inline Git blame annotations on and off for the whole file
|
||||||
|
- Will be hidden if the author of the most recent commit is also the only author of the file or block, to avoid duplicate information and reduce visual noise
|
||||||
|
|
||||||
|
- Provides [customizable](#extension-settings) click behavior for each code lens — choose between one of the following
|
||||||
|
- Toggle whole file blame annotations on and off
|
||||||
|
- Compare the file with the previous commit
|
||||||
|
- Show a quick pick menu with details and commands for the commit
|
||||||
|
- Show a quick pick menu with file details and commands for the commit
|
||||||
|
- Show a quick pick menu with the commit history of the file
|
||||||
|
- Show a quick pick menu with the commit history of the current branch
|
||||||
|
|
||||||
|
- Adds a `Toggle Git Code Lens` command (`gitlens.toggleCodeLens`) with a shortcut of `shift+alt+b` to toggle the code lens on and off — only available if [`"gitlens.codeLens.visibility": "ondemand"`](#extension-settings) is set
|
||||||
|
|
||||||
|
#### Powerful Comparison Tools
|
||||||
|
|
||||||
|
- Effortlessly navigate between comparisions via the `alt+,` and `alt+.` shortcut keys to go back and forth through a file's revisions
|
||||||
|
|
||||||
|
- Provides easy access to the following comparison commands via the `Command Palette` as well as in context via the many provided quick pick menus
|
||||||
|
|
||||||
|
- Adds a `Directory Compare` command (`gitlens.diffDirectory`) to open the configured Git difftool to compare directories between branches
|
||||||
|
|
||||||
|
- Adds a `Compare File with...` command (`gitlens.diffWithBranch`) to compare the active file with the same file on the selected branch
|
||||||
|
|
||||||
|
- Adds a `Compare File with Next Commit` command (`gitlens.diffWithNext`) with a shortcut of `alt+.` to compare the active file/diff with the next commit revision
|
||||||
|
|
||||||
|
- Adds a `Compare File with Previous Commit` command (`gitlens.diffWithPrevious`) with a shortcut of `alt+,` to compare the active file/diff with the previous commit revision
|
||||||
|
|
||||||
|
- Adds a `Compare Line with Previous Commit` command (`gitlens.diffLineWithPrevious`) with a shortcut of `shift+alt+,` to compare the active file/diff with the previous line commit revision
|
||||||
|
|
||||||
|
- Adds a `Compare File with Working Tree` command (`gitlens.diffWithWorking`) with a shortcut of `shift+alt+w` to compare the most recent commit revision of the active file/diff with the working tree
|
||||||
|
|
||||||
|
- Adds a `Compare Line with Working Tree` command (`gitlens.diffLineWithWorking`) with a shortcut of `alt+w` to compare the commit revision of the active line with the working tree
|
||||||
|
|
||||||
|
#### Navigate and Explore
|
||||||
|
|
||||||
|
- Adds a `Search Commits` command (`gitlens.showCommitSearch`) with a shortcut of `alt+/` to search for commits by message, author, file(s), or commit id
|
||||||
|
|
||||||
|
- Adds commands to open files, commits, branches, and the repository in the supported remote services, currently **BitBucket, GitHub, GitLab, and Visual Studio Team Services** — only available if a Git upstream service is configured in the repository
|
||||||
|
- `Open Branch in Remote` command (`gitlens.openBranchInRemote`) — opens the current branch commits in the supported remote service
|
||||||
|
- `Open Line Commit in Remote` command (`gitlens.openCommitInRemote`) — opens the commit revision of the active line in the supported remote service
|
||||||
|
- `Open File in Remote` command (`gitlens.openFileInRemote`) — opens the active file/revision in the supported remote service
|
||||||
|
- `Open Repository in Remote` command (`gitlens.openRepoInRemote`) — opens the repository in the supported remote service
|
||||||
|
|
||||||
|
- Adds a `Show Current Branch History` command (`gitlens.showQuickRepoHistory`) with a shortcut of `shift+alt+h` to show a paged **branch history quick pick menu** of the current branch for exploring its commit history
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
- Provides entries to `Show Commit Search` and `Open Branch in <remote-service>` when available
|
||||||
|
- Navigate back to the previous quick pick menu via `alt+left arrow`, if available
|
||||||
|
- Navigate pages via `alt+,` and `alt+.` to go backward and forward respectively
|
||||||
|
|
||||||
|
- Adds a `Show Branch History` command (`gitlens.showQuickBranchHistory`) to show a paged **branch history quick pick menu** of the selected branch for exploring its commit history
|
||||||
|
- Provides the same features as `Show Current Branch History` above
|
||||||
|
|
||||||
|
- Adds a `Show File History` command (`gitlens.showQuickFileHistory`) to show a paged **file history quick pick menu** of the active file for exploring its commit history
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
- Provides entries to `Show Branch History` and `Open File in <remote-service>` when available
|
||||||
|
- Navigate back to the previous quick pick menu via `alt+left arrow`, if available
|
||||||
|
- Navigate pages via `alt+,` and `alt+.` to go backward and forward respectively
|
||||||
|
|
||||||
|
- Adds a `Show Commit Details` command (`gitlens.showQuickCommitDetails`) to show a **commit details quick pick menu** of the most recent commit of the active file
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
- Quickly see the set of files changed in the commit, complete with status indicators for adds, changes, renames, and deletes
|
||||||
|
- Provides entries to `Copy to Clipboard`, `Directory Compare`, `Open Changed Files`, `Open File in <remote-service>` when available, and more
|
||||||
|
- Navigate back to the previous quick pick menu via `alt+left arrow`, if available
|
||||||
|
- Use the `alt+right arrow` shortcut on an entry to execute it without closing the quick pick menu, if possible — commands that open windows outside of VS Code will still close the quick pick menu unless [`"gitlens.advanced.quickPick.closeOnFocusOut": false`](#extension-settings) is set
|
||||||
|
- Use the `alt+right arrow` shortcut on a file entry in the `Changed Files` section to preview the current revision of the while leaving the quick pick menu open
|
||||||
|
- NOTE: Once [vscode issue #10568](https://github.com/Microsoft/vscode/issues/10568) is resolved this will change to preview the comparison of the current revision with the previous one
|
||||||
|
|
||||||
|
- Adds a `Show Line Commit Details` command (`gitlens.showQuickCommitFileDetails`) with a shortcut of `alt+c` to show a **file commit details quick pick menu** of the most recent commit of the active file
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
- Provides entries to `Show Commit Details`, `Show File History`, `Compare with...`, `Copy to Clipboard`, `Open File`, `Open File in <remote-service>` when available, and more
|
||||||
|
- Navigate back to the previous quick pick menu via `alt+left arrow`, if available
|
||||||
|
- Use the `alt+right arrow` shortcut on an entry to execute it without closing the quick pick menu, if possible — commands that open windows outside of VS Code will still close the quick pick menu unless [`"gitlens.advanced.quickPick.closeOnFocusOut": false`](#extension-settings) is set
|
||||||
|
|
||||||
|
- Adds a `Show Repository Status` command (`gitlens.showQuickRepoStatus`) with a shortcut of `alt+s` to show a **repository status quick pick menu** for visualizing the current repository status
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
- Quickly see upstream status (if an Git upstream is configured) — complete with ahead and behind information
|
||||||
|
- If you are ahead of the upstream, an entry will be shown with the number of commits ahead. Chosing it will show a limited **branch history quick pick menu** containing just the commits ahead of the upstream
|
||||||
|
- If you are behind the upstream, an entry will be shown with the number of commits behind. Chosing it will show a limited **branch history quick pick menu** containing just the commits behind the upstream
|
||||||
|
- Quickly see all working changes, both staged and unstaged, complete with status indicators for adds, changes, renames, and deletes
|
||||||
|
- Provides entries to `Show Stashed Changes`, `Open Changed Files`, and `Close Unchanged Files`
|
||||||
|
- Use the `alt+right arrow` shortcut on an entry to execute it without closing the quick pick menu, if possible — commands that open windows outside of VS Code will still close the quick pick menu unless [`"gitlens.advanced.quickPick.closeOnFocusOut": false`](#extension-settings) is set
|
||||||
|
- Use the `alt+right arrow` shortcut on a file entry in the `Staged Files` or `Unstaged Files` sections to preview the working file while leaving the quick pick menu open
|
||||||
|
- NOTE: Once [vscode issue #10568](https://github.com/Microsoft/vscode/issues/10568) is resolved this will change to preview the comparison of the working file with the previous revision
|
||||||
|
|
||||||
|
- Adds a `Show Stashed Changes` command (`gitlens.showQuickStashList`) to show a **stashed changes quick pick menu** for exploring your repository stash history
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
- [Insiders only](#insiders) — Provides entries to `Stash Changes`
|
||||||
|
- Navigate back to the previous quick pick menu via `alt+left arrow`, if available
|
||||||
|
|
||||||
|
- Chosing a stash entry shows a **stash details quick pick menu** which is very similar to the **commit details quick pick menu** above
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
- Quickly see the set of files changed in the stash, complete with status indicators for adds, changes, renames, and deletes
|
||||||
|
- Provides entries to `Copy Message to Clipboard`, `Directory Compare`, and `Open Changed Files`
|
||||||
|
- [Insiders only](#insiders) — Provides entries to `Apply Stashed Changes` and `Delete Stashed Changes` — both require a confirmation
|
||||||
|
- Navigate back to the previous quick pick menu via `alt+left arrow`, if available
|
||||||
|
- Use the `alt+right arrow` shortcut on an entry to execute it without closing the quick pick menu, if possible — commands that open windows outside of VS Code will still close the quick pick menu unless [`"gitlens.advanced.quickPick.closeOnFocusOut": false`](#extension-settings) is set
|
||||||
|
- Use the `alt+right arrow` shortcut on a file entry in the `Changed Files` section to preview the current revision of the while leaving the quick pick menu open
|
||||||
|
- NOTE: Once [vscode issue #10568](https://github.com/Microsoft/vscode/issues/10568) is resolved this will change to preview the comparison of the current revision with the previous one
|
||||||
|
|
||||||
|
- Adds a `Show Last Opened Quick Pick` command (`gitlens.showLastQuickPick`) with a shortcut of `alt+-` to quickly get back to where you were when the last GitLens quick pick menu closed
|
||||||
|
|
||||||
|
- Adds a `Open File History Explorer` command (`gitlens.showFileHistory`) to show a **file history explorer** (peek style) to visualize the history of a file
|
||||||
|
- Likely to be deprecated in a future release, add your voice to [#66](https://github.com/eamodio/vscode-gitlens/issues/66) if you feel it should not be removed
|
||||||
|
|
||||||
|
- Adds a `Open Blame History Explorer` command (`gitlens.showBlameHistory`) to show a **blame history explorer** (peek style) to visualize the blame history of a file or code block
|
||||||
|
- Likely to be deprecated in a future release, add your voice to [#66](https://github.com/eamodio/vscode-gitlens/issues/66) if you feel it should not be removed
|
||||||
|
|
||||||
|
#### And More
|
||||||
|
|
||||||
|
- Adds a `Copy Commit ID to Clipboard` command (`gitlens.copyShaToClipboard`) to copy the commit id (sha) of the active line to the clipboard
|
||||||
|
|
||||||
|
- Adds a `Copy Commit Message to Clipboard` command (`gitlens.copyMessageToClipboard`) to copy the commit message of the active line to the clipboard
|
||||||
|
|
||||||
|
- Adds a `Open Changed Files` command (`gitlens.openChangedFiles`) to open any files with working tree changes
|
||||||
|
|
||||||
|
- Adds a `Close Unchanged Files` command (`gitlens.closeUnchangedFiles`) to close any files without working tree changes
|
||||||
|
|
||||||
|
- [Insiders only](#insiders) — Adds a `Apply Stashed Changes` command (`gitlens.stashApply`) to chose a stash entry to apply to the working tree from a quick pick menu
|
||||||
|
|
||||||
|
- [Insiders only](#insiders) — Adds a `Stash Changes` command (`gitlens.stashSave`) to save any working tree changes to the stash — can optionally provide a stash message
|
||||||
|
|
||||||
|
## Insiders
|
||||||
|
|
||||||
|
Add [`"gitlens.insiders": true`](#extension-settings) to your settings to join the insiders channel and get early access to upcoming features. Be aware that because this provides early access expect there to be issues.
|
||||||
|
|
||||||
|
## Extension Settings
|
||||||
|
|
||||||
|
GitLens is highly customizable and provides many configuration settings to allow the personalization of almost all features
|
||||||
|
|
||||||
|
|Name | Description
|
||||||
|
|-----|------------
|
||||||
|
|`gitlens.insiders`|Opts into the insiders channel -- provides access to upcoming features
|
||||||
|
|`gitlens.outputLevel`|Specifies how much (if any) output will be sent to the GitLens output channel
|
||||||
|
|`gitlens.blame.annotation.activeLine`|Specifies whether and how to show blame annotations on the active line. `off` - no annotation. `inline` - adds a trailing annotation to the active line. `hover` - adds hover annotation to the active line. `both` - adds both `inline` and `hover` annotations
|
||||||
|
|`gitlens.blame.annotation.activeLineDarkColor`|Specifies the color of the active line blame annotation to use with a dark theme. Must be a valid css color
|
||||||
|
|`gitlens.blame.annotation.activeLineLightColor`|Specifies the color of the active line blame annotation to use with a light theme. Must be a valid css color
|
||||||
|
|`gitlens.blame.annotation.highlight`|Specifies whether and how to highlight blame annotations. `none` - no highlight. `gutter` - adds a gutter icon. `line` - adds a full-line highlight. `both` - adds both `gutter` and `line` highlights
|
||||||
|
|`gitlens.blame.annotation.style`|Specifies the style of the blame annotations. `compact` - groups annotations to limit the repetition and also adds author and date when possible. `expanded` - shows an annotation on every line
|
||||||
|
|`gitlens.blame.annotation.author`|Specifies whether the committer will be shown in the blame annotations. Applies only to the `expanded` & `trailing` annotation styles
|
||||||
|
|`gitlens.blame.annotation.date`|Specifies whether and how the commit date will be shown in the blame annotations. `off` - no date. `relative` - relative date (e.g. 1 day ago). `absolute` - date format specified by `gitlens.blame.annotation.dateFormat`. Applies only to the `expanded` & `trailing` annotation styles
|
||||||
|
|`gitlens.blame.annotation.dateFormat`|Specifies the date format of how absolute dates will be shown in the blame annotations. See https://momentjs.com/docs/#/displaying/format/ for valid formats
|
||||||
|
|`gitlens.blame.annotation.message`|Specifies whether the commit message will be shown in the blame annotations. Applies only to the `expanded` & `trailing` annotation styles
|
||||||
|
|`gitlens.blame.annotation.sha`|Specifies whether the commit sha will be shown in the blame annotations. Applies only to the `expanded` & `trailing` annotation styles
|
||||||
|
|`gitlens.codeLens.visibility`|Specifies when code lens will be shown in the active document. `auto` - always shown. `ondemand` - never shown, unless toggled via the `gitlens.toggleCodeLens` command. `off` - never shown
|
||||||
|
|`gitlens.codeLens.authors.enabled`|Specifies whether the authors code lens is shown
|
||||||
|
|`gitlens.codeLens.authors.command`|Specifies the command executed when the authors code lens is clicked. `gitlens.toggleBlame` - toggles blame annotations. `gitlens.showBlameHistory` - opens the blame history explorer. `gitlens.showFileHistory` - opens the file history explorer. `gitlens.diffWithPrevious` - compares the current committed file with the previous commit. `gitlens.showQuickCommitDetails` - shows a commit details quick pick. `gitlens.showQuickCommitFileDetails` - shows a commit file details quick pick. `gitlens.showQuickFileHistory` - shows a file history quick pick. `gitlens.showQuickRepoHistory` - shows a branch history quick pick
|
||||||
|
|`gitlens.codeLens.recentChange.enabled`|Specifies whether the recent change code lens is shown
|
||||||
|
|`gitlens.codeLens.recentChange.command`|"Specifies the command executed when the recent change code lens is clicked. `gitlens.toggleBlame` - toggles blame annotations. `gitlens.showBlameHistory` - opens the blame history explorer. `gitlens.showFileHistory` - opens the file history explorer. `gitlens.diffWithPrevious` - compares the current committed file with the previous commit. `gitlens.showQuickCommitDetails` - shows a commit details quick pick. `gitlens.showQuickCommitFileDetails` - shows a commit file details quick pick. `gitlens.showQuickFileHistory` - shows a file history quick pick. `gitlens.showQuickRepoHistory` - shows a branch history quick pick
|
||||||
|
|`gitlens.codeLens.location`|Specifies where code lens will be rendered in the active document. `all` - render at the top of the document, on container-like (classes, modules, etc), and on member-like (methods, functions, properties, etc) lines. `document+containers` - render at the top of the document and on container-like lines. `document` - only render at the top of the document. `custom` - rendering controlled by `gitlens.codeLens.locationCustomSymbols`
|
||||||
|
|`gitlens.codeLens.locationCustomSymbols`|Specifies the set of document symbols to render active document code lens on. Must be a member of `SymbolKind`
|
||||||
|
|`gitlens.codeLens.languageLocations`|Specifies where code lens will be rendered in the active document for the specified languages
|
||||||
|
|`gitlens.menus.diff.enabled`|Specifies whether diff commands will be added to the context menus
|
||||||
|
|`gitlens.statusBar.enabled`|Specifies whether blame information is shown in the status bar
|
||||||
|
|`gitlens.statusBar.alignment`|Specifies the blame alignment in the status bar. `left` - align to the left, `right` - align to the right
|
||||||
|
|`gitlens.statusBar.command`|"Specifies the command executed when the blame status bar item is clicked. `gitlens.toggleBlame` - toggles blame annotations. `gitlens.showBlameHistory` - opens the blame history explorer. `gitlens.showFileHistory` - opens the file history explorer. `gitlens.diffWithPrevious` - compares the current committed file with the previous commit. `gitlens.toggleCodeLens` - toggles Git code lens. `gitlens.showQuickCommitDetails` - shows a commit details quick pick. `gitlens.showQuickCommitFileDetails` - shows a commit file details quick pick. `gitlens.showQuickFileHistory` - shows a file history quick pick. `gitlens.showQuickRepoHistory` - shows a branch history quick pick
|
||||||
|
|`gitlens.statusBar.date`|Specifies whether and how the commit date will be shown in the blame status bar. `off` - no date. `relative` - relative date (e.g. 1 day ago). `absolute` - date format specified by `gitlens.statusBar.dateFormat`
|
||||||
|
|`gitlens.statusBar.dateFormat`|Specifies the date format of how absolute dates will be shown in the blame status bar. See https://momentjs.com/docs/#/displaying/format/ for valid formats
|
||||||
|
|
||||||
|
## Known Issues
|
||||||
|
|
||||||
|
- If the `Copy to * clipboard` commands don't work on Linux -- `xclip` needs to be installed. You can install it via `sudo apt-get install xclip`
|
||||||
|
- Visible whitespace causes issues ([vscode issue #11485](https://github.com/Microsoft/vscode/issues/11485)) with the `expanded` and `compact` blame annotation styles when using a non-monospace font -- set `"gitlens.advanced.toggleWhitespace.enabled": true` if you are using a non-monospace font
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 24 KiB |
6
images/blame-dark.svg
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="18px" height="18px" viewBox="0 0 18 18" xml:space="preserve">
|
||||||
|
<g>
|
||||||
|
<rect fill="#FFFFFF" fill-opacity="0.75" x="1" y="0" width="4" height="18"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 313 B |
|
Before Width: | Height: | Size: 24 KiB |
6
images/blame-light.svg
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="18px" height="18px" viewBox="0 0 18 18" xml:space="preserve">
|
||||||
|
<g>
|
||||||
|
<rect fill="#000000" fill-opacity="0.75" x="1" y="0" width="4" height="18"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 314 B |
10
images/git-icon-dark.svg
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 93 93" xml:space="preserve">
|
||||||
|
<g>
|
||||||
|
<path fill="#FFFFFF" fill-opacity="0.74" d="M90,41.8L49.9,1.7c-2.3-2.3-6.1-2.3-8.4,0L33.2,10l10.6,10.6c2.5-0.8,5.3-0.3,7.2,1.7c2,2,2.5,4.8,1.7,7.3
|
||||||
|
l10.2,10.2c2.5-0.8,5.3-0.3,7.3,1.7c2.8,2.7,2.8,7.2,0,10s-7.2,2.8-10,0c-2.1-2.1-2.6-5.1-1.5-7.7l-9.5-9.5v25
|
||||||
|
c0.7,0.3,1.3,0.8,1.9,1.3c2.8,2.7,2.8,7.2,0,10c-2.8,2.7-7.2,2.7-10,0c-2.8-2.8-2.8-7.2,0-10c0.7-0.7,1.5-1.2,2.3-1.5V33.8
|
||||||
|
c-0.8-0.3-1.6-0.9-2.3-1.5c-2.1-2.1-2.6-5.1-1.5-7.7L29.2,14.2L1.7,41.7c-2.3,2.3-2.3,6.1,0,8.4l40.1,40.1c2.3,2.3,6.1,2.3,8.4,0
|
||||||
|
l39.9-39.9C92.4,47.9,92.4,44.1,90,41.8z"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 815 B |
10
images/git-icon-light.svg
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 93 93" xml:space="preserve">
|
||||||
|
<g>
|
||||||
|
<path fill="#000000" fill-opacity="0.6" d="M90,41.8L49.9,1.7c-2.3-2.3-6.1-2.3-8.4,0L33.2,10l10.6,10.6c2.5-0.8,5.3-0.3,7.2,1.7c2,2,2.5,4.8,1.7,7.3
|
||||||
|
l10.2,10.2c2.5-0.8,5.3-0.3,7.3,1.7c2.8,2.7,2.8,7.2,0,10s-7.2,2.8-10,0c-2.1-2.1-2.6-5.1-1.5-7.7l-9.5-9.5v25
|
||||||
|
c0.7,0.3,1.3,0.8,1.9,1.3c2.8,2.7,2.8,7.2,0,10c-2.8,2.7-7.2,2.7-10,0c-2.8-2.8-2.8-7.2,0-10c0.7-0.7,1.5-1.2,2.3-1.5V33.8
|
||||||
|
c-0.8-0.3-1.6-0.9-2.3-1.5c-2.1-2.1-2.6-5.1-1.5-7.7L29.2,14.2L1.7,41.7c-2.3,2.3-2.3,6.1,0,8.4l40.1,40.1c2.3,2.3,6.1,2.3,8.4,0
|
||||||
|
l39.9-39.9C92.4,47.9,92.4,44.1,90,41.8z"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 814 B |
10
images/git-icon.svg
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 93 93" xml:space="preserve">
|
||||||
|
<g>
|
||||||
|
<path fill="#F05133" d="M90,41.8L49.9,1.7c-2.3-2.3-6.1-2.3-8.4,0L33.2,10l10.6,10.6c2.5-0.8,5.3-0.3,7.2,1.7c2,2,2.5,4.8,1.7,7.3
|
||||||
|
l10.2,10.2c2.5-0.8,5.3-0.3,7.3,1.7c2.8,2.7,2.8,7.2,0,10s-7.2,2.8-10,0c-2.1-2.1-2.6-5.1-1.5-7.7l-9.5-9.5v25
|
||||||
|
c0.7,0.3,1.3,0.8,1.9,1.3c2.8,2.7,2.8,7.2,0,10c-2.8,2.7-7.2,2.7-10,0c-2.8-2.8-2.8-7.2,0-10c0.7-0.7,1.5-1.2,2.3-1.5V33.8
|
||||||
|
c-0.8-0.3-1.6-0.9-2.3-1.5c-2.1-2.1-2.6-5.1-1.5-7.7L29.2,14.2L1.7,41.7c-2.3,2.3-2.3,6.1,0,8.4l40.1,40.1c2.3,2.3,6.1,2.3,8.4,0
|
||||||
|
l39.9-39.9C92.4,47.9,92.4,44.1,90,41.8z"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 795 B |
18
images/gitlens-icon.svg
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 93 93" style="enable-background:new 0 0 93 93;" xml:space="preserve">
|
||||||
|
<g>
|
||||||
|
<path fill="#F05133" d="M90,41.8L49.9,1.7c-2.3-2.3-6.1-2.3-8.4,0L33.2,10l10.6,10.6c2.5-0.8,5.3-0.3,7.2,1.7c2,2,2.5,4.8,1.7,7.3
|
||||||
|
l10.2,10.2c2.5-0.8,5.3-0.3,7.3,1.7c2.8,2.7,2.8,7.2,0,10s-7.2,2.8-10,0c-2.1-2.1-2.6-5.1-1.5-7.7l-9.5-9.5v25
|
||||||
|
c0.7,0.3,1.3,0.8,1.9,1.3c2.8,2.7,2.8,7.2,0,10c-2.8,2.7-7.2,2.7-10,0c-2.8-2.8-2.8-7.2,0-10c0.7-0.7,1.5-1.2,2.3-1.5V33.8
|
||||||
|
c-0.8-0.3-1.6-0.9-2.3-1.5c-2.1-2.1-2.6-5.1-1.5-7.7L29.2,14.2L1.7,41.7c-2.3,2.3-2.3,6.1,0,8.4l40.1,40.1c2.3,2.3,6.1,2.3,8.4,0
|
||||||
|
l39.9-39.9C92.4,47.9,92.4,44.1,90,41.8z"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<path fill="#FFFFFF" d="M38.1,56.9l-9-9c1.2-1.6,1.9-3.5,1.9-5.6c0-5.2-4.2-9.4-9.4-9.4s-9.4,4.2-9.4,9.4s4.2,9.4,9.4,9.4
|
||||||
|
c2.1,0,4.1-0.7,5.6-1.9l9,9c0.3,0.3,0.6,0.4,0.9,0.4c0.3,0,0.7-0.1,0.9-0.4C38.6,58.2,38.6,57.4,38.1,56.9z M21.6,49.7
|
||||||
|
c-4.1,0-7.5-3.4-7.5-7.5s3.4-7.5,7.5-7.5s7.5,3.4,7.5,7.5S25.7,49.7,21.6,49.7z"/>
|
||||||
|
<path fill="#FFFFFF" d="M27,41.7c0.1,0,0.2,0,0.2,0c0.5-0.1,0.8-0.6,0.7-1.2c-0.5-1.7-1.5-3.1-3.1-4c-1.5-0.9-3.3-1.1-5-0.7
|
||||||
|
c-0.5,0.1-0.8,0.6-0.7,1.2c0.1,0.5,0.6,0.8,1.2,0.7c1.2-0.3,2.5-0.2,3.6,0.5c1.1,0.6,1.9,1.6,2.2,2.9C26.2,41.4,26.6,41.7,27,41.7z"/>
|
||||||
|
<circle fill="#FFFFFF" cx="27.1" cy="43.9" r="0.9"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.5 KiB |
BIN
images/gitlens-preview1.gif
Normal file
|
After Width: | Height: | Size: 4.8 MiB |
BIN
images/gitlens-preview2.gif
Normal file
|
After Width: | Height: | Size: 6.6 MiB |
|
Before Width: | Height: | Size: 185 KiB |
|
Before Width: | Height: | Size: 124 KiB |
BIN
images/screenshot-branch-history.png
Normal file
|
After Width: | Height: | Size: 98 KiB |
BIN
images/screenshot-commit-details.png
Normal file
|
After Width: | Height: | Size: 67 KiB |
BIN
images/screenshot-commit-file-details.png
Normal file
|
After Width: | Height: | Size: 75 KiB |
BIN
images/screenshot-file-history.png
Normal file
|
After Width: | Height: | Size: 80 KiB |
BIN
images/screenshot-repo-status.png
Normal file
|
After Width: | Height: | Size: 54 KiB |
BIN
images/screenshot-stash-details.png
Normal file
|
After Width: | Height: | Size: 57 KiB |
BIN
images/screenshot-stash-list.png
Normal file
|
After Width: | Height: | Size: 25 KiB |
924
package.json
@@ -1,49 +1,925 @@
|
|||||||
{
|
{
|
||||||
"name": "gitlens",
|
"name": "gitlens",
|
||||||
"version": "0.0.3",
|
"version": "3.5.0-beta",
|
||||||
"author": "Eric Amodio",
|
"author": {
|
||||||
|
"name": "Eric Amodio",
|
||||||
|
"email": "eamodio@gmail.com"
|
||||||
|
},
|
||||||
"publisher": "eamodio",
|
"publisher": "eamodio",
|
||||||
"engines": {
|
"engines": {
|
||||||
"vscode": "^1.3.0"
|
"vscode": "^1.12.0"
|
||||||
},
|
},
|
||||||
"license": "SEE LICENSE IN LICENSE",
|
"license": "SEE LICENSE IN LICENSE",
|
||||||
"displayName": "GitLens",
|
"displayName": "Git Lens \u2014 git blame annotations, code lens, and more",
|
||||||
"description": "Provides Git blame information in CodeLens",
|
"description": "Supercharge Visual Studio Code's Git capabilities \u2014 Visualize code authorship at a glance via inline Git blame annotations and code lens, seamlessly navigate and explore the history of a file or branch, gain valuable insights via powerful comparision commands, and so much more",
|
||||||
|
"badges": [
|
||||||
|
{
|
||||||
|
"url": "https://badges.gitter.im/vscode-gitlens/Lobby.svg",
|
||||||
|
"href": "https://gitter.im/vscode-gitlens/Lobby",
|
||||||
|
"description": "Chat at https://gitter.im/vscode-gitlens/Lobby"
|
||||||
|
}
|
||||||
|
],
|
||||||
"categories": [
|
"categories": [
|
||||||
"Other"
|
"Other"
|
||||||
],
|
],
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"git", "gitblame", "blame", "codelens"
|
"git",
|
||||||
|
"code lens",
|
||||||
|
"blame",
|
||||||
|
"history",
|
||||||
|
"annotation",
|
||||||
|
"log",
|
||||||
|
"inline blame",
|
||||||
|
"compare",
|
||||||
|
"diff"
|
||||||
],
|
],
|
||||||
"galleryBanner": {
|
"galleryBanner": {
|
||||||
"color": "#0000FF",
|
"color": "#56098c",
|
||||||
"theme": "dark"
|
"theme": "dark"
|
||||||
},
|
},
|
||||||
"preview": true,
|
"icon": "images/gitlens-icon.svg",
|
||||||
|
"preview": false,
|
||||||
|
"homepage": "https://github.com/eamodio/vscode-gitlens/blob/master/README.md",
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/eamodio/vscode-gitlens/issues"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/eamodio/vscode-gitlens.git"
|
||||||
|
},
|
||||||
"main": "./out/src/extension",
|
"main": "./out/src/extension",
|
||||||
"contributes": {
|
"contributes": {
|
||||||
"commands": [{
|
"configuration": {
|
||||||
"command": "git.action.showBlameHistory",
|
"type": "object",
|
||||||
"title": "Show Blame History",
|
"title": "GitLens configuration",
|
||||||
"category": "Git"
|
"properties": {
|
||||||
}]
|
"gitlens.debug": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false,
|
||||||
|
"description": "Specifies debug mode"
|
||||||
|
},
|
||||||
|
"gitlens.insiders": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false,
|
||||||
|
"description": "Specifies whether or not to enable new experimental features (expect there to be issues)"
|
||||||
|
},
|
||||||
|
"gitlens.outputLevel": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "silent",
|
||||||
|
"enum": [
|
||||||
|
"silent",
|
||||||
|
"errors",
|
||||||
|
"verbose"
|
||||||
|
],
|
||||||
|
"description": "Specifies how much (if any) output will be sent to the GitLens output channel"
|
||||||
|
},
|
||||||
|
"gitlens.blame.annotation.activeLine": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "both",
|
||||||
|
"enum": [
|
||||||
|
"off",
|
||||||
|
"inline",
|
||||||
|
"hover",
|
||||||
|
"both"
|
||||||
|
],
|
||||||
|
"description": "Specifies whether and how to show blame annotations on the active line. `off` - no annotation. `inline` - adds a trailing annotation to the active line. `hover` - adds hover annotation to the active line. `both` - adds both `inline` and `hover` annotations"
|
||||||
|
},
|
||||||
|
"gitlens.blame.annotation.activeLineDarkColor": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "rgba(153, 153, 153, 0.35)",
|
||||||
|
"description": "Specifies the color of the active line blame annotation to use with a dark theme. Must be a valid css color"
|
||||||
|
},
|
||||||
|
"gitlens.blame.annotation.activeLineLightColor": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "rgba(153, 153, 153, 0.35)",
|
||||||
|
"description": "Specifies the color of the active line blame annotation to use with a light theme. Must be a valid css color"
|
||||||
|
},
|
||||||
|
"gitlens.blame.annotation.highlight": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "both",
|
||||||
|
"enum": [
|
||||||
|
"none",
|
||||||
|
"gutter",
|
||||||
|
"line",
|
||||||
|
"both"
|
||||||
|
],
|
||||||
|
"description": "Specifies whether and how to highlight blame annotations. `none` - no highlight. `gutter` - adds a gutter icon. `line` - adds a full-line highlight. `both` - adds both `gutter` and `line` highlights"
|
||||||
|
},
|
||||||
|
"gitlens.blame.annotation.style": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "expanded",
|
||||||
|
"enum": [
|
||||||
|
"compact",
|
||||||
|
"expanded",
|
||||||
|
"trailing"
|
||||||
|
],
|
||||||
|
"description": "Specifies the style of the blame annotations. `compact` - groups annotations to limit the repetition and also adds author and date when possible. `expanded` - shows an annotation before every line. `trailing` - shows an annotation after every line"
|
||||||
|
},
|
||||||
|
"gitlens.blame.annotation.author": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true,
|
||||||
|
"description": "Specifies whether the committer will be shown in the blame annotations. Applies only to the `expanded` & `trailing` annotation styles"
|
||||||
|
},
|
||||||
|
"gitlens.blame.annotation.date": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "off",
|
||||||
|
"enum": [
|
||||||
|
"off",
|
||||||
|
"relative",
|
||||||
|
"absolute"
|
||||||
|
],
|
||||||
|
"description": "Specifies whether and how the commit date will be shown in the blame annotations. `off` - no date. `relative` - relative date (e.g. 1 day ago). `absolute` - date format specified by `gitlens.blame.annotation.dateFormat`. Applies only to the `expanded` & `trailing` annotation styles"
|
||||||
|
},
|
||||||
|
"gitlens.blame.annotation.dateFormat": {
|
||||||
|
"type": "string",
|
||||||
|
"default": null,
|
||||||
|
"description": "Specifies the date format of how absolute dates will be shown in the blame annotations. See https://momentjs.com/docs/#/displaying/format/ for valid formats"
|
||||||
|
},
|
||||||
|
"gitlens.blame.annotation.message": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false,
|
||||||
|
"description": "Specifies whether the commit message will be shown in the blame annotations. Applies only to the `expanded` & `trailing` annotation styles"
|
||||||
|
},
|
||||||
|
"gitlens.blame.annotation.sha": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true,
|
||||||
|
"description": "Specifies whether the commit id (sha) will be shown in the blame annotations. Applies only to the `expanded` & `trailing` annotation styles"
|
||||||
|
},
|
||||||
|
"gitlens.codeLens.visibility": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "auto",
|
||||||
|
"enum": [
|
||||||
|
"auto",
|
||||||
|
"ondemand",
|
||||||
|
"off"
|
||||||
|
],
|
||||||
|
"description": "Specifies when code lens will be shown in the active document. `auto` - always shown. `ondemand` - never shown, unless toggled via the `gitlens.toggleCodeLens` command. `off` - never shown"
|
||||||
|
},
|
||||||
|
"gitlens.codeLens.authors.enabled": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true,
|
||||||
|
"description": "Specifies whether the authors code lens is shown"
|
||||||
|
},
|
||||||
|
"gitlens.codeLens.authors.command": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "gitlens.toggleBlame",
|
||||||
|
"enum": [
|
||||||
|
"gitlens.toggleBlame",
|
||||||
|
"gitlens.showBlameHistory",
|
||||||
|
"gitlens.showFileHistory",
|
||||||
|
"gitlens.diffWithPrevious",
|
||||||
|
"gitlens.showQuickCommitDetails",
|
||||||
|
"gitlens.showQuickCommitFileDetails",
|
||||||
|
"gitlens.showQuickFileHistory",
|
||||||
|
"gitlens.showQuickRepoHistory"
|
||||||
|
],
|
||||||
|
"description": "Specifies the command executed when the authors code lens is clicked. `gitlens.toggleBlame` - toggles blame annotations. `gitlens.showBlameHistory` - opens the blame history explorer. `gitlens.showFileHistory` - opens the file history explorer. `gitlens.diffWithPrevious` - compares the current committed file with the previous commit. `gitlens.showQuickCommitDetails` - shows a commit details quick pick. `gitlens.showQuickCommitFileDetails` - shows a commit file details quick pick. `gitlens.showQuickFileHistory` - shows a file history quick pick. `gitlens.showQuickRepoHistory` - shows a branch history quick pick"
|
||||||
|
},
|
||||||
|
"gitlens.codeLens.recentChange.enabled": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true,
|
||||||
|
"description": "Specifies whether the recent change code lens is shown"
|
||||||
|
},
|
||||||
|
"gitlens.codeLens.recentChange.command": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "gitlens.showQuickCommitFileDetails",
|
||||||
|
"enum": [
|
||||||
|
"gitlens.toggleBlame",
|
||||||
|
"gitlens.showBlameHistory",
|
||||||
|
"gitlens.showFileHistory",
|
||||||
|
"gitlens.diffWithPrevious",
|
||||||
|
"gitlens.showQuickCommitDetails",
|
||||||
|
"gitlens.showQuickCommitFileDetails",
|
||||||
|
"gitlens.showQuickFileHistory",
|
||||||
|
"gitlens.showQuickRepoHistory"
|
||||||
|
],
|
||||||
|
"description": "Specifies the command executed when the recent change code lens is clicked. `gitlens.toggleBlame` - toggles blame annotations. `gitlens.showBlameHistory` - opens the blame history explorer. `gitlens.showFileHistory` - opens the file history explorer. `gitlens.diffWithPrevious` - compares the current committed file with the previous commit. `gitlens.showQuickCommitDetails` - shows a commit details quick pick. `gitlens.showQuickCommitFileDetails` - shows a commit file details quick pick. `gitlens.showQuickFileHistory` - shows a file history quick pick. `gitlens.showQuickRepoHistory` - shows a branch history quick pick"
|
||||||
|
},
|
||||||
|
"gitlens.codeLens.location": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "document+containers",
|
||||||
|
"enum": [
|
||||||
|
"all",
|
||||||
|
"document+containers",
|
||||||
|
"document",
|
||||||
|
"custom"
|
||||||
|
],
|
||||||
|
"description": "Specifies where code lens will be rendered in the active document. `all` - render at the top of the document, on container-like (classes, modules, etc), and on member-like (methods, functions, properties, etc) lines. `document+containers` - render at the top of the document and on container-like lines. `document` - only render at the top of the document. `custom` - rendering controlled by `gitlens.codeLens.locationCustomSymbols`"
|
||||||
|
},
|
||||||
|
"gitlens.codeLens.locationCustomSymbols": {
|
||||||
|
"type": "array",
|
||||||
|
"description": "Specifies the set of document symbols to render active document code lens on. Must be a member of `SymbolKind`"
|
||||||
|
},
|
||||||
|
"gitlens.codeLens.languageLocations": {
|
||||||
|
"type": "array",
|
||||||
|
"default": [
|
||||||
|
{
|
||||||
|
"language": "css",
|
||||||
|
"location": "document"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"language": "html",
|
||||||
|
"location": "document"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"language": "json",
|
||||||
|
"location": "document"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"language": "less",
|
||||||
|
"location": "document"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"language": "scss",
|
||||||
|
"location": "document"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"language": "vue",
|
||||||
|
"location": "document"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"language",
|
||||||
|
"location"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"language": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Specifies the language to which this code lens override applies"
|
||||||
|
},
|
||||||
|
"location": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "document+containers",
|
||||||
|
"enum": [
|
||||||
|
"all",
|
||||||
|
"document+containers",
|
||||||
|
"document",
|
||||||
|
"custom",
|
||||||
|
"none"
|
||||||
|
],
|
||||||
|
"description": "Specifies where code lens will be rendered in the active document for the specified language. `all` - render at the top of the document, on container-like (classes, modules, etc), and on member-like (methods, functions, properties, etc) lines. `document+containers` - render at the top of the document and on container-like lines. `document` - only render at the top of the document. `custom` - rendering controlled by `customSymbols`"
|
||||||
|
},
|
||||||
|
"customSymbols": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Specifies the set of document symbols to render active document code lens on. Must be a member of `SymbolKind`"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"uniqueItems": true,
|
||||||
|
"enum": [
|
||||||
|
"all",
|
||||||
|
"document+containers",
|
||||||
|
"document",
|
||||||
|
"custom"
|
||||||
|
],
|
||||||
|
"description": "Specifies where code lens will be rendered in the active document for the specified languages"
|
||||||
|
},
|
||||||
|
"gitlens.codeLens.debug": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false,
|
||||||
|
"description": "Specifies whether or not to show debug information in code lens"
|
||||||
|
},
|
||||||
|
"gitlens.menus.diff.enabled": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true,
|
||||||
|
"description": "Specifies whether diff commands will be added to the context menus"
|
||||||
|
},
|
||||||
|
"gitlens.statusBar.enabled": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true,
|
||||||
|
"description": "Specifies whether blame information is shown in the status bar"
|
||||||
|
},
|
||||||
|
"gitlens.statusBar.alignment": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "right",
|
||||||
|
"enum": [
|
||||||
|
"left",
|
||||||
|
"right"
|
||||||
|
],
|
||||||
|
"description": "Specifies the blame alignment in the status bar. `left` - align to the left, `right` - align to the right"
|
||||||
|
},
|
||||||
|
"gitlens.statusBar.command": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "gitlens.showQuickCommitDetails",
|
||||||
|
"enum": [
|
||||||
|
"gitlens.toggleBlame",
|
||||||
|
"gitlens.showBlameHistory",
|
||||||
|
"gitlens.showFileHistory",
|
||||||
|
"gitlens.diffWithPrevious",
|
||||||
|
"gitlens.toggleCodeLens",
|
||||||
|
"gitlens.showQuickCommitDetails",
|
||||||
|
"gitlens.showQuickCommitFileDetails",
|
||||||
|
"gitlens.showQuickFileHistory",
|
||||||
|
"gitlens.showQuickRepoHistory"
|
||||||
|
],
|
||||||
|
"description": "Specifies the command executed when the blame status bar item is clicked. `gitlens.toggleBlame` - toggles blame annotations. `gitlens.showBlameHistory` - opens the blame history explorer. `gitlens.showFileHistory` - opens the file history explorer. `gitlens.diffWithPrevious` - compares the current committed file with the previous commit. `gitlens.toggleCodeLens` - toggles Git code lens. `gitlens.showQuickCommitDetails` - shows a commit details quick pick. `gitlens.showQuickCommitFileDetails` - shows a commit file details quick pick. `gitlens.showQuickFileHistory` - shows a file history quick pick. `gitlens.showQuickRepoHistory` - shows a branch history quick pick"
|
||||||
|
},
|
||||||
|
"gitlens.statusBar.date": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "relative",
|
||||||
|
"enum": [
|
||||||
|
"off",
|
||||||
|
"relative",
|
||||||
|
"absolute"
|
||||||
|
],
|
||||||
|
"description": "Specifies whether and how the commit date will be shown in the blame status bar. `off` - no date. `relative` - relative date (e.g. 1 day ago). `absolute` - date format specified by `gitlens.statusBar.dateFormat`"
|
||||||
|
},
|
||||||
|
"gitlens.statusBar.dateFormat": {
|
||||||
|
"type": "string",
|
||||||
|
"default": null,
|
||||||
|
"description": "Specifies the date format of how absolute dates will be shown in the blame status bar. See https://momentjs.com/docs/#/displaying/format/ for valid formats"
|
||||||
|
},
|
||||||
|
"gitlens.advanced.caching.enabled": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true,
|
||||||
|
"description": "Specifies whether git blame output will be cached"
|
||||||
|
},
|
||||||
|
"gitlens.advanced.caching.statusBar.maxLines": {
|
||||||
|
"type": "number",
|
||||||
|
"default": 0,
|
||||||
|
"description": "Specifies whether status bar git blame output will be cached for larger documents"
|
||||||
|
},
|
||||||
|
"gitlens.advanced.git": {
|
||||||
|
"type": "string",
|
||||||
|
"default": null,
|
||||||
|
"description": "Specifies a git path to use"
|
||||||
|
},
|
||||||
|
"gitlens.advanced.gitignore.enabled": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true,
|
||||||
|
"description": "Specifies whether or not to parse the root .gitignore file for better performance (i.e. avoids blaming excluded files)"
|
||||||
|
},
|
||||||
|
"gitlens.advanced.maxQuickHistory": {
|
||||||
|
"type": "number",
|
||||||
|
"default": 200,
|
||||||
|
"description": "Specifies the maximum number of QuickPick history entries to show"
|
||||||
|
},
|
||||||
|
"gitlens.advanced.quickPick.closeOnFocusOut": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true,
|
||||||
|
"description": "Specifies whether or not to close the QuickPick menu when focus is lost"
|
||||||
|
},
|
||||||
|
"gitlens.advanced.toggleWhitespace.enabled": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false,
|
||||||
|
"description": "Specifies whether or not to toggle whitespace off then showing blame annotations (*may* be required by certain fonts/themes)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"commands": [
|
||||||
|
{
|
||||||
|
"command": "gitlens.diffDirectory",
|
||||||
|
"title": "Directory Compare",
|
||||||
|
"category": "GitLens"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "gitlens.diffWithBranch",
|
||||||
|
"title": "Compare File with...",
|
||||||
|
"category": "GitLens"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "gitlens.diffWithNext",
|
||||||
|
"title": "Compare File with Next Commit",
|
||||||
|
"category": "GitLens"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "gitlens.diffWithPrevious",
|
||||||
|
"title": "Compare File with Previous Commit",
|
||||||
|
"category": "GitLens"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "gitlens.diffLineWithPrevious",
|
||||||
|
"title": "Compare Line with Previous Commit",
|
||||||
|
"category": "GitLens"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "gitlens.diffWithWorking",
|
||||||
|
"title": "Compare File with Working Tree",
|
||||||
|
"category": "GitLens"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "gitlens.diffLineWithWorking",
|
||||||
|
"title": "Compare Line with Working Tree",
|
||||||
|
"category": "GitLens"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "gitlens.showBlame",
|
||||||
|
"title": "Show Blame Annotations",
|
||||||
|
"category": "GitLens"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "gitlens.toggleBlame",
|
||||||
|
"title": "Toggle Blame Annotations",
|
||||||
|
"category": "GitLens",
|
||||||
|
"icon": {
|
||||||
|
"dark": "images/git-icon-dark.svg",
|
||||||
|
"light": "images/git-icon-light.svg"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "gitlens.toggleCodeLens",
|
||||||
|
"title": "Toggle Git Code Lens",
|
||||||
|
"category": "GitLens"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "gitlens.showBlameHistory",
|
||||||
|
"title": "Open Blame History Explorer",
|
||||||
|
"category": "GitLens"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "gitlens.showCommitSearch",
|
||||||
|
"title": "Search Commits",
|
||||||
|
"category": "GitLens"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "gitlens.showFileHistory",
|
||||||
|
"title": "Open File History Explorer",
|
||||||
|
"category": "GitLens"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "gitlens.showLastQuickPick",
|
||||||
|
"title": "Show Last Opened Quick Pick",
|
||||||
|
"category": "GitLens"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "gitlens.showQuickCommitDetails",
|
||||||
|
"title": "Show Commit Details",
|
||||||
|
"category": "GitLens"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "gitlens.showQuickCommitFileDetails",
|
||||||
|
"title": "Show Line Commit Details",
|
||||||
|
"category": "GitLens"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "gitlens.showQuickFileHistory",
|
||||||
|
"title": "Show File History",
|
||||||
|
"category": "GitLens"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "gitlens.showQuickBranchHistory",
|
||||||
|
"title": "Show Branch History",
|
||||||
|
"category": "GitLens"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "gitlens.showQuickRepoHistory",
|
||||||
|
"title": "Show Current Branch History",
|
||||||
|
"category": "GitLens"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "gitlens.showQuickRepoStatus",
|
||||||
|
"title": "Show Repository Status",
|
||||||
|
"category": "GitLens"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "gitlens.showQuickStashList",
|
||||||
|
"title": "Show Stashed Changes",
|
||||||
|
"category": "GitLens"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "gitlens.copyShaToClipboard",
|
||||||
|
"title": "Copy Commit ID to Clipboard",
|
||||||
|
"category": "GitLens"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "gitlens.copyMessageToClipboard",
|
||||||
|
"title": "Copy Commit Message to Clipboard",
|
||||||
|
"category": "GitLens"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "gitlens.closeUnchangedFiles",
|
||||||
|
"title": "Close Unchanged Files",
|
||||||
|
"category": "GitLens"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "gitlens.openChangedFiles",
|
||||||
|
"title": "Open Changed Files",
|
||||||
|
"category": "GitLens"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "gitlens.openBranchInRemote",
|
||||||
|
"title": "Open Branch in Remote",
|
||||||
|
"category": "GitLens"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "gitlens.openCommitInRemote",
|
||||||
|
"title": "Open Line Commit in Remote",
|
||||||
|
"category": "GitLens"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "gitlens.openFileInRemote",
|
||||||
|
"title": "Open File in Remote",
|
||||||
|
"category": "GitLens"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "gitlens.openRepoInRemote",
|
||||||
|
"title": "Open Repository in Remote",
|
||||||
|
"category": "GitLens"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "gitlens.stashApply",
|
||||||
|
"title": "Apply Stashed Changes",
|
||||||
|
"category": "GitLens"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "gitlens.stashSave",
|
||||||
|
"title": "Stash Changes",
|
||||||
|
"category": "GitLens"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"menus": {
|
||||||
|
"commandPalette": [
|
||||||
|
{
|
||||||
|
"command": "gitlens.diffDirectory",
|
||||||
|
"when": "gitlens:enabled"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "gitlens.diffWithBranch",
|
||||||
|
"when": "gitlens:isTracked"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "gitlens.diffWithNext",
|
||||||
|
"when": "gitlens:isTracked"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "gitlens.diffWithPrevious",
|
||||||
|
"when": "gitlens:isTracked"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "gitlens.diffLineWithPrevious",
|
||||||
|
"when": "gitlens:isBlameable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "gitlens.diffWithWorking",
|
||||||
|
"when": "gitlens:isTracked"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "gitlens.diffLineWithWorking",
|
||||||
|
"when": "gitlens:isBlameable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "gitlens.showBlame",
|
||||||
|
"when": "gitlens:isBlameable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "gitlens.toggleBlame",
|
||||||
|
"when": "gitlens:isBlameable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "gitlens.toggleCodeLens",
|
||||||
|
"when": "gitlens:isTracked && gitlens:canToggleCodeLens"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "gitlens.showBlameHistory",
|
||||||
|
"when": "gitlens:isBlameable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "gitlens.showFileHistory",
|
||||||
|
"when": "gitlens:isTracked"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "gitlens.showLastQuickPick",
|
||||||
|
"when": "gitlens:enabled"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "gitlens.showQuickCommitDetails",
|
||||||
|
"when": "gitlens:isBlameable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "gitlens.showQuickCommitFileDetails",
|
||||||
|
"when": "gitlens:isBlameable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "gitlens.showQuickFileHistory",
|
||||||
|
"when": "gitlens:isTracked"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "gitlens.showQuickBranchHistory",
|
||||||
|
"when": "gitlens:enabled"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "gitlens.showQuickRepoHistory",
|
||||||
|
"when": "gitlens:enabled"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "gitlens.showQuickRepoStatus",
|
||||||
|
"when": "gitlens:enabled"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "gitlens.showQuickStashList",
|
||||||
|
"when": "gitlens:enabled"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "gitlens.copyShaToClipboard",
|
||||||
|
"when": "gitlens:isBlameable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "gitlens.copyMessageToClipboard",
|
||||||
|
"when": "gitlens:isBlameable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "gitlens.closeUnchangedFiles",
|
||||||
|
"when": "gitlens:enabled"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "gitlens.openChangedFiles",
|
||||||
|
"when": "gitlens:enabled"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "gitlens.openBranchInRemote",
|
||||||
|
"when": "gitlens:hasRemotes"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "gitlens.openCommitInRemote",
|
||||||
|
"when": "gitlens:isBlameable && gitlens:hasRemotes"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "gitlens.openFileInRemote",
|
||||||
|
"when": "gitlens:isTracked && gitlens:hasRemotes"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "gitlens.openRepoInRemote",
|
||||||
|
"when": "gitlens:hasRemotes"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "gitlens.stashApply",
|
||||||
|
"when": "gitlens:enabled && config.gitlens.insiders"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "gitlens.stashSave",
|
||||||
|
"when": "gitlens:enabled && config.gitlens.insiders"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"explorer/context": [
|
||||||
|
{
|
||||||
|
"command": "gitlens.diffWithPrevious",
|
||||||
|
"when": "gitlens:enabled && config.gitlens.menus.diff.enabled",
|
||||||
|
"group": "1_gitlens@1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "gitlens.diffWithWorking",
|
||||||
|
"when": "gitlens:enabled && config.gitlens.menus.diff.enabled",
|
||||||
|
"group": "1_gitlens@2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "gitlens.showQuickFileHistory",
|
||||||
|
"when": "gitlens:enabled",
|
||||||
|
"group": "1_gitlens_1@1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "gitlens.openFileInRemote",
|
||||||
|
"when": "gitlens:enabled",
|
||||||
|
"group": "1_gitlens_1@2"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"editor/title": [
|
||||||
|
{
|
||||||
|
"command": "gitlens.toggleBlame",
|
||||||
|
"when": "gitlens:isBlameable",
|
||||||
|
"group": "navigation@100"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "gitlens.diffWithPrevious",
|
||||||
|
"when": "editorTextFocus && gitlens:isTracked && config.gitlens.menus.diff.enabled",
|
||||||
|
"group": "2_gitlens"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "gitlens.diffWithWorking",
|
||||||
|
"when": "editorTextFocus && gitlens:isTracked && config.gitlens.menus.diff.enabled",
|
||||||
|
"group": "2_gitlens"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "gitlens.showQuickFileHistory",
|
||||||
|
"when": "editorFocus && gitlens:isTracked",
|
||||||
|
"group": "2_gitlens_1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "gitlens.showQuickRepoHistory",
|
||||||
|
"when": "!editorFocus && gitlens:enabled",
|
||||||
|
"group": "2_gitlens_1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "gitlens.showQuickRepoStatus",
|
||||||
|
"when": "gitlens:enabled",
|
||||||
|
"group": "2_gitlens_1"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"editor/title/context": [
|
||||||
|
{
|
||||||
|
"command": "gitlens.diffWithPrevious",
|
||||||
|
"when": "gitlens:enabled && config.gitlens.menus.diff.enabled",
|
||||||
|
"group": "1_gitlens@1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "gitlens.diffWithWorking",
|
||||||
|
"when": "gitlens:enabled && config.gitlens.menus.diff.enabled",
|
||||||
|
"group": "1_gitlens@2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "gitlens.showQuickFileHistory",
|
||||||
|
"when": "gitlens:enabled",
|
||||||
|
"group": "1_gitlens_1@1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "gitlens.toggleBlame",
|
||||||
|
"when": "gitlens:enabled",
|
||||||
|
"group": "1_gitlens_1@2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "gitlens.openFileInRemote",
|
||||||
|
"when": "gitlens:enabled",
|
||||||
|
"group": "1_gitlens_1@3"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"editor/context": [
|
||||||
|
{
|
||||||
|
"command": "gitlens.diffLineWithPrevious",
|
||||||
|
"when": "editorTextFocus && gitlens:isBlameable && config.gitlens.menus.diff.enabled",
|
||||||
|
"group": "1_gitlens@1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "gitlens.diffLineWithWorking",
|
||||||
|
"when": "editorTextFocus && gitlens:isBlameable && config.gitlens.menus.diff.enabled",
|
||||||
|
"group": "1_gitlens@2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "gitlens.showQuickCommitFileDetails",
|
||||||
|
"when": "editorTextFocus && gitlens:isBlameable",
|
||||||
|
"group": "1_gitlens@3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "gitlens.diffWithPrevious",
|
||||||
|
"when": "editorTextFocus && gitlens:isTracked && config.gitlens.menus.diff.enabled",
|
||||||
|
"group": "1_gitlens_1@1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "gitlens.diffWithWorking",
|
||||||
|
"when": "editorTextFocus && gitlens:isTracked && config.gitlens.menus.diff.enabled",
|
||||||
|
"group": "1_gitlens_1@2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "gitlens.showQuickFileHistory",
|
||||||
|
"when": "gitlens:isTracked",
|
||||||
|
"group": "3_gitlens@1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "gitlens.toggleBlame",
|
||||||
|
"when": "editorTextFocus && gitlens:isBlameable",
|
||||||
|
"group": "3_gitlens@2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "gitlens.openFileInRemote",
|
||||||
|
"when": "editorTextFocus && gitlens:isTracked && gitlens:hasRemotes",
|
||||||
|
"group": "3_gitlens@3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "gitlens.copyShaToClipboard",
|
||||||
|
"when": "editorTextFocus && gitlens:isBlameable",
|
||||||
|
"group": "9_gitlens@1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "gitlens.copyMessageToClipboard",
|
||||||
|
"when": "editorTextFocus && gitlens:isBlameable",
|
||||||
|
"group": "9_gitlens@2"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"keybindings": [
|
||||||
|
{
|
||||||
|
"command": "gitlens.key.left",
|
||||||
|
"key": "alt+left",
|
||||||
|
"when": "gitlens:key:left"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "gitlens.key.right",
|
||||||
|
"key": "alt+right",
|
||||||
|
"when": "gitlens:key:right"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "gitlens.key.,",
|
||||||
|
"key": "alt+,",
|
||||||
|
"when": "gitlens:key:,"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "gitlens.key..",
|
||||||
|
"key": "alt+.",
|
||||||
|
"when": "gitlens:key:."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "gitlens.toggleBlame",
|
||||||
|
"key": "alt+b",
|
||||||
|
"mac": "alt+b",
|
||||||
|
"when": "editorTextFocus && gitlens:isTracked"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "gitlens.toggleCodeLens",
|
||||||
|
"key": "shift+alt+b",
|
||||||
|
"mac": "shift+alt+b",
|
||||||
|
"when": "editorTextFocus && gitlens:isTracked && gitlens:canToggleCodeLens"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "gitlens.showLastQuickPick",
|
||||||
|
"key": "alt+-",
|
||||||
|
"mac": "alt+-",
|
||||||
|
"when": "gitlens:enabled"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "gitlens.showCommitSearch",
|
||||||
|
"key": "alt+/",
|
||||||
|
"mac": "alt+/",
|
||||||
|
"when": "gitlens:enabled"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "gitlens.showQuickFileHistory",
|
||||||
|
"key": "alt+h",
|
||||||
|
"mac": "alt+h",
|
||||||
|
"when": "gitlens:enabled"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "gitlens.showQuickRepoHistory",
|
||||||
|
"key": "shift+alt+h",
|
||||||
|
"mac": "shift+alt+h",
|
||||||
|
"when": "gitlens:enabled"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "gitlens.showQuickRepoStatus",
|
||||||
|
"key": "alt+s",
|
||||||
|
"mac": "alt+s",
|
||||||
|
"when": "gitlens:enabled"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "gitlens.showQuickCommitFileDetails",
|
||||||
|
"key": "alt+c",
|
||||||
|
"mac": "alt+c",
|
||||||
|
"when": "editorTextFocus && gitlens:enabled"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "gitlens.diffWithNext",
|
||||||
|
"key": "alt+.",
|
||||||
|
"mac": "alt+.",
|
||||||
|
"when": "editorTextFocus && gitlens:isTracked"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "gitlens.diffLineWithPrevious",
|
||||||
|
"key": "shift+alt+,",
|
||||||
|
"mac": "shift+alt+,",
|
||||||
|
"when": "editorTextFocus && gitlens:isTracked"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "gitlens.diffWithPrevious",
|
||||||
|
"key": "alt+,",
|
||||||
|
"mac": "alt+,",
|
||||||
|
"when": "editorTextFocus && gitlens:isTracked"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "gitlens.diffLineWithWorking",
|
||||||
|
"key": "alt+w",
|
||||||
|
"mac": "alt+w",
|
||||||
|
"when": "editorTextFocus && gitlens:isTracked"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "gitlens.diffWithWorking",
|
||||||
|
"key": "shift+alt+w",
|
||||||
|
"mac": "shift+alt+w",
|
||||||
|
"when": "editorTextFocus && gitlens:isTracked"
|
||||||
|
}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"activationEvents": [
|
"activationEvents": [
|
||||||
"*"
|
"*"
|
||||||
],
|
],
|
||||||
|
"scripts": {
|
||||||
|
"compile": "npm run lint && tsc -p ./",
|
||||||
|
"watch": "tsc -watch -p ./",
|
||||||
|
"lint": "tslint --project tslint.json",
|
||||||
|
"pack": "git clean -xdf --exclude=node_modules/ && vsce package",
|
||||||
|
"postinstall": "node ./node_modules/vscode/bin/install",
|
||||||
|
"pub": "git clean -xdf && vsce publish",
|
||||||
|
"vscode:prepublish": "npm install && npm run compile"
|
||||||
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"lodash": "^4.15.0",
|
"applicationinsights": "0.20.0",
|
||||||
"moment": "^2.14.1",
|
"copy-paste": "1.3.0",
|
||||||
"spawn-rx": "^2.0.1",
|
"ignore": "3.3.3",
|
||||||
"tmp": "^0.0.28"
|
"lodash.debounce": "4.0.8",
|
||||||
|
"lodash.escaperegexp": "4.1.2",
|
||||||
|
"lodash.isequal": "4.5.0",
|
||||||
|
"lodash.once": "4.1.1",
|
||||||
|
"moment": "2.18.1",
|
||||||
|
"spawn-rx": "2.0.10",
|
||||||
|
"tmp": "0.0.31"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"typescript": "^1.8.10",
|
"@types/applicationinsights": "0.15.33",
|
||||||
"vscode": "^0.11.17"
|
"@types/copy-paste": "1.1.30",
|
||||||
},
|
"@types/mocha": "2.2.41",
|
||||||
"scripts": {
|
"@types/node": "7.0.22",
|
||||||
"vscode:prepublish": "node ./node_modules/vscode/bin/compile",
|
"@types/tmp": "0.0.33",
|
||||||
"compile": "node ./node_modules/vscode/bin/compile -watch -p ./",
|
"mocha": "3.4.1",
|
||||||
"postinstall": "node ./node_modules/vscode/bin/install && tsc"
|
"tslint": "5.3.0",
|
||||||
|
"typescript": "2.3.3",
|
||||||
|
"vscode": "1.1.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
511
src/@types/applicationinsights/index.d.ts
vendored
Normal file
@@ -0,0 +1,511 @@
|
|||||||
|
/*
|
||||||
|
In 0.15.33 `setOfflineMode` is missing from the interfaces `ApplicationInsights` and `Channel`.
|
||||||
|
When a version > 0.15.33 of @types/applicationinsights is available on npm, this file can be
|
||||||
|
deleted and the dev dependency can be resurrected in `package.json`.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Type definitions for Application Insights v0.15.12
|
||||||
|
// Project: https://github.com/Microsoft/ApplicationInsights-node.js
|
||||||
|
// Definitions by: Scott Southwood <https://github.com/scsouthw/>
|
||||||
|
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
|
||||||
|
|
||||||
|
interface AutoCollectConsole {
|
||||||
|
constructor(client: Client): AutoCollectConsole;
|
||||||
|
enable(isEnabled: boolean): void;
|
||||||
|
isInitialized(): boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AutoCollectExceptions {
|
||||||
|
constructor(client:Client): AutoCollectExceptions;
|
||||||
|
isInitialized(): boolean;
|
||||||
|
enable(isEnabled:boolean): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AutoCollectPerformance {
|
||||||
|
constructor(client: Client): AutoCollectPerformance;
|
||||||
|
enable(isEnabled: boolean): void;
|
||||||
|
isInitialized(): boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AutoCollectRequests {
|
||||||
|
constructor(client: Client): AutoCollectRequests;
|
||||||
|
enable(isEnabled: boolean): void;
|
||||||
|
isInitialized(): boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
declare namespace ContractsModule {
|
||||||
|
enum DataPointType {
|
||||||
|
Measurement = 0,
|
||||||
|
Aggregation = 1,
|
||||||
|
}
|
||||||
|
enum DependencyKind {
|
||||||
|
SQL = 0,
|
||||||
|
Http = 1,
|
||||||
|
Other = 2,
|
||||||
|
}
|
||||||
|
enum DependencySourceType {
|
||||||
|
Undefined = 0,
|
||||||
|
Aic = 1,
|
||||||
|
Apmc = 2,
|
||||||
|
}
|
||||||
|
enum SessionState {
|
||||||
|
Start = 0,
|
||||||
|
End = 1,
|
||||||
|
}
|
||||||
|
enum SeverityLevel {
|
||||||
|
Verbose = 0,
|
||||||
|
Information = 1,
|
||||||
|
Warning = 2,
|
||||||
|
Error = 3,
|
||||||
|
Critical = 4,
|
||||||
|
}
|
||||||
|
interface ContextTagKeys {
|
||||||
|
applicationVersion: string;
|
||||||
|
applicationBuild: string;
|
||||||
|
deviceId: string;
|
||||||
|
deviceIp: string;
|
||||||
|
deviceLanguage: string;
|
||||||
|
deviceLocale: string;
|
||||||
|
deviceModel: string;
|
||||||
|
deviceNetwork: string;
|
||||||
|
deviceOEMName: string;
|
||||||
|
deviceOS: string;
|
||||||
|
deviceOSVersion: string;
|
||||||
|
deviceRoleInstance: string;
|
||||||
|
deviceRoleName: string;
|
||||||
|
deviceScreenResolution: string;
|
||||||
|
deviceType: string;
|
||||||
|
deviceMachineName: string;
|
||||||
|
locationIp: string;
|
||||||
|
operationId: string;
|
||||||
|
operationName: string;
|
||||||
|
operationParentId: string;
|
||||||
|
operationRootId: string;
|
||||||
|
operationSyntheticSource: string;
|
||||||
|
operationIsSynthetic: string;
|
||||||
|
sessionId: string;
|
||||||
|
sessionIsFirst: string;
|
||||||
|
sessionIsNew: string;
|
||||||
|
userAccountAcquisitionDate: string;
|
||||||
|
userAccountId: string;
|
||||||
|
userAgent: string;
|
||||||
|
userId: string;
|
||||||
|
userStoreRegion: string;
|
||||||
|
sampleRate: string;
|
||||||
|
internalSdkVersion: string;
|
||||||
|
internalAgentVersion: string;
|
||||||
|
constructor(): ContextTagKeys;
|
||||||
|
}
|
||||||
|
interface Domain {
|
||||||
|
ver: number;
|
||||||
|
properties: any;
|
||||||
|
constructor(): Domain;
|
||||||
|
}
|
||||||
|
interface Data<TDomain extends ContractsModule.Domain> {
|
||||||
|
baseType: string;
|
||||||
|
baseData: TDomain;
|
||||||
|
constructor(): Data<TDomain>;
|
||||||
|
}
|
||||||
|
interface Envelope {
|
||||||
|
ver: number;
|
||||||
|
name: string;
|
||||||
|
time: string;
|
||||||
|
sampleRate: number;
|
||||||
|
seq: string;
|
||||||
|
iKey: string;
|
||||||
|
flags: number;
|
||||||
|
deviceId: string;
|
||||||
|
os: string;
|
||||||
|
osVer: string;
|
||||||
|
appId: string;
|
||||||
|
appVer: string;
|
||||||
|
userId: string;
|
||||||
|
tags: {
|
||||||
|
[key: string]: string;
|
||||||
|
};
|
||||||
|
data: Data<Domain>;
|
||||||
|
constructor(): Envelope;
|
||||||
|
}
|
||||||
|
interface EventData extends ContractsModule.Domain {
|
||||||
|
ver: number;
|
||||||
|
name: string;
|
||||||
|
properties: any;
|
||||||
|
measurements: any;
|
||||||
|
constructor(): EventData;
|
||||||
|
}
|
||||||
|
interface MessageData extends ContractsModule.Domain {
|
||||||
|
ver: number;
|
||||||
|
message: string;
|
||||||
|
severityLevel: ContractsModule.SeverityLevel;
|
||||||
|
properties: any;
|
||||||
|
constructor(): MessageData;
|
||||||
|
}
|
||||||
|
interface ExceptionData extends ContractsModule.Domain {
|
||||||
|
ver: number;
|
||||||
|
handledAt: string;
|
||||||
|
exceptions: ExceptionDetails[];
|
||||||
|
severityLevel: ContractsModule.SeverityLevel;
|
||||||
|
problemId: string;
|
||||||
|
crashThreadId: number;
|
||||||
|
properties: any;
|
||||||
|
measurements: any;
|
||||||
|
constructor(): ExceptionData;
|
||||||
|
}
|
||||||
|
interface StackFrame {
|
||||||
|
level: number;
|
||||||
|
method: string;
|
||||||
|
assembly: string;
|
||||||
|
fileName: string;
|
||||||
|
line: number;
|
||||||
|
constructor(): StackFrame;
|
||||||
|
}
|
||||||
|
interface ExceptionDetails {
|
||||||
|
id: number;
|
||||||
|
outerId: number;
|
||||||
|
typeName: string;
|
||||||
|
message: string;
|
||||||
|
hasFullStack: boolean;
|
||||||
|
stack: string;
|
||||||
|
parsedStack: StackFrame[];
|
||||||
|
constructor(): ExceptionDetails;
|
||||||
|
}
|
||||||
|
interface DataPoint {
|
||||||
|
name: string;
|
||||||
|
kind: ContractsModule.DataPointType;
|
||||||
|
value: number;
|
||||||
|
count: number;
|
||||||
|
min: number;
|
||||||
|
max: number;
|
||||||
|
stdDev: number;
|
||||||
|
constructor(): DataPoint;
|
||||||
|
}
|
||||||
|
interface MetricData extends ContractsModule.Domain {
|
||||||
|
ver: number;
|
||||||
|
metrics: DataPoint[];
|
||||||
|
properties: any;
|
||||||
|
constructor(): MetricData;
|
||||||
|
}
|
||||||
|
interface PageViewData extends ContractsModule.EventData {
|
||||||
|
ver: number;
|
||||||
|
url: string;
|
||||||
|
name: string;
|
||||||
|
duration: string;
|
||||||
|
properties: any;
|
||||||
|
measurements: any;
|
||||||
|
constructor(): PageViewData;
|
||||||
|
}
|
||||||
|
interface PageViewPerfData extends ContractsModule.PageViewData {
|
||||||
|
ver: number;
|
||||||
|
url: string;
|
||||||
|
perfTotal: string;
|
||||||
|
name: string;
|
||||||
|
duration: string;
|
||||||
|
networkConnect: string;
|
||||||
|
sentRequest: string;
|
||||||
|
receivedResponse: string;
|
||||||
|
domProcessing: string;
|
||||||
|
properties: any;
|
||||||
|
measurements: any;
|
||||||
|
constructor(): PageViewPerfData;
|
||||||
|
}
|
||||||
|
interface RemoteDependencyData extends ContractsModule.Domain {
|
||||||
|
ver: number;
|
||||||
|
name: string;
|
||||||
|
kind: ContractsModule.DataPointType;
|
||||||
|
value: number;
|
||||||
|
count: number;
|
||||||
|
min: number;
|
||||||
|
max: number;
|
||||||
|
stdDev: number;
|
||||||
|
dependencyKind: ContractsModule.DependencyKind;
|
||||||
|
success: boolean;
|
||||||
|
async: boolean;
|
||||||
|
dependencySource: ContractsModule.DependencySourceType;
|
||||||
|
commandName: string;
|
||||||
|
dependencyTypeName: string;
|
||||||
|
properties: any;
|
||||||
|
constructor(): RemoteDependencyData;
|
||||||
|
}
|
||||||
|
interface AjaxCallData extends ContractsModule.PageViewData {
|
||||||
|
ver: number;
|
||||||
|
url: string;
|
||||||
|
ajaxUrl: string;
|
||||||
|
name: string;
|
||||||
|
duration: string;
|
||||||
|
requestSize: number;
|
||||||
|
responseSize: number;
|
||||||
|
timeToFirstByte: string;
|
||||||
|
timeToLastByte: string;
|
||||||
|
callbackDuration: string;
|
||||||
|
responseCode: string;
|
||||||
|
success: boolean;
|
||||||
|
properties: any;
|
||||||
|
measurements: any;
|
||||||
|
constructor(): AjaxCallData;
|
||||||
|
}
|
||||||
|
interface RequestData extends ContractsModule.Domain {
|
||||||
|
ver: number;
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
startTime: string;
|
||||||
|
duration: string;
|
||||||
|
responseCode: string;
|
||||||
|
success: boolean;
|
||||||
|
httpMethod: string;
|
||||||
|
url: string;
|
||||||
|
properties: any;
|
||||||
|
measurements: any;
|
||||||
|
constructor(): RequestData;
|
||||||
|
}
|
||||||
|
interface SessionStateData extends ContractsModule.Domain {
|
||||||
|
ver: number;
|
||||||
|
state: ContractsModule.SessionState;
|
||||||
|
constructor(): SessionStateData;
|
||||||
|
}
|
||||||
|
interface PerformanceCounterData extends ContractsModule.Domain {
|
||||||
|
ver: number;
|
||||||
|
categoryName: string;
|
||||||
|
counterName: string;
|
||||||
|
instanceName: string;
|
||||||
|
kind: DataPointType;
|
||||||
|
count: number;
|
||||||
|
min: number;
|
||||||
|
max: number;
|
||||||
|
stdDev: number;
|
||||||
|
value: number;
|
||||||
|
properties: any;
|
||||||
|
constructor(): PerformanceCounterData;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
interface Channel {
|
||||||
|
constructor(isDisabled: () => boolean, getBatchSize: () => number, getBatchIntervalMs: () => number, sender: Sender): Channel;
|
||||||
|
/**
|
||||||
|
* Add a telemetry item to the send buffer
|
||||||
|
*/
|
||||||
|
send(envelope: ContractsModule.Envelope): void;
|
||||||
|
handleCrash(envelope: ContractsModule.Envelope): void;
|
||||||
|
/**
|
||||||
|
* Immediately send buffered data
|
||||||
|
*/
|
||||||
|
triggerSend(isNodeCrashing?: boolean): void;
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
setOfflineMode(value: boolean, resentIntervall?: number): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Client {
|
||||||
|
config: Config;
|
||||||
|
context: Context;
|
||||||
|
commonProperties: {
|
||||||
|
[key: string]: string;
|
||||||
|
};
|
||||||
|
channel: Channel;
|
||||||
|
/**
|
||||||
|
* Constructs a new client of the client
|
||||||
|
* @param iKey the instrumentation key to use (read from environment variable if not specified)
|
||||||
|
*/
|
||||||
|
constructor(iKey?: string): Client;
|
||||||
|
/**
|
||||||
|
* Log a user action or other occurrence.
|
||||||
|
* @param name A string to identify this event in the portal.
|
||||||
|
* @param properties map[string, string] - additional data used to filter events and metrics in the portal. Defaults to empty.
|
||||||
|
* @param measurements map[string, number] - metrics associated with this event, displayed in Metrics Explorer on the portal. Defaults to empty.
|
||||||
|
*/
|
||||||
|
trackEvent(name: string, properties?: {
|
||||||
|
[key: string]: string;
|
||||||
|
}, measurements?: {
|
||||||
|
[key: string]: number;
|
||||||
|
}): void;
|
||||||
|
/**
|
||||||
|
* Log a trace message
|
||||||
|
* @param message A string to identify this event in the portal.
|
||||||
|
* @param properties map[string, string] - additional data used to filter events and metrics in the portal. Defaults to empty.
|
||||||
|
*/
|
||||||
|
trackTrace(message: string, severityLevel?: ContractsModule.SeverityLevel, properties?: {
|
||||||
|
[key: string]: string;
|
||||||
|
}): void;
|
||||||
|
/**
|
||||||
|
* Log an exception you have caught.
|
||||||
|
* @param exception An Error from a catch clause, or the string error message.
|
||||||
|
* @param properties map[string, string] - additional data used to filter events and metrics in the portal. Defaults to empty.
|
||||||
|
* @param measurements map[string, number] - metrics associated with this event, displayed in Metrics Explorer on the portal. Defaults to empty.
|
||||||
|
*/
|
||||||
|
trackException(exception: Error, properties?: {
|
||||||
|
[key: string]: string;
|
||||||
|
}): void;
|
||||||
|
/**
|
||||||
|
* Log a numeric value that is not associated with a specific event. Typically used to send regular reports of performance indicators.
|
||||||
|
* To send a single measurement, use just the first two parameters. If you take measurements very frequently, you can reduce the
|
||||||
|
* telemetry bandwidth by aggregating multiple measurements and sending the resulting average at intervals.
|
||||||
|
* @param name A string that identifies the metric.
|
||||||
|
* @param value The value of the metric
|
||||||
|
* @param count the number of samples used to get this value
|
||||||
|
* @param min the min sample for this set
|
||||||
|
* @param max the max sample for this set
|
||||||
|
* @param stdDev the standard deviation of the set
|
||||||
|
*/
|
||||||
|
trackMetric(name: string, value: number, count?:number, min?: number, max?: number, stdDev?: number, properties?: {
|
||||||
|
[key: string]: string;
|
||||||
|
}): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log an incoming http request to your server. The request data will be tracked during the response "finish" event if it is successful or the request "error"
|
||||||
|
* event if it fails. The request duration is automatically calculated as the timespan between when the trackRequest method was called, and when the response "finish"
|
||||||
|
* or request "error" events were fired.
|
||||||
|
* @param request The http.IncomingMessage object to track
|
||||||
|
* @param response The http.ServerResponse object for this request
|
||||||
|
* @param properties map[string, string] - additional data used to filter requests in the portal. Defaults to empty.
|
||||||
|
*/
|
||||||
|
trackRequest(request: any /* http.IncomingMessage */, response: any /* http.ServerResponse */, properties?: {
|
||||||
|
[key: string]: string;
|
||||||
|
}): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log an incoming http request to your server. The request data is tracked synchronously rather than waiting for the response "finish"" or request "error"" events.
|
||||||
|
* Use this if you need your request telemetry to respect custom app insights operation and user context (for example if you set any appInsights.client.context.tags).
|
||||||
|
* @param request The http.IncomingMessage object to track
|
||||||
|
* @param response The http.ServerResponse object for this request
|
||||||
|
* @param ellapsedMilliseconds The duration for this request. Defaults to 0.
|
||||||
|
* @param properties map[string, string] - additional data used to filter requests in the portal. Defaults to empty.
|
||||||
|
* @param error An error that was returned for this request if it was unsuccessful. Defaults to null.
|
||||||
|
*/
|
||||||
|
trackRequestSync(request: any /*http.IncomingMessage */, response: any /*http.ServerResponse */, ellapsedMilliseconds?: number, properties?: {
|
||||||
|
[key: string]: string;}, error?: any) : void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log information about a dependency of your app. Typically used to track the time database calls or outgoing http requests take from your server.
|
||||||
|
* @param name The name of the dependency (i.e. "myDatabse")
|
||||||
|
* @param commandname The name of the command executed on the dependency
|
||||||
|
* @param elapsedTimeMs The amount of time in ms that the dependency took to return the result
|
||||||
|
* @param success True if the dependency succeeded, false otherwise
|
||||||
|
* @param dependencyTypeName The type of the dependency (i.e. "SQL" "HTTP"). Defaults to empty.
|
||||||
|
* @param properties map[string, string] - additional data used to filter events and metrics in the portal. Defaults to empty.
|
||||||
|
* @param dependencyKind ContractsModule.DependencyKind of this dependency. Defaults to Other.
|
||||||
|
* @param async True if the dependency was executed asynchronously, false otherwise. Defaults to false
|
||||||
|
* @param dependencySource ContractsModule.DependencySourceType of this dependency. Defaults to Undefined.
|
||||||
|
*/
|
||||||
|
trackDependency(name: string, commandName: string, elapsedTimeMs: number, success: boolean, dependencyTypeName?: string, properties?: {}, dependencyKind?: any, async?: boolean, dependencySource?: number): void;
|
||||||
|
/**
|
||||||
|
* Immediately send all queued telemetry.
|
||||||
|
*/
|
||||||
|
sendPendingData(callback?: (response: string) => void): void;
|
||||||
|
getEnvelope(data: ContractsModule.Data<ContractsModule.Domain>, tagOverrides?: {
|
||||||
|
[key: string]: string;
|
||||||
|
}): ContractsModule.Envelope;
|
||||||
|
/**
|
||||||
|
* Generic track method for all telemetry types
|
||||||
|
* @param data the telemetry to send
|
||||||
|
* @param tagOverrides the context tags to use for this telemetry which overwrite default context values
|
||||||
|
*/
|
||||||
|
track(data: ContractsModule.Data<ContractsModule.Domain>, tagOverrides?: {
|
||||||
|
[key: string]: string;
|
||||||
|
}): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Config {
|
||||||
|
instrumentationKey: string;
|
||||||
|
sessionRenewalMs: number;
|
||||||
|
sessionExpirationMs: number;
|
||||||
|
endpointUrl: string;
|
||||||
|
maxBatchSize: number;
|
||||||
|
maxBatchIntervalMs: number;
|
||||||
|
disableAppInsights: boolean;
|
||||||
|
constructor(instrumentationKey?: string): Config;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Context {
|
||||||
|
keys: ContractsModule.ContextTagKeys;
|
||||||
|
tags: {
|
||||||
|
[key: string]: string;
|
||||||
|
};
|
||||||
|
constructor(packageJsonPath?: string): Context;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Sender {
|
||||||
|
constructor(getUrl: () => string, onSuccess?: (response: string) => void, onError?: (error: Error) => void): Sender;
|
||||||
|
send(payload: any/* Buffer */): void;
|
||||||
|
saveOnCrash(payload: string): void;
|
||||||
|
/**
|
||||||
|
* enable caching events locally on error
|
||||||
|
*/
|
||||||
|
enableCacheOnError(): void;
|
||||||
|
/**
|
||||||
|
* disable caching events locally on error
|
||||||
|
*/
|
||||||
|
disableCacheOnError(): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The singleton meta interface for the default client of the client. This interface is used to setup/start and configure
|
||||||
|
* the auto-collection behavior of the application insights module.
|
||||||
|
*/
|
||||||
|
interface ApplicationInsights {
|
||||||
|
client: Client;
|
||||||
|
/**
|
||||||
|
* Initializes a client with the given instrumentation key, if this is not specified, the value will be
|
||||||
|
* read from the environment variable APPINSIGHTS_INSTRUMENTATIONKEY
|
||||||
|
* @returns {ApplicationInsights/Client} a new client
|
||||||
|
*/
|
||||||
|
getClient(instrumentationKey?: string): Client;
|
||||||
|
/**
|
||||||
|
* Initializes the default client of the client and sets the default configuration
|
||||||
|
* @param instrumentationKey the instrumentation key to use. Optional, if this is not specified, the value will be
|
||||||
|
* read from the environment variable APPINSIGHTS_INSTRUMENTATIONKEY
|
||||||
|
* @returns {ApplicationInsights} this interface
|
||||||
|
*/
|
||||||
|
setup(instrumentationKey?: string): ApplicationInsights;
|
||||||
|
/**
|
||||||
|
* Starts automatic collection of telemetry. Prior to calling start no telemetry will be collected
|
||||||
|
* @returns {ApplicationInsights} this interface
|
||||||
|
*/
|
||||||
|
start(): ApplicationInsights;
|
||||||
|
/**
|
||||||
|
* Sets the state of console tracking (enabled by default)
|
||||||
|
* @param value if true console activity will be sent to Application Insights
|
||||||
|
* @returns {ApplicationInsights} this interface
|
||||||
|
*/
|
||||||
|
setAutoCollectConsole(value: boolean): ApplicationInsights;
|
||||||
|
/**
|
||||||
|
* Sets the state of dependency tracking (enabled by default)
|
||||||
|
* @param value if true dependencies will be sent to Application Insights
|
||||||
|
* @returns {ApplicationInsights} this interface
|
||||||
|
*/
|
||||||
|
setAutoCollectDependencies(value: boolean): ApplicationInsights;
|
||||||
|
/**
|
||||||
|
* Sets the state of exception tracking (enabled by default)
|
||||||
|
* @param value if true uncaught exceptions will be sent to Application Insights
|
||||||
|
* @returns {ApplicationInsights} this interface
|
||||||
|
*/
|
||||||
|
setAutoCollectExceptions(value: boolean): ApplicationInsights;
|
||||||
|
/**
|
||||||
|
* Sets the state of performance tracking (enabled by default)
|
||||||
|
* @param value if true performance counters will be collected every second and sent to Application Insights
|
||||||
|
* @returns {ApplicationInsights} this interface
|
||||||
|
*/
|
||||||
|
setAutoCollectPerformance(value: boolean): ApplicationInsights;
|
||||||
|
/**
|
||||||
|
* Sets the state of request tracking (enabled by default)
|
||||||
|
* @param value if true requests will be sent to Application Insights
|
||||||
|
* @returns {ApplicationInsights} this interface
|
||||||
|
*/
|
||||||
|
setAutoCollectRequests(value: boolean): ApplicationInsights;
|
||||||
|
/**
|
||||||
|
* Enables verbose debug logging
|
||||||
|
* @returns {ApplicationInsights} this interface
|
||||||
|
*/
|
||||||
|
enableVerboseLogging(): ApplicationInsights;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
setOfflineMode(value: boolean, resentIntervall?: number): ApplicationInsights;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module "applicationinsights" {
|
||||||
|
const applicationinsights: ApplicationInsights;
|
||||||
|
export = applicationinsights;
|
||||||
|
}
|
||||||
10
src/@types/ignore/index.d.ts
vendored
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
declare module "ignore" {
|
||||||
|
namespace ignore {
|
||||||
|
interface Ignore {
|
||||||
|
add(patterns: string | Array<string> | Ignore): Ignore;
|
||||||
|
filter(paths: Array<string>): Array<string>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function ignore(): ignore.Ignore;
|
||||||
|
export = ignore;
|
||||||
|
}
|
||||||
13
src/@types/spawn-rx/index.d.ts
vendored
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
/// <reference path="../../../node_modules/rxjs/Observable.d.ts" />
|
||||||
|
declare module "spawn-rx" {
|
||||||
|
import { Observable } from 'rxjs/Observable';
|
||||||
|
|
||||||
|
namespace spawnrx {
|
||||||
|
function findActualExecutable(exe: string, args?: Array<string> | undefined): { cmd: string, args: Array<string> };
|
||||||
|
function spawnDetached(exe: string, params?: Array<string> | undefined, opts?: Object | undefined): Observable<string>;
|
||||||
|
function spawn(exe: string, params?: Array<string> | undefined, opts?: Object | undefined): Observable<string>;
|
||||||
|
function spawnDetachedPromise(exe: string, params?: Array<string> | undefined, opts?: Object | undefined): Promise<string>;
|
||||||
|
function spawnPromise(exe: string, params?: Array<string> | undefined, opts?: Object | undefined): Promise<string>;
|
||||||
|
}
|
||||||
|
export = spawnrx;
|
||||||
|
}
|
||||||
60
src/activeEditorTracker.ts
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
|
||||||
|
'use strict';
|
||||||
|
import { commands, Disposable, TextEditor, window } from 'vscode';
|
||||||
|
import { BuiltInCommands } from './constants';
|
||||||
|
|
||||||
|
export class ActiveEditorTracker extends Disposable {
|
||||||
|
|
||||||
|
private _disposable: Disposable;
|
||||||
|
private _resolver: ((value?: TextEditor | PromiseLike<TextEditor>) => void) | undefined;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super(() => this.dispose());
|
||||||
|
|
||||||
|
this._disposable = window.onDidChangeActiveTextEditor(e => this._resolver && this._resolver(e));
|
||||||
|
}
|
||||||
|
|
||||||
|
dispose() {
|
||||||
|
this._disposable && this._disposable.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
async awaitClose(timeout: number = 500): Promise<TextEditor> {
|
||||||
|
this.close();
|
||||||
|
return this.wait(timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
async awaitNext(timeout: number = 500): Promise<TextEditor> {
|
||||||
|
this.next();
|
||||||
|
return this.wait(timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
async close(): Promise<{} | undefined> {
|
||||||
|
return commands.executeCommand(BuiltInCommands.CloseActiveEditor);
|
||||||
|
}
|
||||||
|
|
||||||
|
async next(): Promise<{} | undefined> {
|
||||||
|
return commands.executeCommand(BuiltInCommands.NextEditor);
|
||||||
|
}
|
||||||
|
|
||||||
|
async wait(timeout: number = 500): Promise<TextEditor> {
|
||||||
|
const editor = await new Promise<TextEditor>((resolve, reject) => {
|
||||||
|
let timer: any;
|
||||||
|
|
||||||
|
this._resolver = (editor: TextEditor) => {
|
||||||
|
if (timer) {
|
||||||
|
clearTimeout(timer as any);
|
||||||
|
timer = 0;
|
||||||
|
resolve(editor);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
timer = setTimeout(() => {
|
||||||
|
resolve(window.activeTextEditor);
|
||||||
|
timer = 0;
|
||||||
|
}, timeout) as any;
|
||||||
|
});
|
||||||
|
this._resolver = undefined;
|
||||||
|
return editor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
390
src/blameActiveLineController.ts
Normal file
@@ -0,0 +1,390 @@
|
|||||||
|
'use strict';
|
||||||
|
import { Functions, Objects } from './system';
|
||||||
|
import { DecorationOptions, DecorationInstanceRenderOptions, DecorationRenderOptions, Disposable, ExtensionContext, Range, StatusBarAlignment, StatusBarItem, TextEditor, TextEditorDecorationType, TextEditorSelectionChangeEvent, window, workspace } from 'vscode';
|
||||||
|
import { BlameAnnotationController } from './blameAnnotationController';
|
||||||
|
import { BlameAnnotationFormat, BlameAnnotationFormatter } from './blameAnnotationFormatter';
|
||||||
|
import { TextEditorComparer } from './comparers';
|
||||||
|
import { IBlameConfig, IConfig, StatusBarCommand } from './configuration';
|
||||||
|
import { DocumentSchemes, ExtensionKey } from './constants';
|
||||||
|
import { BlameabilityChangeEvent, GitCommit, GitContextTracker, GitService, GitUri, IGitCommitLine } from './gitService';
|
||||||
|
import * as moment from 'moment';
|
||||||
|
|
||||||
|
const activeLineDecoration: TextEditorDecorationType = window.createTextEditorDecorationType({
|
||||||
|
after: {
|
||||||
|
margin: '0 0 0 4em'
|
||||||
|
}
|
||||||
|
} as DecorationRenderOptions);
|
||||||
|
|
||||||
|
export class BlameActiveLineController extends Disposable {
|
||||||
|
|
||||||
|
private _activeEditorLineDisposable: Disposable | undefined;
|
||||||
|
private _blameable: boolean;
|
||||||
|
private _config: IConfig;
|
||||||
|
private _currentLine: number = -1;
|
||||||
|
private _disposable: Disposable;
|
||||||
|
private _editor: TextEditor | undefined;
|
||||||
|
private _statusBarItem: StatusBarItem | undefined;
|
||||||
|
private _updateBlameDebounced: (line: number, editor: TextEditor) => Promise<void>;
|
||||||
|
private _uri: GitUri;
|
||||||
|
|
||||||
|
constructor(context: ExtensionContext, private git: GitService, private gitContextTracker: GitContextTracker, private annotationController: BlameAnnotationController) {
|
||||||
|
super(() => this.dispose());
|
||||||
|
|
||||||
|
this._updateBlameDebounced = Functions.debounce(this._updateBlame, 250);
|
||||||
|
|
||||||
|
this._onConfigurationChanged();
|
||||||
|
|
||||||
|
const subscriptions: Disposable[] = [];
|
||||||
|
|
||||||
|
subscriptions.push(workspace.onDidChangeConfiguration(this._onConfigurationChanged, this));
|
||||||
|
subscriptions.push(git.onDidChangeGitCache(this._onGitCacheChanged, this));
|
||||||
|
subscriptions.push(annotationController.onDidToggleBlameAnnotations(this._onBlameAnnotationToggled, this));
|
||||||
|
|
||||||
|
this._disposable = Disposable.from(...subscriptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
dispose() {
|
||||||
|
this._editor && this._editor.setDecorations(activeLineDecoration, []);
|
||||||
|
|
||||||
|
this._activeEditorLineDisposable && this._activeEditorLineDisposable.dispose();
|
||||||
|
this._statusBarItem && this._statusBarItem.dispose();
|
||||||
|
this._disposable && this._disposable.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
private _onConfigurationChanged() {
|
||||||
|
const cfg = workspace.getConfiguration().get<IConfig>(ExtensionKey)!;
|
||||||
|
|
||||||
|
let changed: boolean = false;
|
||||||
|
|
||||||
|
if (!Objects.areEquivalent(cfg.statusBar, this._config && this._config.statusBar)) {
|
||||||
|
changed = true;
|
||||||
|
if (cfg.statusBar.enabled) {
|
||||||
|
const alignment = cfg.statusBar.alignment !== 'left' ? StatusBarAlignment.Right : StatusBarAlignment.Left;
|
||||||
|
if (this._statusBarItem !== undefined && this._statusBarItem.alignment !== alignment) {
|
||||||
|
this._statusBarItem.dispose();
|
||||||
|
this._statusBarItem = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._statusBarItem = this._statusBarItem || window.createStatusBarItem(alignment, alignment === StatusBarAlignment.Right ? 1000 : 0);
|
||||||
|
this._statusBarItem.command = cfg.statusBar.command;
|
||||||
|
}
|
||||||
|
else if (!cfg.statusBar.enabled && this._statusBarItem) {
|
||||||
|
this._statusBarItem.dispose();
|
||||||
|
this._statusBarItem = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Objects.areEquivalent(cfg.blame.annotation.activeLine, this._config && this._config.blame.annotation.activeLine)) {
|
||||||
|
changed = true;
|
||||||
|
if (cfg.blame.annotation.activeLine !== 'off' && this._editor) {
|
||||||
|
this._editor.setDecorations(activeLineDecoration, []);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!Objects.areEquivalent(cfg.blame.annotation.activeLineDarkColor, this._config && this._config.blame.annotation.activeLineDarkColor) ||
|
||||||
|
!Objects.areEquivalent(cfg.blame.annotation.activeLineLightColor, this._config && this._config.blame.annotation.activeLineLightColor)) {
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._config = cfg;
|
||||||
|
|
||||||
|
if (!changed) return;
|
||||||
|
|
||||||
|
let trackActiveLine = cfg.statusBar.enabled || cfg.blame.annotation.activeLine !== 'off';
|
||||||
|
if (trackActiveLine && !this._activeEditorLineDisposable) {
|
||||||
|
const subscriptions: Disposable[] = [];
|
||||||
|
|
||||||
|
subscriptions.push(window.onDidChangeActiveTextEditor(this._onActiveTextEditorChanged, this));
|
||||||
|
subscriptions.push(window.onDidChangeTextEditorSelection(this._onTextEditorSelectionChanged, this));
|
||||||
|
subscriptions.push(this.gitContextTracker.onDidBlameabilityChange(this._onBlameabilityChanged, this));
|
||||||
|
|
||||||
|
this._activeEditorLineDisposable = Disposable.from(...subscriptions);
|
||||||
|
}
|
||||||
|
else if (!trackActiveLine && this._activeEditorLineDisposable) {
|
||||||
|
this._activeEditorLineDisposable.dispose();
|
||||||
|
this._activeEditorLineDisposable = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._onActiveTextEditorChanged(window.activeTextEditor);
|
||||||
|
}
|
||||||
|
|
||||||
|
private isEditorBlameable(editor: TextEditor | undefined): boolean {
|
||||||
|
if (editor === undefined || editor.document === undefined) return false;
|
||||||
|
|
||||||
|
if (!this.git.isTrackable(editor.document.uri)) return false;
|
||||||
|
if (editor.document.isUntitled && editor.document.uri.scheme === DocumentSchemes.File) return false;
|
||||||
|
|
||||||
|
return this.git.isEditorBlameable(editor);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _onActiveTextEditorChanged(editor: TextEditor | undefined) {
|
||||||
|
this._currentLine = -1;
|
||||||
|
|
||||||
|
const previousEditor = this._editor;
|
||||||
|
previousEditor && previousEditor.setDecorations(activeLineDecoration, []);
|
||||||
|
|
||||||
|
if (editor === undefined || !this.isEditorBlameable(editor)) {
|
||||||
|
this.clear(editor);
|
||||||
|
|
||||||
|
this._editor = undefined;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._blameable = editor !== undefined && editor.document !== undefined && !editor.document.isDirty;
|
||||||
|
this._editor = editor;
|
||||||
|
this._uri = await GitUri.fromUri(editor.document.uri, this.git);
|
||||||
|
|
||||||
|
const maxLines = this._config.advanced.caching.statusBar.maxLines;
|
||||||
|
// If caching is on and the file is small enough -- kick off a blame for the whole file
|
||||||
|
if (this._config.advanced.caching.enabled && (maxLines <= 0 || editor.document.lineCount <= maxLines)) {
|
||||||
|
this.git.getBlameForFile(this._uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
this._updateBlame(editor.selection.active.line, editor);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _onBlameabilityChanged(e: BlameabilityChangeEvent) {
|
||||||
|
this._blameable = e.blameable;
|
||||||
|
if (!e.blameable || !this._editor) {
|
||||||
|
this.clear(e.editor);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure this is for the editor we are tracking
|
||||||
|
if (!TextEditorComparer.equals(this._editor, e.editor)) return;
|
||||||
|
|
||||||
|
this._updateBlame(this._editor.selection.active.line, this._editor);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _onBlameAnnotationToggled() {
|
||||||
|
this._onActiveTextEditorChanged(window.activeTextEditor);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _onGitCacheChanged() {
|
||||||
|
this._onActiveTextEditorChanged(window.activeTextEditor);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _onTextEditorSelectionChanged(e: TextEditorSelectionChangeEvent): Promise<void> {
|
||||||
|
// Make sure this is for the editor we are tracking
|
||||||
|
if (!this._blameable || !TextEditorComparer.equals(this._editor, e.textEditor)) return;
|
||||||
|
|
||||||
|
const line = e.selections[0].active.line;
|
||||||
|
if (line === this._currentLine) return;
|
||||||
|
this._currentLine = line;
|
||||||
|
|
||||||
|
if (!this._uri && e.textEditor) {
|
||||||
|
this._uri = await GitUri.fromUri(e.textEditor.document.uri, this.git);
|
||||||
|
}
|
||||||
|
|
||||||
|
this._updateBlameDebounced(line, e.textEditor);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _updateBlame(line: number, editor: TextEditor) {
|
||||||
|
line = line - this._uri.offset;
|
||||||
|
|
||||||
|
let commit: GitCommit | undefined = undefined;
|
||||||
|
let commitLine: IGitCommitLine | undefined = undefined;
|
||||||
|
// Since blame information isn't valid when there are unsaved changes -- don't show any status
|
||||||
|
if (this._blameable && line >= 0) {
|
||||||
|
const blameLine = await this.git.getBlameForLine(this._uri, line);
|
||||||
|
commitLine = blameLine === undefined ? undefined : blameLine.line;
|
||||||
|
commit = blameLine === undefined ? undefined : blameLine.commit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (commit !== undefined && commitLine !== undefined) {
|
||||||
|
this.show(commit, commitLine, editor);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.clear(editor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
clear(editor: TextEditor | undefined, previousEditor?: TextEditor) {
|
||||||
|
editor && editor.setDecorations(activeLineDecoration, []);
|
||||||
|
// I have no idea why the decorators sometimes don't get removed, but if they don't try again with a tiny delay
|
||||||
|
if (editor) {
|
||||||
|
setTimeout(() => editor.setDecorations(activeLineDecoration, []), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
this._statusBarItem && this._statusBarItem.hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
async show(commit: GitCommit, blameLine: IGitCommitLine, editor: TextEditor) {
|
||||||
|
// I have no idea why I need this protection -- but it happens
|
||||||
|
if (!editor.document) return;
|
||||||
|
|
||||||
|
if (this._config.statusBar.enabled && this._statusBarItem !== undefined) {
|
||||||
|
switch (this._config.statusBar.date) {
|
||||||
|
case 'off':
|
||||||
|
this._statusBarItem.text = `$(git-commit) ${commit.author}`;
|
||||||
|
break;
|
||||||
|
case 'absolute':
|
||||||
|
const dateFormat = this._config.statusBar.dateFormat || 'MMMM Do, YYYY h:MMa';
|
||||||
|
let date: string;
|
||||||
|
try {
|
||||||
|
date = moment(commit.date).format(dateFormat);
|
||||||
|
} catch (ex) {
|
||||||
|
date = moment(commit.date).format('MMMM Do, YYYY h:MMa');
|
||||||
|
}
|
||||||
|
this._statusBarItem.text = `$(git-commit) ${commit.author}, ${date}`;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
this._statusBarItem.text = `$(git-commit) ${commit.author}, ${moment(commit.date).fromNow()}`;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (this._config.statusBar.command) {
|
||||||
|
case StatusBarCommand.BlameAnnotate:
|
||||||
|
this._statusBarItem.tooltip = 'Toggle Blame Annotations';
|
||||||
|
break;
|
||||||
|
case StatusBarCommand.ShowBlameHistory:
|
||||||
|
this._statusBarItem.tooltip = 'Open Blame History Explorer';
|
||||||
|
break;
|
||||||
|
case StatusBarCommand.ShowFileHistory:
|
||||||
|
this._statusBarItem.tooltip = 'Open File History Explorer';
|
||||||
|
break;
|
||||||
|
case StatusBarCommand.DiffWithPrevious:
|
||||||
|
this._statusBarItem.tooltip = 'Compare with Previous Commit';
|
||||||
|
break;
|
||||||
|
case StatusBarCommand.ToggleCodeLens:
|
||||||
|
this._statusBarItem.tooltip = 'Toggle Git CodeLens';
|
||||||
|
break;
|
||||||
|
case StatusBarCommand.ShowQuickCommitDetails:
|
||||||
|
this._statusBarItem.tooltip = 'Show Commit Details';
|
||||||
|
break;
|
||||||
|
case StatusBarCommand.ShowQuickCommitFileDetails:
|
||||||
|
this._statusBarItem.tooltip = 'Show Line Commit Details';
|
||||||
|
break;
|
||||||
|
case StatusBarCommand.ShowQuickFileHistory:
|
||||||
|
this._statusBarItem.tooltip = 'Show File History';
|
||||||
|
break;
|
||||||
|
case StatusBarCommand.ShowQuickCurrentBranchHistory:
|
||||||
|
this._statusBarItem.tooltip = 'Show Branch History';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._statusBarItem.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._config.blame.annotation.activeLine !== 'off') {
|
||||||
|
const activeLine = this._config.blame.annotation.activeLine;
|
||||||
|
const offset = this._uri.offset;
|
||||||
|
|
||||||
|
const cfg = {
|
||||||
|
annotation: {
|
||||||
|
sha: true,
|
||||||
|
author: this._config.statusBar.enabled ? false : this._config.blame.annotation.author,
|
||||||
|
date: this._config.statusBar.enabled ? 'off' : this._config.blame.annotation.date,
|
||||||
|
message: true
|
||||||
|
}
|
||||||
|
} as IBlameConfig;
|
||||||
|
|
||||||
|
const annotation = BlameAnnotationFormatter.getAnnotation(cfg, commit, BlameAnnotationFormat.Unconstrained);
|
||||||
|
|
||||||
|
// Get the full commit message -- since blame only returns the summary
|
||||||
|
let logCommit: GitCommit | undefined = undefined;
|
||||||
|
if (!commit.isUncommitted) {
|
||||||
|
logCommit = await this.git.getLogCommit(this._uri.repoPath, this._uri.fsPath, commit.sha);
|
||||||
|
}
|
||||||
|
|
||||||
|
// I have no idea why I need this protection -- but it happens
|
||||||
|
if (!editor.document) return;
|
||||||
|
|
||||||
|
let hoverMessage: string | string[] | undefined = undefined;
|
||||||
|
if (activeLine !== 'inline') {
|
||||||
|
// If the messages match (or we couldn't find the log), then this is a possible duplicate annotation
|
||||||
|
const possibleDuplicate = !logCommit || logCommit.message === commit.message;
|
||||||
|
// If we don't have a possible dupe or we aren't showing annotations get the hover message
|
||||||
|
if (!commit.isUncommitted && (!possibleDuplicate || !this.annotationController.isAnnotating(editor))) {
|
||||||
|
hoverMessage = BlameAnnotationFormatter.getAnnotationHover(cfg, blameLine, logCommit || commit);
|
||||||
|
|
||||||
|
// if (commit.previousSha !== undefined) {
|
||||||
|
// const changes = await this.git.getDiffForLine(this._uri.repoPath, this._uri.fsPath, blameLine.line + offset, commit.previousSha);
|
||||||
|
// if (changes !== undefined) {
|
||||||
|
// const previous = changes[0];
|
||||||
|
// if (previous !== undefined) {
|
||||||
|
// hoverMessage += `\n\n\`Before ${commit.shortSha}\`\n\`\`\`\n${previous.trim().replace(/\n/g, '\`\n>\n> \`')}\n\`\`\``;
|
||||||
|
// }
|
||||||
|
// else {
|
||||||
|
// hoverMessage += `\n\n\`Added in ${commit.shortSha}\``;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
else if (commit.isUncommitted) {
|
||||||
|
const changes = await this.git.getDiffForLine(this._uri.repoPath, this._uri.fsPath, blameLine.line + offset);
|
||||||
|
if (changes !== undefined) {
|
||||||
|
let original = changes[0];
|
||||||
|
if (original !== undefined) {
|
||||||
|
original = original.replace(/\n/g, '\`\n>\n> \`').trim();
|
||||||
|
hoverMessage = `\`${'0'.repeat(8)}\` __Uncommitted change__\n\n\---\n\`\`\`\n${original}\n\`\`\``;
|
||||||
|
}
|
||||||
|
// else {
|
||||||
|
// hoverMessage = `\`${'0'.repeat(8)}\` __Uncommitted change__\n\n\`Added\``;
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let decorationOptions: [DecorationOptions] | undefined = undefined;
|
||||||
|
switch (activeLine) {
|
||||||
|
case 'both':
|
||||||
|
case 'inline':
|
||||||
|
const range = editor.document.validateRange(new Range(blameLine.line + offset, 0, blameLine.line + offset, 1000000));
|
||||||
|
decorationOptions = [
|
||||||
|
{
|
||||||
|
range: range.with({
|
||||||
|
start: range.start.with({
|
||||||
|
character: range.end.character
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
hoverMessage: hoverMessage,
|
||||||
|
renderOptions: {
|
||||||
|
after: {
|
||||||
|
contentText: annotation
|
||||||
|
},
|
||||||
|
dark: {
|
||||||
|
after: {
|
||||||
|
color: this._config.blame.annotation.activeLineDarkColor || 'rgba(153, 153, 153, 0.35)'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
light: {
|
||||||
|
after: {
|
||||||
|
color: this._config.blame.annotation.activeLineLightColor || 'rgba(153, 153, 153, 0.35)'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} as DecorationInstanceRenderOptions
|
||||||
|
} as DecorationOptions
|
||||||
|
];
|
||||||
|
|
||||||
|
if (activeLine === 'both') {
|
||||||
|
// Add a hover decoration to the area between the start of the line and the first non-whitespace character
|
||||||
|
decorationOptions.push({
|
||||||
|
range: range.with({
|
||||||
|
end: range.end.with({
|
||||||
|
character: editor.document.lineAt(range.end.line).firstNonWhitespaceCharacterIndex
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
hoverMessage: hoverMessage
|
||||||
|
} as DecorationOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'hover':
|
||||||
|
decorationOptions = [
|
||||||
|
{
|
||||||
|
range: editor.document.validateRange(new Range(blameLine.line + offset, 0, blameLine.line + offset, 1000000)),
|
||||||
|
hoverMessage: hoverMessage
|
||||||
|
} as DecorationOptions
|
||||||
|
];
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (decorationOptions !== undefined) {
|
||||||
|
editor.setDecorations(activeLineDecoration, decorationOptions);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
253
src/blameAnnotationController.ts
Normal file
@@ -0,0 +1,253 @@
|
|||||||
|
'use strict';
|
||||||
|
import { Functions } from './system';
|
||||||
|
import { DecorationRenderOptions, Disposable, Event, EventEmitter, ExtensionContext, OverviewRulerLane, TextDocument, TextEditor, TextEditorDecorationType, TextEditorViewColumnChangeEvent, window, workspace } from 'vscode';
|
||||||
|
import { BlameAnnotationProvider } from './blameAnnotationProvider';
|
||||||
|
import { TextDocumentComparer, TextEditorComparer } from './comparers';
|
||||||
|
import { IBlameConfig } from './configuration';
|
||||||
|
import { ExtensionKey } from './constants';
|
||||||
|
import { BlameabilityChangeEvent, GitService, GitUri, GitContextTracker } from './gitService';
|
||||||
|
import { Logger } from './logger';
|
||||||
|
import { WhitespaceController } from './whitespaceController';
|
||||||
|
|
||||||
|
export const BlameDecorations = {
|
||||||
|
annotation: window.createTextEditorDecorationType({
|
||||||
|
before: {
|
||||||
|
margin: '0 1.75em 0 0'
|
||||||
|
},
|
||||||
|
after: {
|
||||||
|
margin: '0 0 0 4em'
|
||||||
|
}
|
||||||
|
} as DecorationRenderOptions),
|
||||||
|
highlight: undefined as TextEditorDecorationType | undefined
|
||||||
|
};
|
||||||
|
|
||||||
|
export class BlameAnnotationController extends Disposable {
|
||||||
|
|
||||||
|
private _onDidToggleBlameAnnotations = new EventEmitter<void>();
|
||||||
|
get onDidToggleBlameAnnotations(): Event<void> {
|
||||||
|
return this._onDidToggleBlameAnnotations.event;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _annotationProviders: Map<number, BlameAnnotationProvider> = new Map();
|
||||||
|
private _blameAnnotationsDisposable: Disposable | undefined;
|
||||||
|
private _config: IBlameConfig;
|
||||||
|
private _disposable: Disposable;
|
||||||
|
private _whitespaceController: WhitespaceController | undefined;
|
||||||
|
|
||||||
|
constructor(private context: ExtensionContext, private git: GitService, private gitContextTracker: GitContextTracker) {
|
||||||
|
super(() => this.dispose());
|
||||||
|
|
||||||
|
this._onConfigurationChanged();
|
||||||
|
|
||||||
|
const subscriptions: Disposable[] = [];
|
||||||
|
|
||||||
|
subscriptions.push(workspace.onDidChangeConfiguration(this._onConfigurationChanged, this));
|
||||||
|
|
||||||
|
this._disposable = Disposable.from(...subscriptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
dispose() {
|
||||||
|
this._annotationProviders.forEach(async (p, i) => await this.clear(i));
|
||||||
|
|
||||||
|
BlameDecorations.annotation && BlameDecorations.annotation.dispose();
|
||||||
|
BlameDecorations.highlight && BlameDecorations.highlight.dispose();
|
||||||
|
|
||||||
|
this._blameAnnotationsDisposable && this._blameAnnotationsDisposable.dispose();
|
||||||
|
this._whitespaceController && this._whitespaceController.dispose();
|
||||||
|
this._disposable && this._disposable.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
private _onConfigurationChanged() {
|
||||||
|
let toggleWhitespace = workspace.getConfiguration(`${ExtensionKey}.advanced.toggleWhitespace`).get<boolean>('enabled');
|
||||||
|
if (!toggleWhitespace) {
|
||||||
|
// Until https://github.com/Microsoft/vscode/issues/11485 is fixed we need to toggle whitespace for non-monospace fonts and ligatures
|
||||||
|
// TODO: detect monospace font
|
||||||
|
toggleWhitespace = workspace.getConfiguration('editor').get<boolean>('fontLigatures');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (toggleWhitespace && !this._whitespaceController) {
|
||||||
|
this._whitespaceController = new WhitespaceController();
|
||||||
|
}
|
||||||
|
else if (!toggleWhitespace && this._whitespaceController) {
|
||||||
|
this._whitespaceController.dispose();
|
||||||
|
this._whitespaceController = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const cfg = workspace.getConfiguration(ExtensionKey).get<IBlameConfig>('blame')!;
|
||||||
|
|
||||||
|
if (cfg.annotation.highlight !== (this._config && this._config.annotation.highlight)) {
|
||||||
|
BlameDecorations.highlight && BlameDecorations.highlight.dispose();
|
||||||
|
|
||||||
|
switch (cfg.annotation.highlight) {
|
||||||
|
case 'gutter':
|
||||||
|
BlameDecorations.highlight = window.createTextEditorDecorationType({
|
||||||
|
dark: {
|
||||||
|
gutterIconPath: this.context.asAbsolutePath('images/blame-dark.svg'),
|
||||||
|
overviewRulerColor: 'rgba(255, 255, 255, 0.75)'
|
||||||
|
},
|
||||||
|
light: {
|
||||||
|
gutterIconPath: this.context.asAbsolutePath('images/blame-light.svg'),
|
||||||
|
overviewRulerColor: 'rgba(0, 0, 0, 0.75)'
|
||||||
|
},
|
||||||
|
gutterIconSize: 'contain',
|
||||||
|
overviewRulerLane: OverviewRulerLane.Right
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'line':
|
||||||
|
BlameDecorations.highlight = window.createTextEditorDecorationType({
|
||||||
|
dark: {
|
||||||
|
backgroundColor: 'rgba(255, 255, 255, 0.15)',
|
||||||
|
overviewRulerColor: 'rgba(255, 255, 255, 0.75)'
|
||||||
|
},
|
||||||
|
light: {
|
||||||
|
backgroundColor: 'rgba(0, 0, 0, 0.15)',
|
||||||
|
overviewRulerColor: 'rgba(0, 0, 0, 0.75)'
|
||||||
|
},
|
||||||
|
overviewRulerLane: OverviewRulerLane.Right,
|
||||||
|
isWholeLine: true
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'both':
|
||||||
|
BlameDecorations.highlight = window.createTextEditorDecorationType({
|
||||||
|
dark: {
|
||||||
|
backgroundColor: 'rgba(255, 255, 255, 0.15)',
|
||||||
|
gutterIconPath: this.context.asAbsolutePath('images/blame-dark.svg'),
|
||||||
|
overviewRulerColor: 'rgba(255, 255, 255, 0.75)'
|
||||||
|
},
|
||||||
|
light: {
|
||||||
|
backgroundColor: 'rgba(0, 0, 0, 0.15)',
|
||||||
|
gutterIconPath: this.context.asAbsolutePath('images/blame-light.svg'),
|
||||||
|
overviewRulerColor: 'rgba(0, 0, 0, 0.75)'
|
||||||
|
},
|
||||||
|
gutterIconSize: 'contain',
|
||||||
|
overviewRulerLane: OverviewRulerLane.Right,
|
||||||
|
isWholeLine: true
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
BlameDecorations.highlight = undefined;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this._config = cfg;
|
||||||
|
}
|
||||||
|
|
||||||
|
async clear(column: number) {
|
||||||
|
const provider = this._annotationProviders.get(column);
|
||||||
|
if (!provider) return;
|
||||||
|
|
||||||
|
this._annotationProviders.delete(column);
|
||||||
|
await provider.dispose();
|
||||||
|
|
||||||
|
if (this._annotationProviders.size === 0) {
|
||||||
|
Logger.log(`Remove listener registrations for blame annotations`);
|
||||||
|
this._blameAnnotationsDisposable && this._blameAnnotationsDisposable.dispose();
|
||||||
|
this._blameAnnotationsDisposable = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._onDidToggleBlameAnnotations.fire();
|
||||||
|
}
|
||||||
|
|
||||||
|
async showBlameAnnotation(editor: TextEditor, shaOrLine?: string | number): Promise<boolean> {
|
||||||
|
if (!editor || !editor.document || !this.git.isEditorBlameable(editor)) return false;
|
||||||
|
|
||||||
|
const currentProvider = this._annotationProviders.get(editor.viewColumn || -1);
|
||||||
|
if (currentProvider && TextEditorComparer.equals(currentProvider.editor, editor)) {
|
||||||
|
await currentProvider.setSelection(shaOrLine);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const gitUri = await GitUri.fromUri(editor.document.uri, this.git);
|
||||||
|
const provider = new BlameAnnotationProvider(this.context, this.git, this._whitespaceController, editor, gitUri);
|
||||||
|
if (!await provider.supportsBlame()) return false;
|
||||||
|
|
||||||
|
if (currentProvider) {
|
||||||
|
await this.clear(currentProvider.editor.viewColumn || -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this._blameAnnotationsDisposable && this._annotationProviders.size === 0) {
|
||||||
|
Logger.log(`Add listener registrations for blame annotations`);
|
||||||
|
|
||||||
|
const subscriptions: Disposable[] = [];
|
||||||
|
|
||||||
|
subscriptions.push(window.onDidChangeVisibleTextEditors(Functions.debounce(this._onVisibleTextEditorsChanged, 100), this));
|
||||||
|
subscriptions.push(window.onDidChangeTextEditorViewColumn(this._onTextEditorViewColumnChanged, this));
|
||||||
|
subscriptions.push(workspace.onDidCloseTextDocument(this._onTextDocumentClosed, this));
|
||||||
|
subscriptions.push(this.gitContextTracker.onDidBlameabilityChange(this._onBlameabilityChanged, this));
|
||||||
|
|
||||||
|
this._blameAnnotationsDisposable = Disposable.from(...subscriptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
this._annotationProviders.set(editor.viewColumn || -1, provider);
|
||||||
|
if (await provider.provideBlameAnnotation(shaOrLine)) {
|
||||||
|
this._onDidToggleBlameAnnotations.fire();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
isAnnotating(editor: TextEditor): boolean {
|
||||||
|
if (!editor || !editor.document || !this.git.isEditorBlameable(editor)) return false;
|
||||||
|
|
||||||
|
return !!this._annotationProviders.get(editor.viewColumn || -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
async toggleBlameAnnotation(editor: TextEditor, shaOrLine?: string | number): Promise<boolean> {
|
||||||
|
if (!editor || !editor.document || !this.git.isEditorBlameable(editor)) return false;
|
||||||
|
|
||||||
|
let provider = this._annotationProviders.get(editor.viewColumn || -1);
|
||||||
|
if (!provider) return this.showBlameAnnotation(editor, shaOrLine);
|
||||||
|
|
||||||
|
await this.clear(provider.editor.viewColumn || -1);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _onBlameabilityChanged(e: BlameabilityChangeEvent) {
|
||||||
|
if (e.blameable || !e.editor) return;
|
||||||
|
|
||||||
|
for (const [key, p] of this._annotationProviders) {
|
||||||
|
if (!TextDocumentComparer.equals(p.document, e.editor.document)) continue;
|
||||||
|
|
||||||
|
Logger.log('BlameabilityChanged:', `Clear blame annotations for column ${key}`);
|
||||||
|
this.clear(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _onTextDocumentClosed(e: TextDocument) {
|
||||||
|
for (const [key, p] of this._annotationProviders) {
|
||||||
|
if (!TextDocumentComparer.equals(p.document, e)) continue;
|
||||||
|
|
||||||
|
Logger.log('TextDocumentClosed:', `Clear blame annotations for column ${key}`);
|
||||||
|
this.clear(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _onTextEditorViewColumnChanged(e: TextEditorViewColumnChangeEvent) {
|
||||||
|
const viewColumn = e.viewColumn || -1;
|
||||||
|
|
||||||
|
Logger.log('TextEditorViewColumnChanged:', `Clear blame annotations for column ${viewColumn}`);
|
||||||
|
await this.clear(viewColumn);
|
||||||
|
|
||||||
|
for (const [key, p] of this._annotationProviders) {
|
||||||
|
if (!TextEditorComparer.equals(p.editor, e.textEditor)) continue;
|
||||||
|
|
||||||
|
Logger.log('TextEditorViewColumnChanged:', `Clear blame annotations for column ${key}`);
|
||||||
|
await this.clear(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _onVisibleTextEditorsChanged(e: TextEditor[]) {
|
||||||
|
if (e.every(_ => _.document.uri.scheme === 'inmemory')) return;
|
||||||
|
|
||||||
|
for (const [key, p] of this._annotationProviders) {
|
||||||
|
if (e.some(_ => TextEditorComparer.equals(p.editor, _))) continue;
|
||||||
|
|
||||||
|
Logger.log('VisibleTextEditorsChanged:', `Clear blame annotations for column ${key}`);
|
||||||
|
this.clear(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
113
src/blameAnnotationFormatter.ts
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
'use strict';
|
||||||
|
import { IBlameConfig } from './configuration';
|
||||||
|
import { GitCommit, IGitCommitLine } from './gitService';
|
||||||
|
import * as moment from 'moment';
|
||||||
|
|
||||||
|
export const defaultAbsoluteDateLength = 10;
|
||||||
|
export const defaultRelativeDateLength = 13;
|
||||||
|
export const defaultAuthorLength = 16;
|
||||||
|
export const defaultMessageLength = 32;
|
||||||
|
|
||||||
|
export enum BlameAnnotationFormat {
|
||||||
|
Constrained,
|
||||||
|
Unconstrained
|
||||||
|
}
|
||||||
|
|
||||||
|
export class BlameAnnotationFormatter {
|
||||||
|
|
||||||
|
static getAnnotation(config: IBlameConfig, commit: GitCommit, format: BlameAnnotationFormat) {
|
||||||
|
const sha = commit.shortSha;
|
||||||
|
let message = this.getMessage(config, commit, format === BlameAnnotationFormat.Unconstrained ? 0 : defaultMessageLength);
|
||||||
|
|
||||||
|
if (format === BlameAnnotationFormat.Unconstrained) {
|
||||||
|
const authorAndDate = this.getAuthorAndDate(config, commit, config.annotation.dateFormat || 'MMMM Do, YYYY h:MMa');
|
||||||
|
if (config.annotation.sha) {
|
||||||
|
message = `${sha}${(authorAndDate ? `\u00a0\u2022\u00a0${authorAndDate}` : '')}${(message ? `\u00a0\u2022\u00a0${message}` : '')}`;
|
||||||
|
}
|
||||||
|
else if (config.annotation.author || config.annotation.date) {
|
||||||
|
message = `${authorAndDate}${(message ? `\u00a0\u2022\u00a0${message}` : '')}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
const author = this.getAuthor(config, commit, defaultAuthorLength);
|
||||||
|
const date = this.getDate(config, commit, config.annotation.dateFormat || 'MM/DD/YYYY', true);
|
||||||
|
if (config.annotation.sha) {
|
||||||
|
message = `${sha}${(author ? `\u00a0\u2022\u00a0${author}` : '')}${(date ? `\u00a0\u2022\u00a0${date}` : '')}${(message ? `\u00a0\u2022\u00a0${message}` : '')}`;
|
||||||
|
}
|
||||||
|
else if (config.annotation.author) {
|
||||||
|
message = `${author}${(date ? `\u00a0\u2022\u00a0${date}` : '')}${(message ? `\u00a0\u2022\u00a0${message}` : '')}`;
|
||||||
|
}
|
||||||
|
else if (config.annotation.date) {
|
||||||
|
message = `${date}${(message ? `\u00a0\u2022\u00a0${message}` : '')}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
static getAnnotationHover(config: IBlameConfig, line: IGitCommitLine, commit: GitCommit): string | Array<string> {
|
||||||
|
const message = `> \`${commit.message.replace(/\n/g, '\`\n>\n> \`')}\``;
|
||||||
|
if (commit.isUncommitted) {
|
||||||
|
return `\`${'0'.repeat(8)}\` __Uncommitted change__`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `\`${commit.shortSha}\` __${commit.author}__, ${moment(commit.date).fromNow()} _(${moment(commit.date).format(config.annotation.dateFormat || 'MMMM Do, YYYY h:MMa')})_ \n\n${message}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
static getAuthorAndDate(config: IBlameConfig, commit: GitCommit, format: string, force: boolean = false) {
|
||||||
|
if (!force && !config.annotation.author && (!config.annotation.date || config.annotation.date === 'off')) return '';
|
||||||
|
|
||||||
|
if (!config.annotation.author) {
|
||||||
|
return this.getDate(config, commit, format);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!config.annotation.date || config.annotation.date === 'off') {
|
||||||
|
return this.getAuthor(config, commit);
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${this.getAuthor(config, commit)}, ${this.getDate(config, commit, format)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
static getAuthor(config: IBlameConfig, commit: GitCommit, truncateTo: number = 0, force: boolean = false) {
|
||||||
|
if (!force && !config.annotation.author) return '';
|
||||||
|
|
||||||
|
const author = commit.isUncommitted ? 'Uncommitted' : commit.author;
|
||||||
|
if (!truncateTo) return author;
|
||||||
|
|
||||||
|
if (author.length > truncateTo) {
|
||||||
|
return `${author.substring(0, truncateTo - 1)}\u2026`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (force) return author; // Don't pad when just asking for the value
|
||||||
|
return author + '\u00a0'.repeat(truncateTo - author.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
static getDate(config: IBlameConfig, commit: GitCommit, format: string, truncate: boolean = false, force: boolean = false) {
|
||||||
|
if (!force && (!config.annotation.date || config.annotation.date === 'off')) return '';
|
||||||
|
|
||||||
|
const date = config.annotation.date === 'relative'
|
||||||
|
? moment(commit.date).fromNow()
|
||||||
|
: moment(commit.date).format(format);
|
||||||
|
if (!truncate) return date;
|
||||||
|
|
||||||
|
const truncateTo = config.annotation.date === 'relative' ? defaultRelativeDateLength : defaultAbsoluteDateLength;
|
||||||
|
if (date.length > truncateTo) {
|
||||||
|
return `${date.substring(0, truncateTo - 1)}\u2026`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (force) return date; // Don't pad when just asking for the value
|
||||||
|
return date + '\u00a0'.repeat(truncateTo - date.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
static getMessage(config: IBlameConfig, commit: GitCommit, truncateTo: number = 0, force: boolean = false) {
|
||||||
|
if (!force && !config.annotation.message) return '';
|
||||||
|
|
||||||
|
let message = commit.isUncommitted ? 'Uncommitted change' : commit.message;
|
||||||
|
if (truncateTo && message.length > truncateTo) {
|
||||||
|
return `${message.substring(0, truncateTo - 1)}\u2026`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
}
|
||||||
302
src/blameAnnotationProvider.ts
Normal file
@@ -0,0 +1,302 @@
|
|||||||
|
'use strict';
|
||||||
|
import { Iterables } from './system';
|
||||||
|
import { DecorationInstanceRenderOptions, DecorationOptions, Disposable, ExtensionContext, Range, TextDocument, TextEditor, TextEditorSelectionChangeEvent, window, workspace } from 'vscode';
|
||||||
|
import { BlameAnnotationFormat, BlameAnnotationFormatter, defaultAuthorLength } from './blameAnnotationFormatter';
|
||||||
|
import { BlameDecorations } from './blameAnnotationController';
|
||||||
|
import { TextDocumentComparer } from './comparers';
|
||||||
|
import { BlameAnnotationStyle, IBlameConfig } from './configuration';
|
||||||
|
import { ExtensionKey } from './constants';
|
||||||
|
import { GitService, GitUri, IGitBlame } from './gitService';
|
||||||
|
import { WhitespaceController } from './whitespaceController';
|
||||||
|
|
||||||
|
export class BlameAnnotationProvider extends Disposable {
|
||||||
|
|
||||||
|
public document: TextDocument;
|
||||||
|
|
||||||
|
private _blame: Promise<IGitBlame>;
|
||||||
|
private _config: IBlameConfig;
|
||||||
|
private _disposable: Disposable;
|
||||||
|
|
||||||
|
constructor(context: ExtensionContext, private git: GitService, private whitespaceController: WhitespaceController | undefined, public editor: TextEditor, private uri: GitUri) {
|
||||||
|
super(() => this.dispose());
|
||||||
|
|
||||||
|
this.document = this.editor.document;
|
||||||
|
|
||||||
|
this._blame = this.git.getBlameForFile(this.uri);
|
||||||
|
|
||||||
|
this._config = workspace.getConfiguration(ExtensionKey).get<IBlameConfig>('blame')!;
|
||||||
|
|
||||||
|
const subscriptions: Disposable[] = [];
|
||||||
|
|
||||||
|
subscriptions.push(window.onDidChangeTextEditorSelection(this._onActiveSelectionChanged, this));
|
||||||
|
|
||||||
|
this._disposable = Disposable.from(...subscriptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
async dispose() {
|
||||||
|
if (this.editor) {
|
||||||
|
try {
|
||||||
|
this.editor.setDecorations(BlameDecorations.annotation, []);
|
||||||
|
BlameDecorations.highlight && this.editor.setDecorations(BlameDecorations.highlight, []);
|
||||||
|
// I have no idea why the decorators sometimes don't get removed, but if they don't try again with a tiny delay
|
||||||
|
if (BlameDecorations.highlight !== undefined) {
|
||||||
|
setTimeout(() => {
|
||||||
|
if (BlameDecorations.highlight === undefined) return;
|
||||||
|
|
||||||
|
this.editor.setDecorations(BlameDecorations.highlight, []);
|
||||||
|
}, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (ex) { }
|
||||||
|
}
|
||||||
|
|
||||||
|
// HACK: Until https://github.com/Microsoft/vscode/issues/11485 is fixed -- restore whitespace
|
||||||
|
this.whitespaceController && await this.whitespaceController.restore();
|
||||||
|
|
||||||
|
this._disposable && this._disposable.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _onActiveSelectionChanged(e: TextEditorSelectionChangeEvent) {
|
||||||
|
if (!TextDocumentComparer.equals(this.document, e.textEditor && e.textEditor.document)) return;
|
||||||
|
|
||||||
|
return this.setSelection(e.selections[0].active.line);
|
||||||
|
}
|
||||||
|
|
||||||
|
async supportsBlame(): Promise<boolean> {
|
||||||
|
const blame = await this._blame;
|
||||||
|
return !!(blame && blame.lines.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
async provideBlameAnnotation(shaOrLine?: string | number): Promise<boolean> {
|
||||||
|
let whitespacePromise: Promise<void> | undefined;
|
||||||
|
// HACK: Until https://github.com/Microsoft/vscode/issues/11485 is fixed -- override whitespace (turn off)
|
||||||
|
if (this._config.annotation.style !== BlameAnnotationStyle.Trailing) {
|
||||||
|
whitespacePromise = this.whitespaceController && this.whitespaceController.override();
|
||||||
|
}
|
||||||
|
|
||||||
|
let blame: IGitBlame;
|
||||||
|
if (whitespacePromise) {
|
||||||
|
[blame] = await Promise.all([this._blame, whitespacePromise]);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
blame = await this._blame;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!blame || !blame.lines.length) {
|
||||||
|
this.whitespaceController && await this.whitespaceController.restore();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let blameDecorationOptions: DecorationOptions[] | undefined;
|
||||||
|
switch (this._config.annotation.style) {
|
||||||
|
case BlameAnnotationStyle.Compact:
|
||||||
|
blameDecorationOptions = this._getCompactGutterDecorations(blame);
|
||||||
|
break;
|
||||||
|
case BlameAnnotationStyle.Expanded:
|
||||||
|
blameDecorationOptions = this._getExpandedGutterDecorations(blame, false);
|
||||||
|
break;
|
||||||
|
case BlameAnnotationStyle.Trailing:
|
||||||
|
blameDecorationOptions = this._getExpandedGutterDecorations(blame, true);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (blameDecorationOptions) {
|
||||||
|
this.editor.setDecorations(BlameDecorations.annotation, blameDecorationOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
this._setSelection(blame, shaOrLine);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async setSelection(shaOrLine?: string | number) {
|
||||||
|
const blame = await this._blame;
|
||||||
|
if (!blame || !blame.lines.length) return;
|
||||||
|
|
||||||
|
return this._setSelection(blame, shaOrLine);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _setSelection(blame: IGitBlame, shaOrLine?: string | number) {
|
||||||
|
if (!BlameDecorations.highlight) return;
|
||||||
|
|
||||||
|
const offset = this.uri.offset;
|
||||||
|
|
||||||
|
let sha: string | undefined = undefined;
|
||||||
|
if (typeof shaOrLine === 'string') {
|
||||||
|
sha = shaOrLine;
|
||||||
|
}
|
||||||
|
else if (typeof shaOrLine === 'number') {
|
||||||
|
const line = shaOrLine - offset;
|
||||||
|
if (line >= 0) {
|
||||||
|
const commitLine = blame.lines[line];
|
||||||
|
sha = commitLine && commitLine.sha;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
sha = Iterables.first(blame.commits.values()).sha;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!sha) {
|
||||||
|
this.editor.setDecorations(BlameDecorations.highlight, []);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const highlightDecorationRanges = blame.lines
|
||||||
|
.filter(l => l.sha === sha)
|
||||||
|
.map(l => this.editor.document.validateRange(new Range(l.line + offset, 0, l.line + offset, 1000000)));
|
||||||
|
|
||||||
|
this.editor.setDecorations(BlameDecorations.highlight, highlightDecorationRanges);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _getCompactGutterDecorations(blame: IGitBlame): DecorationOptions[] {
|
||||||
|
const offset = this.uri.offset;
|
||||||
|
|
||||||
|
let count = 0;
|
||||||
|
let lastSha: string;
|
||||||
|
return blame.lines.map(l => {
|
||||||
|
const commit = blame.commits.get(l.sha);
|
||||||
|
if (commit === undefined) throw new Error(`Cannot find sha ${l.sha}`);
|
||||||
|
|
||||||
|
let color: string;
|
||||||
|
if (commit.isUncommitted) {
|
||||||
|
color = 'rgba(0, 188, 242, 0.6)';
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
color = l.previousSha ? '#999999' : '#6b6b6b';
|
||||||
|
}
|
||||||
|
|
||||||
|
let gutter = '';
|
||||||
|
if (lastSha !== l.sha) {
|
||||||
|
count = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isEmptyOrWhitespace = this.document.lineAt(l.line).isEmptyOrWhitespace;
|
||||||
|
if (!isEmptyOrWhitespace) {
|
||||||
|
switch (++count) {
|
||||||
|
case 0:
|
||||||
|
gutter = commit.shortSha;
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
gutter = `\u2759 ${BlameAnnotationFormatter.getAuthor(this._config, commit, defaultAuthorLength, true)}`;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
gutter = `\u2759 ${BlameAnnotationFormatter.getDate(this._config, commit, this._config.annotation.dateFormat || 'MM/DD/YYYY', true, true)}`;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
gutter = `\u2759`;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const hoverMessage = BlameAnnotationFormatter.getAnnotationHover(this._config, l, commit);
|
||||||
|
|
||||||
|
lastSha = l.sha;
|
||||||
|
|
||||||
|
return {
|
||||||
|
range: this.editor.document.validateRange(new Range(l.line + offset, 0, l.line + offset, 1000000)),
|
||||||
|
hoverMessage: hoverMessage,
|
||||||
|
renderOptions: {
|
||||||
|
before: {
|
||||||
|
color: color,
|
||||||
|
contentText: gutter,
|
||||||
|
width: '11em'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} as DecorationOptions;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private _getExpandedGutterDecorations(blame: IGitBlame, trailing: boolean = false): DecorationOptions[] {
|
||||||
|
const offset = this.uri.offset;
|
||||||
|
|
||||||
|
let width = 0;
|
||||||
|
if (!trailing) {
|
||||||
|
if (this._config.annotation.sha) {
|
||||||
|
width += 5;
|
||||||
|
}
|
||||||
|
if (this._config.annotation.date && this._config.annotation.date !== 'off') {
|
||||||
|
if (width > 0) {
|
||||||
|
width += 7;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
width += 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._config.annotation.date === 'relative') {
|
||||||
|
width += 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (this._config.annotation.author) {
|
||||||
|
if (width > 5 + 6) {
|
||||||
|
width += 12;
|
||||||
|
}
|
||||||
|
else if (width > 0) {
|
||||||
|
width += 11;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
width += 10;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (this._config.annotation.message) {
|
||||||
|
if (width > 5 + 6 + 10) {
|
||||||
|
width += 21;
|
||||||
|
}
|
||||||
|
else if (width > 5 + 6) {
|
||||||
|
width += 21;
|
||||||
|
}
|
||||||
|
else if (width > 0) {
|
||||||
|
width += 21;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
width += 19;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return blame.lines.map(l => {
|
||||||
|
const commit = blame.commits.get(l.sha);
|
||||||
|
if (commit === undefined) throw new Error(`Cannot find sha ${l.sha}`);
|
||||||
|
|
||||||
|
let color: string;
|
||||||
|
if (commit.isUncommitted) {
|
||||||
|
color = 'rgba(0, 188, 242, 0.6)';
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (trailing) {
|
||||||
|
color = l.previousSha ? 'rgba(153, 153, 153, 0.5)' : 'rgba(107, 107, 107, 0.5)';
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
color = l.previousSha ? 'rgb(153, 153, 153)' : 'rgb(107, 107, 107)';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const format = trailing ? BlameAnnotationFormat.Unconstrained : BlameAnnotationFormat.Constrained;
|
||||||
|
const gutter = BlameAnnotationFormatter.getAnnotation(this._config, commit, format);
|
||||||
|
const hoverMessage = BlameAnnotationFormatter.getAnnotationHover(this._config, l, commit);
|
||||||
|
|
||||||
|
let renderOptions: DecorationInstanceRenderOptions;
|
||||||
|
if (trailing) {
|
||||||
|
renderOptions = {
|
||||||
|
after: {
|
||||||
|
color: color,
|
||||||
|
contentText: gutter
|
||||||
|
}
|
||||||
|
} as DecorationInstanceRenderOptions;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
renderOptions = {
|
||||||
|
before: {
|
||||||
|
color: color,
|
||||||
|
contentText: gutter,
|
||||||
|
width: `${width}em`
|
||||||
|
}
|
||||||
|
} as DecorationInstanceRenderOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
range: this.editor.document.validateRange(new Range(l.line + offset, 0, l.line + offset, 1000000)),
|
||||||
|
hoverMessage: hoverMessage,
|
||||||
|
renderOptions: renderOptions
|
||||||
|
} as DecorationOptions;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
108
src/commands.ts
@@ -1,74 +1,38 @@
|
|||||||
'use strict'
|
'use strict';
|
||||||
import {commands, Disposable, Position, Range, Uri, window} from 'vscode';
|
export * from './commands/common';
|
||||||
import {Commands, VsCodeCommands} from './constants';
|
|
||||||
import GitProvider from './gitProvider';
|
|
||||||
import {basename} from 'path';
|
|
||||||
|
|
||||||
abstract class Command extends Disposable {
|
export * from './commands/keyboard';
|
||||||
private _subscriptions: Disposable;
|
|
||||||
|
|
||||||
constructor(command: Commands) {
|
export * from './commands/closeUnchangedFiles';
|
||||||
super(() => this.dispose());
|
export * from './commands/copyMessageToClipboard';
|
||||||
this._subscriptions = commands.registerCommand(command, this.execute.bind(this));
|
export * from './commands/copyShaToClipboard';
|
||||||
}
|
export * from './commands/diffDirectory';
|
||||||
|
export * from './commands/diffLineWithPrevious';
|
||||||
dispose() {
|
export * from './commands/diffLineWithWorking';
|
||||||
this._subscriptions && this._subscriptions.dispose();
|
export * from './commands/diffWithBranch';
|
||||||
super.dispose();
|
export * from './commands/diffWithNext';
|
||||||
}
|
export * from './commands/diffWithPrevious';
|
||||||
|
export * from './commands/diffWithWorking';
|
||||||
abstract execute(...args): any;
|
export * from './commands/openChangedFiles';
|
||||||
}
|
export * from './commands/openBranchInRemote';
|
||||||
|
export * from './commands/openCommitInRemote';
|
||||||
export class BlameCommand extends Command {
|
export * from './commands/openFileInRemote';
|
||||||
constructor(private git: GitProvider) {
|
export * from './commands/openInRemote';
|
||||||
super(Commands.ShowBlameHistory);
|
export * from './commands/openRepoInRemote';
|
||||||
}
|
export * from './commands/showBlame';
|
||||||
|
export * from './commands/showBlameHistory';
|
||||||
execute(uri?: Uri, range?: Range, position?: Position) {
|
export * from './commands/showFileHistory';
|
||||||
// If the command is executed manually -- treat it as a click on the root lens (i.e. show blame for the whole file)
|
export * from './commands/showLastQuickPick';
|
||||||
if (!uri) {
|
export * from './commands/showQuickCommitDetails';
|
||||||
const doc = window.activeTextEditor && window.activeTextEditor.document;
|
export * from './commands/showQuickCommitFileDetails';
|
||||||
if (doc) {
|
export * from './commands/showCommitSearch';
|
||||||
uri = doc.uri;
|
export * from './commands/showQuickFileHistory';
|
||||||
range = doc.validateRange(new Range(0, 0, 1000000, 1000000));
|
export * from './commands/showQuickBranchHistory';
|
||||||
position = doc.validateRange(new Range(0, 0, 0, 1000000)).start;
|
export * from './commands/showQuickCurrentBranchHistory';
|
||||||
}
|
export * from './commands/showQuickRepoStatus';
|
||||||
|
export * from './commands/showQuickStashList';
|
||||||
if (!uri) return;
|
export * from './commands/stashApply';
|
||||||
}
|
export * from './commands/stashDelete';
|
||||||
|
export * from './commands/stashSave';
|
||||||
return this.git.getBlameLocations(uri.path, range).then(locations => {
|
export * from './commands/toggleBlame';
|
||||||
return commands.executeCommand(VsCodeCommands.ShowReferences, uri, position, locations);
|
export * from './commands/toggleCodeLens';
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class DiffWithPreviousCommand extends Command {
|
|
||||||
constructor(private git: GitProvider) {
|
|
||||||
super(Commands.DiffWithPrevious);
|
|
||||||
}
|
|
||||||
|
|
||||||
execute(uri?: Uri, sha?: string, compareWithSha?: string) {
|
|
||||||
// TODO: Execute these in parallel rather than series
|
|
||||||
return this.git.getVersionedFile(uri.path, sha).then(source => {
|
|
||||||
this.git.getVersionedFile(uri.path, compareWithSha).then(compare => {
|
|
||||||
const fileName = basename(uri.path);
|
|
||||||
return commands.executeCommand(VsCodeCommands.Diff, Uri.file(source), Uri.file(compare), `${fileName} (${sha}) ↔ ${fileName} (${compareWithSha})`);
|
|
||||||
})
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class DiffWithWorkingCommand extends Command {
|
|
||||||
constructor(private git: GitProvider) {
|
|
||||||
super(Commands.DiffWithWorking);
|
|
||||||
}
|
|
||||||
|
|
||||||
execute(uri?: Uri, sha?: string) {
|
|
||||||
return this.git.getVersionedFile(uri.path, sha).then(compare => {
|
|
||||||
const fileName = basename(uri.path);
|
|
||||||
return commands.executeCommand(VsCodeCommands.Diff, uri, Uri.file(compare), `${fileName} (index) ↔ ${fileName} (${sha})`);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
71
src/commands/closeUnchangedFiles.ts
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
'use strict';
|
||||||
|
import { TextEditor, Uri, window } from 'vscode';
|
||||||
|
import { ActiveEditorTracker } from '../activeEditorTracker';
|
||||||
|
import { ActiveEditorCommand, Commands, getCommandUri } from './common';
|
||||||
|
import { TextEditorComparer, UriComparer } from '../comparers';
|
||||||
|
import { GitService } from '../gitService';
|
||||||
|
import { Logger } from '../logger';
|
||||||
|
|
||||||
|
export interface CloseUnchangedFilesCommandArgs {
|
||||||
|
uris?: Uri[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CloseUnchangedFilesCommand extends ActiveEditorCommand {
|
||||||
|
|
||||||
|
constructor(private git: GitService) {
|
||||||
|
super(Commands.CloseUnchangedFiles);
|
||||||
|
}
|
||||||
|
|
||||||
|
async execute(editor: TextEditor, uri?: Uri, args: CloseUnchangedFilesCommandArgs = {}) {
|
||||||
|
uri = getCommandUri(uri, editor);
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (args.uris === undefined) {
|
||||||
|
const repoPath = await this.git.getRepoPathFromUri(uri);
|
||||||
|
if (!repoPath) return window.showWarningMessage(`Unable to close unchanged files`);
|
||||||
|
|
||||||
|
const status = await this.git.getStatusForRepo(repoPath);
|
||||||
|
if (status === undefined) return window.showWarningMessage(`Unable to close unchanged files`);
|
||||||
|
|
||||||
|
args.uris = status.files.map(_ => _.Uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
const editorTracker = new ActiveEditorTracker();
|
||||||
|
|
||||||
|
let active = window.activeTextEditor;
|
||||||
|
let editor = active;
|
||||||
|
do {
|
||||||
|
if (editor !== undefined) {
|
||||||
|
if ((editor.document !== undefined && editor.document.isDirty) ||
|
||||||
|
args.uris.some(_ => UriComparer.equals(_, editor!.document && editor!.document.uri))) {
|
||||||
|
// If we didn't start with a valid editor, set one once we find it
|
||||||
|
if (active === undefined) {
|
||||||
|
active = editor;
|
||||||
|
}
|
||||||
|
editor = await editorTracker.awaitNext(500);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (active === editor) {
|
||||||
|
active = undefined;
|
||||||
|
}
|
||||||
|
editor = await editorTracker.awaitClose(500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (active === editor) {
|
||||||
|
active = undefined;
|
||||||
|
}
|
||||||
|
editor = await editorTracker.awaitClose(500);
|
||||||
|
}
|
||||||
|
} while ((active === undefined && editor === undefined) || !TextEditorComparer.equals(active, editor, { useId: true, usePosition: true }));
|
||||||
|
|
||||||
|
editorTracker.dispose();
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
catch (ex) {
|
||||||
|
Logger.error(ex, 'CloseUnchangedFilesCommand');
|
||||||
|
return window.showErrorMessage(`Unable to close unchanged files. See output channel for more details`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
166
src/commands/common.ts
Normal file
@@ -0,0 +1,166 @@
|
|||||||
|
'use strict';
|
||||||
|
import { commands, Disposable, TextDocumentShowOptions, TextEditor, TextEditorEdit, Uri, window, workspace } from 'vscode';
|
||||||
|
import { BuiltInCommands } from '../constants';
|
||||||
|
import { Logger } from '../logger';
|
||||||
|
import { Telemetry } from '../telemetry';
|
||||||
|
|
||||||
|
export type Commands = 'gitlens.closeUnchangedFiles' | 'gitlens.copyMessageToClipboard' | 'gitlens.copyShaToClipboard' |
|
||||||
|
'gitlens.diffDirectory' | 'gitlens.diffWithBranch' | 'gitlens.diffWithNext' | 'gitlens.diffWithPrevious' | 'gitlens.diffLineWithPrevious' | 'gitlens.diffWithWorking' | 'gitlens.diffLineWithWorking' |
|
||||||
|
'gitlens.openChangedFiles' | 'gitlens.openBranchInRemote' | 'gitlens.openCommitInRemote' | 'gitlens.openFileInRemote' | 'gitlens.openInRemote' | 'gitlens.openRepoInRemote' |
|
||||||
|
'gitlens.showBlame' | 'gitlens.showBlameHistory' | 'gitlens.showCommitSearch' | 'gitlens.showFileHistory' |
|
||||||
|
'gitlens.showLastQuickPick' | 'gitlens.showQuickBranchHistory' |
|
||||||
|
'gitlens.showQuickCommitDetails' | 'gitlens.showQuickCommitFileDetails' |
|
||||||
|
'gitlens.showQuickFileHistory' | 'gitlens.showQuickRepoHistory' |
|
||||||
|
'gitlens.showQuickRepoStatus' | 'gitlens.showQuickStashList' |
|
||||||
|
'gitlens.stashApply' | 'gitlens.stashDelete' | 'gitlens.stashSave' |
|
||||||
|
'gitlens.toggleBlame' | 'gitlens.toggleCodeLens';
|
||||||
|
export const Commands = {
|
||||||
|
CloseUnchangedFiles: 'gitlens.closeUnchangedFiles' as Commands,
|
||||||
|
CopyMessageToClipboard: 'gitlens.copyMessageToClipboard' as Commands,
|
||||||
|
CopyShaToClipboard: 'gitlens.copyShaToClipboard' as Commands,
|
||||||
|
DiffDirectory: 'gitlens.diffDirectory' as Commands,
|
||||||
|
DiffWithBranch: 'gitlens.diffWithBranch' as Commands,
|
||||||
|
DiffWithNext: 'gitlens.diffWithNext' as Commands,
|
||||||
|
DiffWithPrevious: 'gitlens.diffWithPrevious' as Commands,
|
||||||
|
DiffLineWithPrevious: 'gitlens.diffLineWithPrevious' as Commands,
|
||||||
|
DiffWithWorking: 'gitlens.diffWithWorking' as Commands,
|
||||||
|
DiffLineWithWorking: 'gitlens.diffLineWithWorking' as Commands,
|
||||||
|
OpenChangedFiles: 'gitlens.openChangedFiles' as Commands,
|
||||||
|
OpenBranchInRemote: 'gitlens.openBranchInRemote' as Commands,
|
||||||
|
OpenCommitInRemote: 'gitlens.openCommitInRemote' as Commands,
|
||||||
|
OpenFileInRemote: 'gitlens.openFileInRemote' as Commands,
|
||||||
|
OpenInRemote: 'gitlens.openInRemote' as Commands,
|
||||||
|
OpenRepoInRemote: 'gitlens.openRepoInRemote' as Commands,
|
||||||
|
ShowBlame: 'gitlens.showBlame' as Commands,
|
||||||
|
ShowBlameHistory: 'gitlens.showBlameHistory' as Commands,
|
||||||
|
ShowCommitSearch: 'gitlens.showCommitSearch' as Commands,
|
||||||
|
ShowFileHistory: 'gitlens.showFileHistory' as Commands,
|
||||||
|
ShowLastQuickPick: 'gitlens.showLastQuickPick' as Commands,
|
||||||
|
ShowQuickCommitDetails: 'gitlens.showQuickCommitDetails' as Commands,
|
||||||
|
ShowQuickCommitFileDetails: 'gitlens.showQuickCommitFileDetails' as Commands,
|
||||||
|
ShowQuickFileHistory: 'gitlens.showQuickFileHistory' as Commands,
|
||||||
|
ShowQuickBranchHistory: 'gitlens.showQuickBranchHistory' as Commands,
|
||||||
|
ShowQuickCurrentBranchHistory: 'gitlens.showQuickRepoHistory' as Commands,
|
||||||
|
ShowQuickRepoStatus: 'gitlens.showQuickRepoStatus' as Commands,
|
||||||
|
ShowQuickStashList: 'gitlens.showQuickStashList' as Commands,
|
||||||
|
StashApply: 'gitlens.stashApply' as Commands,
|
||||||
|
StashDelete: 'gitlens.stashDelete' as Commands,
|
||||||
|
StashSave: 'gitlens.stashSave' as Commands,
|
||||||
|
ToggleBlame: 'gitlens.toggleBlame' as Commands,
|
||||||
|
ToggleCodeLens: 'gitlens.toggleCodeLens' as Commands
|
||||||
|
};
|
||||||
|
|
||||||
|
export function getCommandUri(uri?: Uri, editor?: TextEditor): Uri | undefined {
|
||||||
|
if (uri instanceof Uri) return uri;
|
||||||
|
if (editor === undefined || editor.document === undefined) return undefined;
|
||||||
|
return editor.document.uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CommandContext = 'gitlens:canToggleCodeLens' | 'gitlens:enabled' | 'gitlens:hasRemotes' | 'gitlens:isBlameable' | 'gitlens:isRepository' | 'gitlens:isTracked' | 'gitlens:key';
|
||||||
|
export const CommandContext = {
|
||||||
|
CanToggleCodeLens: 'gitlens:canToggleCodeLens' as CommandContext,
|
||||||
|
Enabled: 'gitlens:enabled' as CommandContext,
|
||||||
|
HasRemotes: 'gitlens:hasRemotes' as CommandContext,
|
||||||
|
IsBlameable: 'gitlens:isBlameable' as CommandContext,
|
||||||
|
IsRepository: 'gitlens:isRepository' as CommandContext,
|
||||||
|
IsTracked: 'gitlens:isTracked' as CommandContext,
|
||||||
|
Key: 'gitlens:key' as CommandContext
|
||||||
|
};
|
||||||
|
|
||||||
|
export function setCommandContext(key: CommandContext | string, value: any) {
|
||||||
|
return commands.executeCommand(BuiltInCommands.SetContext, key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
export abstract class Command extends Disposable {
|
||||||
|
|
||||||
|
private _disposable: Disposable;
|
||||||
|
|
||||||
|
constructor(protected command: Commands) {
|
||||||
|
super(() => this.dispose());
|
||||||
|
this._disposable = commands.registerCommand(command, this._execute, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
dispose() {
|
||||||
|
this._disposable && this._disposable.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected _execute(...args: any[]): any {
|
||||||
|
Telemetry.trackEvent(this.command);
|
||||||
|
return this.execute(...args);
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract execute(...args: any[]): any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export abstract class EditorCommand extends Disposable {
|
||||||
|
|
||||||
|
private _disposable: Disposable;
|
||||||
|
|
||||||
|
constructor(public readonly command: Commands) {
|
||||||
|
super(() => this.dispose());
|
||||||
|
this._disposable = commands.registerTextEditorCommand(command, this._execute, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
dispose() {
|
||||||
|
this._disposable && this._disposable.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
private _execute(editor: TextEditor, edit: TextEditorEdit, ...args: any[]): any {
|
||||||
|
Telemetry.trackEvent(this.command);
|
||||||
|
return this.execute(editor, edit, ...args);
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract execute(editor: TextEditor, edit: TextEditorEdit, ...args: any[]): any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export abstract class ActiveEditorCommand extends Command {
|
||||||
|
|
||||||
|
constructor(public readonly command: Commands) {
|
||||||
|
super(command);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected _execute(...args: any[]): any {
|
||||||
|
return super._execute(window.activeTextEditor, ...args);
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract execute(editor: TextEditor, ...args: any[]): any;
|
||||||
|
}
|
||||||
|
|
||||||
|
let lastCommand: { command: string, args: any[] } | undefined = undefined;
|
||||||
|
export function getLastCommand() {
|
||||||
|
return lastCommand;
|
||||||
|
}
|
||||||
|
|
||||||
|
export abstract class ActiveEditorCachedCommand extends ActiveEditorCommand {
|
||||||
|
|
||||||
|
constructor(public readonly command: Commands) {
|
||||||
|
super(command);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected _execute(...args: any[]): any {
|
||||||
|
lastCommand = {
|
||||||
|
command: this.command,
|
||||||
|
args: args
|
||||||
|
};
|
||||||
|
return super._execute(...args);
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract execute(editor: TextEditor, ...args: any[]): any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function openEditor(uri: Uri, options?: TextDocumentShowOptions): Promise<TextEditor | undefined> {
|
||||||
|
try {
|
||||||
|
const defaults: TextDocumentShowOptions = {
|
||||||
|
preserveFocus: false,
|
||||||
|
preview: true,
|
||||||
|
viewColumn: (window.activeTextEditor && window.activeTextEditor.viewColumn) || 1
|
||||||
|
};
|
||||||
|
|
||||||
|
const document = await workspace.openTextDocument(uri);
|
||||||
|
return window.showTextDocument(document, { ...defaults, ...(options || {}) });
|
||||||
|
}
|
||||||
|
catch (ex) {
|
||||||
|
Logger.error(ex, 'openEditor');
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
78
src/commands/copyMessageToClipboard.ts
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
'use strict';
|
||||||
|
import { Iterables } from '../system';
|
||||||
|
import { TextEditor, Uri, window } from 'vscode';
|
||||||
|
import { ActiveEditorCommand, Commands, getCommandUri } from './common';
|
||||||
|
import { GitService, GitUri } from '../gitService';
|
||||||
|
import { Logger } from '../logger';
|
||||||
|
import { copy } from 'copy-paste';
|
||||||
|
|
||||||
|
export interface CopyMessageToClipboardCommandArgs {
|
||||||
|
message?: string;
|
||||||
|
sha?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CopyMessageToClipboardCommand extends ActiveEditorCommand {
|
||||||
|
|
||||||
|
constructor(private git: GitService) {
|
||||||
|
super(Commands.CopyMessageToClipboard);
|
||||||
|
}
|
||||||
|
|
||||||
|
async execute(editor: TextEditor, uri?: Uri, args: CopyMessageToClipboardCommandArgs = {}): Promise<any> {
|
||||||
|
uri = getCommandUri(uri, editor);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// If we don't have an editor then get the message of the last commit to the branch
|
||||||
|
if (uri === undefined) {
|
||||||
|
if (!this.git.repoPath) return undefined;
|
||||||
|
|
||||||
|
const log = await this.git.getLogForRepo(this.git.repoPath, undefined, 1);
|
||||||
|
if (!log) return undefined;
|
||||||
|
|
||||||
|
args.message = Iterables.first(log.commits.values()).message;
|
||||||
|
copy(args.message);
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const gitUri = await GitUri.fromUri(uri, this.git);
|
||||||
|
|
||||||
|
if (args.message === undefined) {
|
||||||
|
if (args.sha === undefined) {
|
||||||
|
if (editor !== undefined && editor.document !== undefined && editor.document.isDirty) return undefined;
|
||||||
|
|
||||||
|
const line = (editor && editor.selection.active.line) || gitUri.offset;
|
||||||
|
const blameline = line - gitUri.offset;
|
||||||
|
if (blameline < 0) return undefined;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const blame = await this.git.getBlameForLine(gitUri, blameline);
|
||||||
|
if (!blame) return undefined;
|
||||||
|
|
||||||
|
if (blame.commit.isUncommitted) return undefined;
|
||||||
|
|
||||||
|
args.sha = blame.commit.sha;
|
||||||
|
if (!gitUri.repoPath) {
|
||||||
|
gitUri.repoPath = blame.commit.repoPath;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (ex) {
|
||||||
|
Logger.error(ex, 'CopyMessageToClipboardCommand', `getBlameForLine(${blameline})`);
|
||||||
|
return window.showErrorMessage(`Unable to copy message. See output channel for more details`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the full commit message -- since blame only returns the summary
|
||||||
|
const commit = await this.git.getLogCommit(gitUri.repoPath, gitUri.fsPath, args.sha);
|
||||||
|
if (!commit) return undefined;
|
||||||
|
|
||||||
|
args.message = commit.message;
|
||||||
|
}
|
||||||
|
|
||||||
|
copy(args.message);
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
catch (ex) {
|
||||||
|
Logger.error(ex, 'CopyMessageToClipboardCommand');
|
||||||
|
return window.showErrorMessage(`Unable to copy message. See output channel for more details`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
64
src/commands/copyShaToClipboard.ts
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
'use strict';
|
||||||
|
import { Iterables } from '../system';
|
||||||
|
import { TextEditor, Uri, window } from 'vscode';
|
||||||
|
import { ActiveEditorCommand, Commands, getCommandUri } from './common';
|
||||||
|
import { GitService, GitUri } from '../gitService';
|
||||||
|
import { Logger } from '../logger';
|
||||||
|
import { copy } from 'copy-paste';
|
||||||
|
|
||||||
|
export interface CopyShaToClipboardCommandArgs {
|
||||||
|
sha?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CopyShaToClipboardCommand extends ActiveEditorCommand {
|
||||||
|
|
||||||
|
constructor(private git: GitService) {
|
||||||
|
super(Commands.CopyShaToClipboard);
|
||||||
|
}
|
||||||
|
|
||||||
|
async execute(editor: TextEditor, uri?: Uri, args: CopyShaToClipboardCommandArgs = {}): Promise<any> {
|
||||||
|
uri = getCommandUri(uri, editor);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// If we don't have an editor then get the sha of the last commit to the branch
|
||||||
|
if (uri === undefined) {
|
||||||
|
if (!this.git.repoPath) return undefined;
|
||||||
|
|
||||||
|
const log = await this.git.getLogForRepo(this.git.repoPath, undefined, 1);
|
||||||
|
if (!log) return undefined;
|
||||||
|
|
||||||
|
args.sha = Iterables.first(log.commits.values()).sha;
|
||||||
|
copy(args.sha);
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const gitUri = await GitUri.fromUri(uri, this.git);
|
||||||
|
|
||||||
|
if (args.sha === undefined) {
|
||||||
|
if (editor !== undefined && editor.document !== undefined && editor.document.isDirty) return undefined;
|
||||||
|
|
||||||
|
const line = (editor && editor.selection.active.line) || gitUri.offset;
|
||||||
|
const blameline = line - gitUri.offset;
|
||||||
|
if (blameline < 0) return undefined;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const blame = await this.git.getBlameForLine(gitUri, blameline);
|
||||||
|
if (!blame) return undefined;
|
||||||
|
|
||||||
|
args.sha = blame.commit.sha;
|
||||||
|
}
|
||||||
|
catch (ex) {
|
||||||
|
Logger.error(ex, 'CopyShaToClipboardCommand', `getBlameForLine(${blameline})`);
|
||||||
|
return window.showErrorMessage(`Unable to copy commit id. See output channel for more details`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
copy(args.sha);
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
catch (ex) {
|
||||||
|
Logger.error(ex, 'CopyShaToClipboardCommand');
|
||||||
|
return window.showErrorMessage(`Unable to copy commit id. See output channel for more details`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
58
src/commands/diffDirectory.ts
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
'use strict';
|
||||||
|
import { Iterables } from '../system';
|
||||||
|
import { commands, TextEditor, Uri, window } from 'vscode';
|
||||||
|
import { ActiveEditorCommand, Commands, getCommandUri } from './common';
|
||||||
|
import { BuiltInCommands } from '../constants';
|
||||||
|
import { GitService } from '../gitService';
|
||||||
|
import { Logger } from '../logger';
|
||||||
|
import { CommandQuickPickItem, BranchesQuickPick } from '../quickPicks';
|
||||||
|
|
||||||
|
export interface DiffDirectoryCommandCommandArgs {
|
||||||
|
shaOrBranch1?: string;
|
||||||
|
shaOrBranch2?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class DiffDirectoryCommand extends ActiveEditorCommand {
|
||||||
|
|
||||||
|
constructor(private git: GitService) {
|
||||||
|
super(Commands.DiffDirectory);
|
||||||
|
}
|
||||||
|
|
||||||
|
async execute(editor: TextEditor, uri?: Uri, args: DiffDirectoryCommandCommandArgs = {}): Promise<any> {
|
||||||
|
const diffTool = await this.git.getConfig('diff.tool');
|
||||||
|
if (!diffTool) {
|
||||||
|
const result = await window.showWarningMessage(`Unable to open directory compare because there is no Git diff tool configured`, 'View Git Docs');
|
||||||
|
if (!result) return undefined;
|
||||||
|
|
||||||
|
return commands.executeCommand(BuiltInCommands.Open, Uri.parse('https://git-scm.com/docs/git-config#git-config-difftool'));
|
||||||
|
}
|
||||||
|
|
||||||
|
uri = getCommandUri(uri, editor);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const repoPath = await this.git.getRepoPathFromUri(uri);
|
||||||
|
if (!repoPath) return window.showWarningMessage(`Unable to open directory compare`);
|
||||||
|
|
||||||
|
if (!args.shaOrBranch1) {
|
||||||
|
const branches = await this.git.getBranches(repoPath);
|
||||||
|
const current = Iterables.find(branches, _ => _.current);
|
||||||
|
if (current == null) return window.showWarningMessage(`Unable to open directory compare`);
|
||||||
|
|
||||||
|
const pick = await BranchesQuickPick.show(branches, `Compare ${current.name} to \u2026`);
|
||||||
|
if (pick === undefined) return undefined;
|
||||||
|
|
||||||
|
if (pick instanceof CommandQuickPickItem) return pick.execute();
|
||||||
|
|
||||||
|
args.shaOrBranch1 = pick.branch.name;
|
||||||
|
if (args.shaOrBranch1 === undefined) return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.git.openDirectoryDiff(repoPath, args.shaOrBranch1, args.shaOrBranch2);
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
catch (ex) {
|
||||||
|
Logger.error(ex, 'DiffDirectoryCommand');
|
||||||
|
return window.showErrorMessage(`Unable to open directory compare. See output channel for more details`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
89
src/commands/diffLineWithPrevious.ts
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
'use strict';
|
||||||
|
import { commands, TextDocumentShowOptions, TextEditor, Uri, window } from 'vscode';
|
||||||
|
import { ActiveEditorCommand, Commands, getCommandUri } from './common';
|
||||||
|
import { BuiltInCommands } from '../constants';
|
||||||
|
import { DiffWithPreviousCommandArgs } from './diffWithPrevious';
|
||||||
|
import { DiffWithWorkingCommandArgs } from './diffWithWorking';
|
||||||
|
import { GitCommit, GitService, GitUri } from '../gitService';
|
||||||
|
import { Logger } from '../logger';
|
||||||
|
import * as path from 'path';
|
||||||
|
|
||||||
|
export interface DiffLineWithPreviousCommandArgs {
|
||||||
|
commit?: GitCommit;
|
||||||
|
line?: number;
|
||||||
|
showOptions?: TextDocumentShowOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class DiffLineWithPreviousCommand extends ActiveEditorCommand {
|
||||||
|
|
||||||
|
constructor(private git: GitService) {
|
||||||
|
super(Commands.DiffLineWithPrevious);
|
||||||
|
}
|
||||||
|
|
||||||
|
async execute(editor: TextEditor, uri?: Uri, args: DiffLineWithPreviousCommandArgs = {}): Promise<any> {
|
||||||
|
uri = getCommandUri(uri, editor);
|
||||||
|
if (uri === undefined) return undefined;
|
||||||
|
|
||||||
|
const gitUri = await GitUri.fromUri(uri, this.git);
|
||||||
|
args.line = args.line || (editor === undefined ? gitUri.offset : editor.selection.active.line);
|
||||||
|
|
||||||
|
if (args.commit === undefined || GitService.isUncommitted(args.commit.sha)) {
|
||||||
|
if (editor !== undefined && editor.document !== undefined && editor.document.isDirty) return undefined;
|
||||||
|
|
||||||
|
const blameline = args.line - gitUri.offset;
|
||||||
|
if (blameline < 0) return undefined;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const blame = await this.git.getBlameForLine(gitUri, blameline);
|
||||||
|
if (blame === undefined) return window.showWarningMessage(`Unable to open compare. File is probably not under source control`);
|
||||||
|
|
||||||
|
args.commit = blame.commit;
|
||||||
|
|
||||||
|
// If we don't have a sha or the current commit matches the blame, show the previous
|
||||||
|
if (gitUri.sha === undefined || gitUri.sha === args.commit.sha) {
|
||||||
|
return commands.executeCommand(Commands.DiffWithPrevious, new GitUri(uri, args.commit), {
|
||||||
|
line: args.line,
|
||||||
|
showOptions: args.showOptions
|
||||||
|
} as DiffWithPreviousCommandArgs);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the line is uncommitted, find the previous commit and treat it as a DiffWithWorking
|
||||||
|
if (args.commit.isUncommitted) {
|
||||||
|
uri = args.commit.uri;
|
||||||
|
args.commit = new GitCommit(args.commit.type, args.commit.repoPath, args.commit.previousSha!, args.commit.previousFileName!, args.commit.author, args.commit.date, args.commit.message);
|
||||||
|
args.line = (blame.line.line + 1) + gitUri.offset;
|
||||||
|
|
||||||
|
return commands.executeCommand(Commands.DiffWithWorking, uri, {
|
||||||
|
commit: args.commit,
|
||||||
|
line: args.line,
|
||||||
|
showOptions: args.showOptions
|
||||||
|
} as DiffWithWorkingCommandArgs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (ex) {
|
||||||
|
Logger.error(ex, 'DiffWithPreviousLineCommand', `getBlameForLine(${blameline})`);
|
||||||
|
return window.showErrorMessage(`Unable to open compare. See output channel for more details`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const [rhs, lhs] = await Promise.all([
|
||||||
|
this.git.getVersionedFile(gitUri.repoPath, gitUri.fsPath, gitUri.sha!),
|
||||||
|
this.git.getVersionedFile(args.commit.repoPath, args.commit.uri.fsPath, args.commit.sha)
|
||||||
|
]);
|
||||||
|
|
||||||
|
await commands.executeCommand(BuiltInCommands.Diff,
|
||||||
|
Uri.file(lhs),
|
||||||
|
Uri.file(rhs),
|
||||||
|
`${path.basename(args.commit.uri.fsPath)} (${args.commit.shortSha}) \u2194 ${path.basename(gitUri.fsPath)} (${gitUri.shortSha})`,
|
||||||
|
args.showOptions);
|
||||||
|
|
||||||
|
// TODO: Figure out how to focus the left pane
|
||||||
|
return await commands.executeCommand(BuiltInCommands.RevealLine, { lineNumber: args.line, at: 'center' });
|
||||||
|
}
|
||||||
|
catch (ex) {
|
||||||
|
Logger.error(ex, 'DiffWithPreviousLineCommand', 'getVersionedFile');
|
||||||
|
return window.showErrorMessage(`Unable to open compare. See output channel for more details`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
52
src/commands/diffLineWithWorking.ts
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
'use strict';
|
||||||
|
import { commands, TextDocumentShowOptions, TextEditor, Uri, window } from 'vscode';
|
||||||
|
import { ActiveEditorCommand, Commands, getCommandUri } from './common';
|
||||||
|
import { DiffWithWorkingCommandArgs } from './diffWithWorking';
|
||||||
|
import { GitCommit, GitService, GitUri } from '../gitService';
|
||||||
|
import { Logger } from '../logger';
|
||||||
|
|
||||||
|
export interface DiffLineWithWorkingCommandArgs {
|
||||||
|
commit?: GitCommit;
|
||||||
|
line?: number;
|
||||||
|
showOptions?: TextDocumentShowOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class DiffLineWithWorkingCommand extends ActiveEditorCommand {
|
||||||
|
|
||||||
|
constructor(private git: GitService) {
|
||||||
|
super(Commands.DiffLineWithWorking);
|
||||||
|
}
|
||||||
|
|
||||||
|
async execute(editor: TextEditor, uri?: Uri, args: DiffLineWithWorkingCommandArgs = {}): Promise<any> {
|
||||||
|
uri = getCommandUri(uri, editor);
|
||||||
|
if (uri === undefined) return undefined;
|
||||||
|
|
||||||
|
const gitUri = await GitUri.fromUri(uri, this.git);
|
||||||
|
args.line = args.line || (editor === undefined ? gitUri.offset : editor.selection.active.line);
|
||||||
|
|
||||||
|
if (args.commit === undefined || GitService.isUncommitted(args.commit.sha)) {
|
||||||
|
if (editor !== undefined && editor.document !== undefined && editor.document.isDirty) return undefined;
|
||||||
|
|
||||||
|
const blameline = args.line - gitUri.offset;
|
||||||
|
if (blameline < 0) return undefined;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const blame = await this.git.getBlameForLine(gitUri, blameline);
|
||||||
|
if (blame === undefined) return window.showWarningMessage(`Unable to open compare. File is probably not under source control`);
|
||||||
|
|
||||||
|
args.commit = blame.commit;
|
||||||
|
// If the line is uncommitted, find the previous commit
|
||||||
|
if (args.commit.isUncommitted) {
|
||||||
|
args.commit = new GitCommit(args.commit.type, args.commit.repoPath, args.commit.previousSha!, args.commit.previousFileName!, args.commit.author, args.commit.date, args.commit.message);
|
||||||
|
args.line = blame.line.line + 1 + gitUri.offset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (ex) {
|
||||||
|
Logger.error(ex, 'DiffLineWithWorkingCommand', `getBlameForLine(${blameline})`);
|
||||||
|
return window.showErrorMessage(`Unable to open compare. See output channel for more details`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return commands.executeCommand(Commands.DiffWithWorking, uri, args as DiffWithWorkingCommandArgs);
|
||||||
|
}
|
||||||
|
}
|
||||||
58
src/commands/diffWithBranch.ts
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
'use strict';
|
||||||
|
import { commands, TextDocumentShowOptions, TextEditor, Uri, window } from 'vscode';
|
||||||
|
import { ActiveEditorCommand, Commands, getCommandUri } from './common';
|
||||||
|
import { BuiltInCommands } from '../constants';
|
||||||
|
import { GitService, GitUri } from '../gitService';
|
||||||
|
import { Logger } from '../logger';
|
||||||
|
import { CommandQuickPickItem, BranchesQuickPick } from '../quickPicks';
|
||||||
|
import * as path from 'path';
|
||||||
|
|
||||||
|
export interface DiffWithBranchCommandArgs {
|
||||||
|
line?: number;
|
||||||
|
showOptions?: TextDocumentShowOptions;
|
||||||
|
|
||||||
|
goBackCommand?: CommandQuickPickItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class DiffWithBranchCommand extends ActiveEditorCommand {
|
||||||
|
|
||||||
|
constructor(private git: GitService) {
|
||||||
|
super(Commands.DiffWithBranch);
|
||||||
|
}
|
||||||
|
|
||||||
|
async execute(editor: TextEditor, uri?: Uri, args: DiffWithBranchCommandArgs = {}): Promise<any> {
|
||||||
|
uri = getCommandUri(uri, editor);
|
||||||
|
if (uri === undefined) return undefined;
|
||||||
|
|
||||||
|
args.line = args.line || (editor === undefined ? 0 : editor.selection.active.line);
|
||||||
|
|
||||||
|
const gitUri = await GitUri.fromUri(uri, this.git);
|
||||||
|
if (!gitUri.repoPath) return window.showWarningMessage(`Unable to open branch compare`);
|
||||||
|
|
||||||
|
const branches = await this.git.getBranches(gitUri.repoPath);
|
||||||
|
const pick = await BranchesQuickPick.show(branches, `Compare ${path.basename(gitUri.fsPath)} to \u2026`, args.goBackCommand);
|
||||||
|
if (pick === undefined) return undefined;
|
||||||
|
|
||||||
|
if (pick instanceof CommandQuickPickItem) return pick.execute();
|
||||||
|
|
||||||
|
const branch = pick.branch.name;
|
||||||
|
if (branch === undefined) return undefined;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const compare = await this.git.getVersionedFile(gitUri.repoPath, gitUri.fsPath, branch);
|
||||||
|
|
||||||
|
await commands.executeCommand(BuiltInCommands.Diff,
|
||||||
|
Uri.file(compare),
|
||||||
|
gitUri.fileUri(),
|
||||||
|
`${path.basename(gitUri.fsPath)} (${branch}) \u2194 ${path.basename(gitUri.fsPath)}`,
|
||||||
|
args.showOptions);
|
||||||
|
|
||||||
|
// TODO: Figure out how to focus the left pane
|
||||||
|
return await commands.executeCommand(BuiltInCommands.RevealLine, { lineNumber: args.line, at: 'center' });
|
||||||
|
}
|
||||||
|
catch (ex) {
|
||||||
|
Logger.error(ex, 'DiffWithBranchCommand', 'getVersionedFile');
|
||||||
|
return window.showErrorMessage(`Unable to open branch compare. See output channel for more details`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
73
src/commands/diffWithNext.ts
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
'use strict';
|
||||||
|
import { Iterables } from '../system';
|
||||||
|
import { commands, Range, TextDocumentShowOptions, TextEditor, Uri, window } from 'vscode';
|
||||||
|
import { ActiveEditorCommand, Commands, getCommandUri } from './common';
|
||||||
|
import { BuiltInCommands } from '../constants';
|
||||||
|
import { GitLogCommit, GitService, GitUri } from '../gitService';
|
||||||
|
import { Logger } from '../logger';
|
||||||
|
import * as path from 'path';
|
||||||
|
|
||||||
|
export interface DiffWithNextCommandArgs {
|
||||||
|
commit?: GitLogCommit;
|
||||||
|
line?: number;
|
||||||
|
range?: Range;
|
||||||
|
showOptions?: TextDocumentShowOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class DiffWithNextCommand extends ActiveEditorCommand {
|
||||||
|
|
||||||
|
constructor(private git: GitService) {
|
||||||
|
super(Commands.DiffWithNext);
|
||||||
|
}
|
||||||
|
|
||||||
|
async execute(editor: TextEditor, uri?: Uri, args: DiffWithNextCommandArgs = {}): Promise<any> {
|
||||||
|
uri = getCommandUri(uri, editor);
|
||||||
|
if (uri === undefined) return undefined;
|
||||||
|
|
||||||
|
args.line = args.line || (editor === undefined ? 0 : editor.selection.active.line);
|
||||||
|
|
||||||
|
if (args.commit === undefined || !(args.commit instanceof GitLogCommit) || args.range !== undefined) {
|
||||||
|
const gitUri = await GitUri.fromUri(uri, this.git);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// If the sha is missing or the file is uncommitted, treat it as a DiffWithWorking
|
||||||
|
if (gitUri.sha === undefined && await this.git.isFileUncommitted(gitUri)) {
|
||||||
|
return commands.executeCommand(Commands.DiffWithWorking, uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
const sha = args.commit === undefined ? gitUri.sha : args.commit.sha;
|
||||||
|
|
||||||
|
const log = await this.git.getLogForFile(gitUri.repoPath, gitUri.fsPath, undefined, sha ? undefined : 2, args.range!);
|
||||||
|
if (log === undefined) return window.showWarningMessage(`Unable to open compare. File is probably not under source control`);
|
||||||
|
|
||||||
|
args.commit = (sha && log.commits.get(sha)) || Iterables.first(log.commits.values());
|
||||||
|
}
|
||||||
|
catch (ex) {
|
||||||
|
Logger.error(ex, 'DiffWithNextCommand', `getLogForFile(${gitUri.repoPath}, ${gitUri.fsPath})`);
|
||||||
|
return window.showErrorMessage(`Unable to open compare. See output channel for more details`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.commit.nextSha === undefined) return commands.executeCommand(Commands.DiffWithWorking, uri);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const [rhs, lhs] = await Promise.all([
|
||||||
|
this.git.getVersionedFile(args.commit.repoPath, args.commit.nextUri.fsPath, args.commit.nextSha),
|
||||||
|
this.git.getVersionedFile(args.commit.repoPath, args.commit.uri.fsPath, args.commit.sha)
|
||||||
|
]);
|
||||||
|
|
||||||
|
await commands.executeCommand(BuiltInCommands.Diff,
|
||||||
|
Uri.file(lhs),
|
||||||
|
Uri.file(rhs),
|
||||||
|
`${path.basename(args.commit.uri.fsPath)} (${args.commit.shortSha}) \u2194 ${path.basename(args.commit.nextUri.fsPath)} (${args.commit.nextShortSha})`,
|
||||||
|
args.showOptions);
|
||||||
|
|
||||||
|
// TODO: Figure out how to focus the left pane
|
||||||
|
return await commands.executeCommand(BuiltInCommands.RevealLine, { lineNumber: args.line, at: 'center' });
|
||||||
|
}
|
||||||
|
catch (ex) {
|
||||||
|
Logger.error(ex, 'DiffWithNextCommand', 'getVersionedFile');
|
||||||
|
return window.showErrorMessage(`Unable to open compare. See output channel for more details`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
75
src/commands/diffWithPrevious.ts
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
'use strict';
|
||||||
|
import { Iterables } from '../system';
|
||||||
|
import { commands, Range, TextDocumentShowOptions, TextEditor, Uri, window } from 'vscode';
|
||||||
|
import { ActiveEditorCommand, Commands, getCommandUri } from './common';
|
||||||
|
import { BuiltInCommands } from '../constants';
|
||||||
|
import { DiffWithWorkingCommandArgs } from './diffWithWorking';
|
||||||
|
import { GitCommit, GitService, GitUri } from '../gitService';
|
||||||
|
import { Logger } from '../logger';
|
||||||
|
import * as moment from 'moment';
|
||||||
|
import * as path from 'path';
|
||||||
|
|
||||||
|
export interface DiffWithPreviousCommandArgs {
|
||||||
|
commit?: GitCommit;
|
||||||
|
line?: number;
|
||||||
|
range?: Range;
|
||||||
|
showOptions?: TextDocumentShowOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class DiffWithPreviousCommand extends ActiveEditorCommand {
|
||||||
|
|
||||||
|
constructor(private git: GitService) {
|
||||||
|
super(Commands.DiffWithPrevious);
|
||||||
|
}
|
||||||
|
|
||||||
|
async execute(editor: TextEditor, uri?: Uri, args: DiffWithPreviousCommandArgs = {}): Promise<any> {
|
||||||
|
uri = getCommandUri(uri, editor);
|
||||||
|
if (uri === undefined) return undefined;
|
||||||
|
|
||||||
|
args.line = args.line || (editor === undefined ? 0 : editor.selection.active.line);
|
||||||
|
|
||||||
|
if (args.commit === undefined || (args.commit.type !== 'file') || args.range !== undefined) {
|
||||||
|
const gitUri = await GitUri.fromUri(uri, this.git);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// If the sha is missing or the file is uncommitted, treat it as a DiffWithWorking
|
||||||
|
if (gitUri.sha === undefined && await this.git.isFileUncommitted(gitUri)) {
|
||||||
|
return commands.executeCommand(Commands.DiffWithWorking, uri, { showOptions: args.showOptions } as DiffWithWorkingCommandArgs);
|
||||||
|
}
|
||||||
|
|
||||||
|
const sha = args.commit === undefined ? gitUri.sha : args.commit.sha;
|
||||||
|
|
||||||
|
const log = await this.git.getLogForFile(gitUri.repoPath, gitUri.fsPath, undefined, sha ? undefined : 2, args.range!);
|
||||||
|
if (log === undefined) return window.showWarningMessage(`Unable to open compare. File is probably not under source control`);
|
||||||
|
|
||||||
|
args.commit = (sha && log.commits.get(sha)) || Iterables.first(log.commits.values());
|
||||||
|
}
|
||||||
|
catch (ex) {
|
||||||
|
Logger.error(ex, 'DiffWithPreviousCommand', `getLogForFile(${gitUri.repoPath}, ${gitUri.fsPath})`);
|
||||||
|
return window.showErrorMessage(`Unable to open compare. See output channel for more details`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.commit.previousSha === undefined) return window.showInformationMessage(`Commit ${args.commit.shortSha} (${args.commit.author}, ${moment(args.commit.date).fromNow()}) has no previous commit`);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const [rhs, lhs] = await Promise.all([
|
||||||
|
this.git.getVersionedFile(args.commit.repoPath, args.commit.uri.fsPath, args.commit.sha),
|
||||||
|
this.git.getVersionedFile(args.commit.repoPath, args.commit.previousUri.fsPath, args.commit.previousSha)
|
||||||
|
]);
|
||||||
|
|
||||||
|
await commands.executeCommand(BuiltInCommands.Diff,
|
||||||
|
Uri.file(lhs),
|
||||||
|
Uri.file(rhs),
|
||||||
|
`${path.basename(args.commit.previousUri.fsPath)} (${args.commit.previousShortSha}) \u2194 ${path.basename(args.commit.uri.fsPath)} (${args.commit.shortSha})`,
|
||||||
|
args.showOptions);
|
||||||
|
|
||||||
|
// TODO: Figure out how to focus the left pane
|
||||||
|
return await commands.executeCommand(BuiltInCommands.RevealLine, { lineNumber: args.line, at: 'center' });
|
||||||
|
}
|
||||||
|
catch (ex) {
|
||||||
|
Logger.error(ex, 'DiffWithPreviousCommand', 'getVersionedFile');
|
||||||
|
return window.showErrorMessage(`Unable to open compare. See output channel for more details`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
62
src/commands/diffWithWorking.ts
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
'use strict';
|
||||||
|
import { commands, TextDocumentShowOptions, TextEditor, Uri, window } from 'vscode';
|
||||||
|
import { ActiveEditorCommand, Commands, getCommandUri } from './common';
|
||||||
|
import { BuiltInCommands } from '../constants';
|
||||||
|
import { GitCommit, GitService, GitUri } from '../gitService';
|
||||||
|
import { Logger } from '../logger';
|
||||||
|
import * as path from 'path';
|
||||||
|
|
||||||
|
export interface DiffWithWorkingCommandArgs {
|
||||||
|
commit?: GitCommit;
|
||||||
|
line?: number;
|
||||||
|
showOptions?: TextDocumentShowOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class DiffWithWorkingCommand extends ActiveEditorCommand {
|
||||||
|
|
||||||
|
constructor(private git: GitService) {
|
||||||
|
super(Commands.DiffWithWorking);
|
||||||
|
}
|
||||||
|
|
||||||
|
async execute(editor: TextEditor, uri?: Uri, args: DiffWithWorkingCommandArgs = {}): Promise<any> {
|
||||||
|
uri = getCommandUri(uri, editor);
|
||||||
|
if (uri === undefined) return undefined;
|
||||||
|
|
||||||
|
args.line = args.line || (editor === undefined ? 0 : editor.selection.active.line);
|
||||||
|
|
||||||
|
if (args.commit === undefined || GitService.isUncommitted(args.commit.sha)) {
|
||||||
|
const gitUri = await GitUri.fromUri(uri, this.git);
|
||||||
|
|
||||||
|
try {
|
||||||
|
args.commit = await this.git.getLogCommit(gitUri.repoPath, gitUri.fsPath, gitUri.sha, { firstIfMissing: true });
|
||||||
|
if (args.commit === undefined) return window.showWarningMessage(`Unable to open compare. File is probably not under source control`);
|
||||||
|
}
|
||||||
|
catch (ex) {
|
||||||
|
Logger.error(ex, 'DiffWithWorkingCommand', `getLogCommit(${gitUri.repoPath}, ${gitUri.fsPath}, ${gitUri.sha})`);
|
||||||
|
return window.showErrorMessage(`Unable to open compare. See output channel for more details`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const gitUri = await GitUri.fromUri(uri, this.git);
|
||||||
|
|
||||||
|
const workingFileName = await this.git.findWorkingFileName(gitUri.repoPath, gitUri.fsPath);
|
||||||
|
if (workingFileName === undefined) return undefined;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const compare = await this.git.getVersionedFile(args.commit.repoPath, args.commit.uri.fsPath, args.commit.sha);
|
||||||
|
|
||||||
|
await commands.executeCommand(BuiltInCommands.Diff,
|
||||||
|
Uri.file(compare),
|
||||||
|
Uri.file(path.resolve(gitUri.repoPath, workingFileName)),
|
||||||
|
`${path.basename(args.commit.uri.fsPath)} (${args.commit.shortSha}) \u2194 ${path.basename(workingFileName)}`,
|
||||||
|
args.showOptions);
|
||||||
|
|
||||||
|
// TODO: Figure out how to focus the left pane
|
||||||
|
return await commands.executeCommand(BuiltInCommands.RevealLine, { lineNumber: args.line, at: 'center' });
|
||||||
|
}
|
||||||
|
catch (ex) {
|
||||||
|
Logger.error(ex, 'DiffWithWorkingCommand', 'getVersionedFile');
|
||||||
|
return window.showErrorMessage(`Unable to open compare. See output channel for more details`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
137
src/commands/keyboard.ts
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
'use strict';
|
||||||
|
import { commands, Disposable } from 'vscode';
|
||||||
|
import { CommandContext, setCommandContext } from './common';
|
||||||
|
import { ExtensionKey } from '../constants';
|
||||||
|
import { QuickPickItem } from '../quickPicks';
|
||||||
|
import { Logger } from '../logger';
|
||||||
|
|
||||||
|
const keyNoopCommand = Object.create(null) as QuickPickItem;
|
||||||
|
export { keyNoopCommand as KeyNoopCommand };
|
||||||
|
|
||||||
|
export declare type Keys = 'left' | 'right' | ',' | '.';
|
||||||
|
export const keys: Keys[] = [
|
||||||
|
'left',
|
||||||
|
'right',
|
||||||
|
',',
|
||||||
|
'.'
|
||||||
|
];
|
||||||
|
|
||||||
|
export declare type KeyMapping = { [id: string]: (QuickPickItem | (() => Promise<QuickPickItem>) | undefined) };
|
||||||
|
let mappings: KeyMapping[] = [];
|
||||||
|
|
||||||
|
let _instance: Keyboard;
|
||||||
|
|
||||||
|
export class KeyboardScope extends Disposable {
|
||||||
|
|
||||||
|
constructor(private mapping: KeyMapping) {
|
||||||
|
super(() => this.dispose());
|
||||||
|
|
||||||
|
for (const key in mapping) {
|
||||||
|
mapping[key] = mapping[key] || keyNoopCommand;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async dispose() {
|
||||||
|
const index = mappings.indexOf(this.mapping);
|
||||||
|
Logger.log('KeyboardScope.dispose', mappings.length, index);
|
||||||
|
if (index === (mappings.length - 1)) {
|
||||||
|
mappings.pop();
|
||||||
|
await this.updateKeyCommandsContext(mappings[mappings.length - 1]);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
mappings.splice(index, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async begin() {
|
||||||
|
mappings.push(this.mapping);
|
||||||
|
await this.updateKeyCommandsContext(this.mapping);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
async clearKeyCommand(key: Keys) {
|
||||||
|
const mapping = mappings[mappings.length - 1];
|
||||||
|
if (mapping !== this.mapping || !mapping[key]) return;
|
||||||
|
|
||||||
|
Logger.log('KeyboardScope.clearKeyCommand', mappings.length, key);
|
||||||
|
mapping[key] = undefined;
|
||||||
|
await setCommandContext(`${CommandContext.Key}:${key}`, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
async setKeyCommand(key: Keys, command: QuickPickItem | (() => Promise<QuickPickItem>)) {
|
||||||
|
const mapping = mappings[mappings.length - 1];
|
||||||
|
if (mapping !== this.mapping) return;
|
||||||
|
|
||||||
|
Logger.log('KeyboardScope.setKeyCommand', mappings.length, key, !!mapping[key]);
|
||||||
|
|
||||||
|
if (!mapping[key]) {
|
||||||
|
mapping[key] = command;
|
||||||
|
await setCommandContext(`${CommandContext.Key}:${key}`, true);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
mapping[key] = command;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async updateKeyCommandsContext(mapping: KeyMapping) {
|
||||||
|
const promises = [];
|
||||||
|
for (const key of keys) {
|
||||||
|
promises.push(setCommandContext(`${CommandContext.Key}:${key}`, !!(mapping && mapping[key])));
|
||||||
|
}
|
||||||
|
await Promise.all(promises);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Keyboard extends Disposable {
|
||||||
|
|
||||||
|
static get instance(): Keyboard {
|
||||||
|
return _instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _disposable: Disposable;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super(() => this.dispose());
|
||||||
|
|
||||||
|
const subscriptions: Disposable[] = [];
|
||||||
|
|
||||||
|
for (const key of keys) {
|
||||||
|
subscriptions.push(commands.registerCommand(`${ExtensionKey}.key.${key}`, () => this.execute(key), this));
|
||||||
|
}
|
||||||
|
|
||||||
|
this._disposable = Disposable.from(...subscriptions);
|
||||||
|
|
||||||
|
_instance = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
dispose() {
|
||||||
|
this._disposable && this._disposable.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
async beginScope(mapping?: KeyMapping): Promise<KeyboardScope> {
|
||||||
|
Logger.log('Keyboard.beginScope', mappings.length);
|
||||||
|
return await new KeyboardScope(mapping ? Object.assign(Object.create(null), mapping) : Object.create(null)).begin();
|
||||||
|
}
|
||||||
|
|
||||||
|
async execute(key: Keys): Promise<{} | undefined> {
|
||||||
|
if (!mappings.length) return undefined;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const mapping = mappings[mappings.length - 1];
|
||||||
|
|
||||||
|
let command = mapping[key] as QuickPickItem | (() => Promise<QuickPickItem>);
|
||||||
|
if (typeof command === 'function') {
|
||||||
|
command = await command();
|
||||||
|
}
|
||||||
|
if (!command || typeof command.onDidPressKey !== 'function') return undefined;
|
||||||
|
|
||||||
|
Logger.log('Keyboard.execute', key);
|
||||||
|
|
||||||
|
return await command.onDidPressKey(key);
|
||||||
|
}
|
||||||
|
catch (ex) {
|
||||||
|
Logger.error(ex, 'Keyboard.execute');
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
55
src/commands/openBranchInRemote.ts
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
'use strict';
|
||||||
|
import { Arrays } from '../system';
|
||||||
|
import { commands, TextEditor, TextEditorEdit, Uri, window } from 'vscode';
|
||||||
|
import { ActiveEditorCommand, Commands, getCommandUri } from './common';
|
||||||
|
import { GitService, GitUri } from '../gitService';
|
||||||
|
import { Logger } from '../logger';
|
||||||
|
import { BranchesQuickPick, CommandQuickPickItem } from '../quickPicks';
|
||||||
|
import { OpenInRemoteCommandArgs } from './openInRemote';
|
||||||
|
|
||||||
|
export interface OpenBranchInRemoteCommandArgs {
|
||||||
|
branch?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class OpenBranchInRemoteCommand extends ActiveEditorCommand {
|
||||||
|
|
||||||
|
constructor(private git: GitService) {
|
||||||
|
super(Commands.OpenBranchInRemote);
|
||||||
|
}
|
||||||
|
|
||||||
|
async execute(editor: TextEditor, edit: TextEditorEdit, uri?: Uri, args: OpenBranchInRemoteCommandArgs = {}) {
|
||||||
|
uri = getCommandUri(uri, editor);
|
||||||
|
|
||||||
|
const gitUri = uri && await GitUri.fromUri(uri, this.git);
|
||||||
|
|
||||||
|
const repoPath = gitUri === undefined ? this.git.repoPath : gitUri.repoPath;
|
||||||
|
if (!repoPath) return undefined;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (args.branch === undefined) {
|
||||||
|
const branches = await this.git.getBranches(repoPath);
|
||||||
|
|
||||||
|
const pick = await BranchesQuickPick.show(branches, `Show history for branch\u2026`);
|
||||||
|
if (pick === undefined) return undefined;
|
||||||
|
|
||||||
|
if (pick instanceof CommandQuickPickItem) return undefined;
|
||||||
|
|
||||||
|
args.branch = pick.branch.name;
|
||||||
|
if (args.branch === undefined) return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const remotes = Arrays.uniqueBy(await this.git.getRemotes(repoPath), _ => _.url, _ => !!_.provider);
|
||||||
|
return commands.executeCommand(Commands.OpenInRemote, uri, {
|
||||||
|
resource: {
|
||||||
|
type: 'branch',
|
||||||
|
branch: args.branch
|
||||||
|
},
|
||||||
|
remotes
|
||||||
|
} as OpenInRemoteCommandArgs);
|
||||||
|
}
|
||||||
|
catch (ex) {
|
||||||
|
Logger.error(ex, 'OpenBranchInRemoteCommandArgs');
|
||||||
|
return window.showErrorMessage(`Unable to open branch in remote provider. See output channel for more details`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
42
src/commands/openChangedFiles.ts
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
'use strict';
|
||||||
|
import { TextDocumentShowOptions, TextEditor, Uri, window } from 'vscode';
|
||||||
|
import { ActiveEditorCommand, Commands, getCommandUri, openEditor } from './common';
|
||||||
|
import { GitService } from '../gitService';
|
||||||
|
import { Logger } from '../logger';
|
||||||
|
|
||||||
|
export interface OpenChangedFilesCommandArgs {
|
||||||
|
uris?: Uri[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export class OpenChangedFilesCommand extends ActiveEditorCommand {
|
||||||
|
|
||||||
|
constructor(private git: GitService) {
|
||||||
|
super(Commands.OpenChangedFiles);
|
||||||
|
}
|
||||||
|
|
||||||
|
async execute(editor: TextEditor, uri?: Uri, args: OpenChangedFilesCommandArgs = {}) {
|
||||||
|
uri = getCommandUri(uri, editor);
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (args.uris === undefined) {
|
||||||
|
const repoPath = await this.git.getRepoPathFromUri(uri);
|
||||||
|
if (!repoPath) return window.showWarningMessage(`Unable to open changed files`);
|
||||||
|
|
||||||
|
const status = await this.git.getStatusForRepo(repoPath);
|
||||||
|
if (status === undefined) return window.showWarningMessage(`Unable to open changed files`);
|
||||||
|
|
||||||
|
args.uris = status.files.filter(_ => _.status !== 'D').map(_ => _.Uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const uri of args.uris) {
|
||||||
|
await openEditor(uri, { preserveFocus: true, preview: false } as TextDocumentShowOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
catch (ex) {
|
||||||
|
Logger.error(ex, 'OpenChangedFilesCommand');
|
||||||
|
return window.showErrorMessage(`Unable to open changed files. See output channel for more details`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
52
src/commands/openCommitInRemote.ts
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
'use strict';
|
||||||
|
import { Arrays } from '../system';
|
||||||
|
import { commands, TextEditor, TextEditorEdit, Uri, window } from 'vscode';
|
||||||
|
import { ActiveEditorCommand, Commands, getCommandUri } from './common';
|
||||||
|
import { GitCommit, GitService, GitUri } from '../gitService';
|
||||||
|
import { Logger } from '../logger';
|
||||||
|
import { OpenInRemoteCommandArgs } from './openInRemote';
|
||||||
|
|
||||||
|
export class OpenCommitInRemoteCommand extends ActiveEditorCommand {
|
||||||
|
|
||||||
|
constructor(private git: GitService) {
|
||||||
|
super(Commands.OpenCommitInRemote);
|
||||||
|
}
|
||||||
|
|
||||||
|
async execute(editor: TextEditor, edit: TextEditorEdit, uri?: Uri) {
|
||||||
|
uri = getCommandUri(uri, editor);
|
||||||
|
if (uri === undefined) return undefined;
|
||||||
|
if (editor !== undefined && editor.document !== undefined && editor.document.isDirty) return undefined;
|
||||||
|
|
||||||
|
const gitUri = await GitUri.fromUri(uri, this.git);
|
||||||
|
if (!gitUri.repoPath) return undefined;
|
||||||
|
|
||||||
|
const line = editor === undefined ? gitUri.offset : editor.selection.active.line;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const blameline = line - gitUri.offset;
|
||||||
|
if (blameline < 0) return undefined;
|
||||||
|
|
||||||
|
const blame = await this.git.getBlameForLine(gitUri, blameline);
|
||||||
|
if (blame === undefined) return window.showWarningMessage(`Unable to open commit in remote provider. File is probably not under source control`);
|
||||||
|
|
||||||
|
let commit = blame.commit;
|
||||||
|
// If the line is uncommitted, find the previous commit
|
||||||
|
if (commit.isUncommitted) {
|
||||||
|
commit = new GitCommit(commit.type, commit.repoPath, commit.previousSha!, commit.previousFileName!, commit.author, commit.date, commit.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
const remotes = Arrays.uniqueBy(await this.git.getRemotes(gitUri.repoPath), _ => _.url, _ => !!_.provider);
|
||||||
|
return commands.executeCommand(Commands.OpenInRemote, uri, {
|
||||||
|
resource: {
|
||||||
|
type: 'commit',
|
||||||
|
sha: commit.sha
|
||||||
|
},
|
||||||
|
remotes
|
||||||
|
} as OpenInRemoteCommandArgs);
|
||||||
|
}
|
||||||
|
catch (ex) {
|
||||||
|
Logger.error(ex, 'OpenCommitInRemoteCommand');
|
||||||
|
return window.showErrorMessage(`Unable to open commit in remote provider. See output channel for more details`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
44
src/commands/openFileInRemote.ts
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
'use strict';
|
||||||
|
import { Arrays } from '../system';
|
||||||
|
import { commands, Range, TextEditor, TextEditorEdit, Uri, window } from 'vscode';
|
||||||
|
import { ActiveEditorCommand, Commands, getCommandUri } from './common';
|
||||||
|
import { GitService, GitUri } from '../gitService';
|
||||||
|
import { Logger } from '../logger';
|
||||||
|
import { OpenInRemoteCommandArgs } from './openInRemote';
|
||||||
|
|
||||||
|
export class OpenFileInRemoteCommand extends ActiveEditorCommand {
|
||||||
|
|
||||||
|
constructor(private git: GitService) {
|
||||||
|
super(Commands.OpenFileInRemote);
|
||||||
|
}
|
||||||
|
|
||||||
|
async execute(editor: TextEditor, edit: TextEditorEdit, uri?: Uri) {
|
||||||
|
uri = getCommandUri(uri, editor);
|
||||||
|
if (uri === undefined) return undefined;
|
||||||
|
|
||||||
|
const gitUri = await GitUri.fromUri(uri, this.git);
|
||||||
|
if (!gitUri.repoPath) return undefined;
|
||||||
|
|
||||||
|
const branch = await this.git.getBranch(gitUri.repoPath);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const remotes = Arrays.uniqueBy(await this.git.getRemotes(gitUri.repoPath), _ => _.url, _ => !!_.provider);
|
||||||
|
const range = editor === undefined ? undefined : new Range(editor.selection.start.with({ line: editor.selection.start.line + 1 }), editor.selection.end.with({ line: editor.selection.end.line + 1 }));
|
||||||
|
|
||||||
|
return commands.executeCommand(Commands.OpenInRemote, uri, {
|
||||||
|
resource: {
|
||||||
|
type: 'file',
|
||||||
|
branch: branch === undefined ? 'Current' : branch.name,
|
||||||
|
fileName: gitUri.getRelativePath(),
|
||||||
|
range: range,
|
||||||
|
sha: gitUri.sha
|
||||||
|
},
|
||||||
|
remotes
|
||||||
|
} as OpenInRemoteCommandArgs);
|
||||||
|
}
|
||||||
|
catch (ex) {
|
||||||
|
Logger.error(ex, 'OpenFileInRemoteCommand');
|
||||||
|
return window.showErrorMessage(`Unable to open file in remote provider. See output channel for more details`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
93
src/commands/openInRemote.ts
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
'use strict';
|
||||||
|
import { TextEditor, Uri, window } from 'vscode';
|
||||||
|
import { ActiveEditorCommand, Commands, getCommandUri } from './common';
|
||||||
|
import { GitLogCommit, GitRemote, RemoteResource } from '../gitService';
|
||||||
|
import { Logger } from '../logger';
|
||||||
|
import { CommandQuickPickItem, OpenRemoteCommandQuickPickItem, RemotesQuickPick } from '../quickPicks';
|
||||||
|
|
||||||
|
export interface OpenInRemoteCommandArgs {
|
||||||
|
remotes?: GitRemote[];
|
||||||
|
resource?: RemoteResource;
|
||||||
|
|
||||||
|
goBackCommand?: CommandQuickPickItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class OpenInRemoteCommand extends ActiveEditorCommand {
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super(Commands.OpenInRemote);
|
||||||
|
}
|
||||||
|
|
||||||
|
async execute(editor: TextEditor, uri?: Uri, args: OpenInRemoteCommandArgs = {}) {
|
||||||
|
uri = getCommandUri(uri, editor);
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (args.remotes === undefined || args.resource === undefined) return undefined;
|
||||||
|
|
||||||
|
if (args.remotes.length === 1) {
|
||||||
|
const command = new OpenRemoteCommandQuickPickItem(args.remotes[0], args.resource);
|
||||||
|
return command.execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
let placeHolder: string = '';
|
||||||
|
switch (args.resource.type) {
|
||||||
|
case 'branch':
|
||||||
|
// Check to see if the remote is in the branch
|
||||||
|
const index = args.resource.branch.indexOf('/');
|
||||||
|
if (index >= 0) {
|
||||||
|
const remoteName = args.resource.branch.substring(0, index);
|
||||||
|
const remote = args.remotes.find(r => r.name === remoteName);
|
||||||
|
if (remote !== undefined) {
|
||||||
|
args.resource.branch = args.resource.branch.substring(index + 1);
|
||||||
|
args.remotes = [remote];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
placeHolder = `open ${args.resource.branch} branch in\u2026`;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'commit':
|
||||||
|
const shortSha = args.resource.sha.substring(0, 8);
|
||||||
|
placeHolder = `open commit ${shortSha} in\u2026`;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'file':
|
||||||
|
if (args.resource.commit !== undefined && args.resource.commit instanceof GitLogCommit) {
|
||||||
|
if (args.resource.commit.status === 'D') {
|
||||||
|
args.resource.sha = args.resource.commit.previousSha;
|
||||||
|
placeHolder = `open ${args.resource.fileName} \u00a0\u2022\u00a0 ${args.resource.commit.previousShortSha} in\u2026`;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
args.resource.sha = args.resource.commit.sha;
|
||||||
|
placeHolder = `open ${args.resource.fileName} \u00a0\u2022\u00a0 ${args.resource.commit.shortSha} in\u2026`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const shortFileSha = args.resource.sha === undefined ? '' : args.resource.sha.substring(0, 8);
|
||||||
|
const shaSuffix = shortFileSha ? ` \u00a0\u2022\u00a0 ${shortFileSha}` : '';
|
||||||
|
|
||||||
|
placeHolder = `open ${args.resource.fileName}${shaSuffix} in\u2026`;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'working-file':
|
||||||
|
placeHolder = `open ${args.resource.fileName} in\u2026`;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.remotes.length === 1) {
|
||||||
|
const command = new OpenRemoteCommandQuickPickItem(args.remotes[0], args.resource);
|
||||||
|
return command.execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
const pick = await RemotesQuickPick.show(args.remotes, placeHolder, args.resource, args.goBackCommand);
|
||||||
|
if (pick === undefined) return undefined;
|
||||||
|
|
||||||
|
return pick.execute();
|
||||||
|
|
||||||
|
}
|
||||||
|
catch (ex) {
|
||||||
|
Logger.error(ex, 'OpenInRemoteCommand');
|
||||||
|
return window.showErrorMessage(`Unable to open in remote provider. See output channel for more details`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
37
src/commands/openRepoInRemote.ts
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
'use strict';
|
||||||
|
import { Arrays } from '../system';
|
||||||
|
import { commands, TextEditor, TextEditorEdit, Uri, window } from 'vscode';
|
||||||
|
import { ActiveEditorCommand, Commands, getCommandUri } from './common';
|
||||||
|
import { GitService, GitUri } from '../gitService';
|
||||||
|
import { Logger } from '../logger';
|
||||||
|
import { OpenInRemoteCommandArgs } from './openInRemote';
|
||||||
|
|
||||||
|
export class OpenRepoInRemoteCommand extends ActiveEditorCommand {
|
||||||
|
|
||||||
|
constructor(private git: GitService) {
|
||||||
|
super(Commands.OpenRepoInRemote);
|
||||||
|
}
|
||||||
|
|
||||||
|
async execute(editor: TextEditor, edit: TextEditorEdit, uri?: Uri) {
|
||||||
|
uri = getCommandUri(uri, editor);
|
||||||
|
|
||||||
|
const gitUri = uri && await GitUri.fromUri(uri, this.git);
|
||||||
|
|
||||||
|
const repoPath = gitUri === undefined ? this.git.repoPath : gitUri.repoPath;
|
||||||
|
if (!repoPath) return undefined;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const remotes = Arrays.uniqueBy(await this.git.getRemotes(repoPath), _ => _.url, _ => !!_.provider);
|
||||||
|
return commands.executeCommand(Commands.OpenInRemote, uri, {
|
||||||
|
resource: {
|
||||||
|
type: 'repo'
|
||||||
|
},
|
||||||
|
remotes
|
||||||
|
} as OpenInRemoteCommandArgs);
|
||||||
|
}
|
||||||
|
catch (ex) {
|
||||||
|
Logger.error(ex, 'OpenRepoInRemoteCommand');
|
||||||
|
return window.showErrorMessage(`Unable to open repository in remote provider. See output channel for more details`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
28
src/commands/showBlame.ts
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
'use strict';
|
||||||
|
import { TextEditor, TextEditorEdit, Uri, window } from 'vscode';
|
||||||
|
import { BlameAnnotationController } from '../blameAnnotationController';
|
||||||
|
import { Commands, EditorCommand } from './common';
|
||||||
|
import { Logger } from '../logger';
|
||||||
|
|
||||||
|
export interface ShowBlameCommandArgs {
|
||||||
|
sha?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ShowBlameCommand extends EditorCommand {
|
||||||
|
|
||||||
|
constructor(private annotationController: BlameAnnotationController) {
|
||||||
|
super(Commands.ShowBlame);
|
||||||
|
}
|
||||||
|
|
||||||
|
async execute(editor: TextEditor, edit: TextEditorEdit, uri?: Uri, args: ShowBlameCommandArgs = {}): Promise<any> {
|
||||||
|
if (editor !== undefined && editor.document !== undefined && editor.document.isDirty) return undefined;
|
||||||
|
|
||||||
|
try {
|
||||||
|
return this.annotationController.showBlameAnnotation(editor, args.sha !== undefined ? args.sha : editor.selection.active.line);
|
||||||
|
}
|
||||||
|
catch (ex) {
|
||||||
|
Logger.error(ex, 'ShowBlameCommand');
|
||||||
|
return window.showErrorMessage(`Unable to show blame annotations. See output channel for more details`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
44
src/commands/showBlameHistory.ts
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
'use strict';
|
||||||
|
import { commands, Position, Range, TextEditor, TextEditorEdit, Uri, window } from 'vscode';
|
||||||
|
import { Commands, EditorCommand, getCommandUri } from './common';
|
||||||
|
import { BuiltInCommands } from '../constants';
|
||||||
|
import { GitService, GitUri } from '../gitService';
|
||||||
|
import { Logger } from '../logger';
|
||||||
|
|
||||||
|
export interface ShowBlameHistoryCommandArgs {
|
||||||
|
line?: number;
|
||||||
|
position?: Position;
|
||||||
|
range?: Range;
|
||||||
|
sha?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ShowBlameHistoryCommand extends EditorCommand {
|
||||||
|
|
||||||
|
constructor(private git: GitService) {
|
||||||
|
super(Commands.ShowBlameHistory);
|
||||||
|
}
|
||||||
|
|
||||||
|
async execute(editor: TextEditor, edit: TextEditorEdit, uri?: Uri, args: ShowBlameHistoryCommandArgs = {}) {
|
||||||
|
uri = getCommandUri(uri, editor);
|
||||||
|
if (uri === undefined) return undefined;
|
||||||
|
|
||||||
|
if (args.range == null || args.position == null) {
|
||||||
|
// If the command is executed manually -- treat it as a click on the root lens (i.e. show blame for the whole file)
|
||||||
|
args.range = editor.document.validateRange(new Range(0, 0, 1000000, 1000000));
|
||||||
|
args.position = editor.document.validateRange(new Range(0, 0, 0, 1000000)).start;
|
||||||
|
}
|
||||||
|
|
||||||
|
const gitUri = await GitUri.fromUri(uri, this.git);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const locations = await this.git.getBlameLocations(gitUri, args.range, args.sha, args.line);
|
||||||
|
if (locations === undefined) return window.showWarningMessage(`Unable to show blame history. File is probably not under source control`);
|
||||||
|
|
||||||
|
return commands.executeCommand(BuiltInCommands.ShowReferences, uri, args.position, locations);
|
||||||
|
}
|
||||||
|
catch (ex) {
|
||||||
|
Logger.error(ex, 'ShowBlameHistoryCommand', 'getBlameLocations');
|
||||||
|
return window.showErrorMessage(`Unable to show blame history. See output channel for more details`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
145
src/commands/showCommitSearch.ts
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
'use strict';
|
||||||
|
import { commands, InputBoxOptions, TextEditor, Uri, window } from 'vscode';
|
||||||
|
import { ActiveEditorCachedCommand, Commands, getCommandUri } from './common';
|
||||||
|
import { GitRepoSearchBy, GitService, GitUri } from '../gitService';
|
||||||
|
import { Logger } from '../logger';
|
||||||
|
import { CommandQuickPickItem, CommitsQuickPick } from '../quickPicks';
|
||||||
|
import { ShowQuickCommitDetailsCommandArgs } from './showQuickCommitDetails';
|
||||||
|
import { paste } from 'copy-paste';
|
||||||
|
|
||||||
|
const searchByRegex = /^([@:#])/;
|
||||||
|
const searchByMap = new Map<string, GitRepoSearchBy>([
|
||||||
|
['@', GitRepoSearchBy.Author],
|
||||||
|
[':', GitRepoSearchBy.Files],
|
||||||
|
['#', GitRepoSearchBy.Sha]
|
||||||
|
]);
|
||||||
|
|
||||||
|
export interface ShowCommitSearchCommandArgs {
|
||||||
|
search?: string;
|
||||||
|
searchBy?: GitRepoSearchBy;
|
||||||
|
|
||||||
|
goBackCommand?: CommandQuickPickItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ShowCommitSearchCommand extends ActiveEditorCachedCommand {
|
||||||
|
|
||||||
|
constructor(private git: GitService) {
|
||||||
|
super(Commands.ShowCommitSearch);
|
||||||
|
}
|
||||||
|
|
||||||
|
async execute(editor: TextEditor, uri?: Uri, args: ShowCommitSearchCommandArgs = {}) {
|
||||||
|
uri = getCommandUri(uri, editor);
|
||||||
|
|
||||||
|
const gitUri = uri === undefined ? undefined : await GitUri.fromUri(uri, this.git);
|
||||||
|
|
||||||
|
const repoPath = gitUri === undefined ? this.git.repoPath : gitUri.repoPath;
|
||||||
|
if (!repoPath) return window.showWarningMessage(`Unable to show commit search`);
|
||||||
|
|
||||||
|
if (!args.search || args.searchBy == null) {
|
||||||
|
try {
|
||||||
|
if (!args.search) {
|
||||||
|
if (editor !== undefined && gitUri !== undefined) {
|
||||||
|
const line = editor.selection.active.line - gitUri.offset;
|
||||||
|
const blameLine = await this.git.getBlameForLine(gitUri, line);
|
||||||
|
if (blameLine !== undefined) {
|
||||||
|
args.search = `#${blameLine.commit.shortSha}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!args.search) {
|
||||||
|
args.search = await new Promise<string>((resolve, reject) => {
|
||||||
|
paste((err: Error, content: string) => resolve(err ? '' : content));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (ex) {
|
||||||
|
Logger.error(ex, 'ShowCommitSearchCommand', 'search prefetch failed');
|
||||||
|
}
|
||||||
|
|
||||||
|
args.search = await window.showInputBox({
|
||||||
|
value: args.search,
|
||||||
|
prompt: `Please enter a search string`,
|
||||||
|
placeHolder: `search by message, author (use @<name>), files (use :<pattern>), or commit id (use #<sha>)`
|
||||||
|
} as InputBoxOptions);
|
||||||
|
if (args.search === undefined) return args.goBackCommand === undefined ? undefined : args.goBackCommand.execute();
|
||||||
|
|
||||||
|
const match = searchByRegex.exec(args.search);
|
||||||
|
if (match && match[1]) {
|
||||||
|
args.searchBy = searchByMap.get(match[1]);
|
||||||
|
args.search = args.search.substring((args.search[1] === ' ') ? 2 : 1);
|
||||||
|
}
|
||||||
|
else if (GitService.isSha(args.search)) {
|
||||||
|
args.searchBy = GitRepoSearchBy.Sha;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
args.searchBy = GitRepoSearchBy.Message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (args.searchBy === undefined) {
|
||||||
|
args.searchBy = GitRepoSearchBy.Message;
|
||||||
|
}
|
||||||
|
|
||||||
|
const log = await this.git.getLogForRepoSearch(repoPath, args.search, args.searchBy);
|
||||||
|
if (log === undefined) return undefined;
|
||||||
|
|
||||||
|
let originalSearch: string | undefined = undefined;
|
||||||
|
let placeHolder: string | undefined = undefined;
|
||||||
|
switch (args.searchBy) {
|
||||||
|
case GitRepoSearchBy.Author:
|
||||||
|
originalSearch = `@${args.search}`;
|
||||||
|
placeHolder = `commits with author matching '${args.search}'`;
|
||||||
|
break;
|
||||||
|
case GitRepoSearchBy.Files:
|
||||||
|
originalSearch = `:${args.search}`;
|
||||||
|
placeHolder = `commits with files matching '${args.search}'`;
|
||||||
|
break;
|
||||||
|
case GitRepoSearchBy.Message:
|
||||||
|
originalSearch = args.search;
|
||||||
|
placeHolder = `commits with message matching '${args.search}'`;
|
||||||
|
break;
|
||||||
|
case GitRepoSearchBy.Sha:
|
||||||
|
originalSearch = `#${args.search}`;
|
||||||
|
placeHolder = `commits with id matching '${args.search}'`;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a command to get back to here
|
||||||
|
const currentCommand = new CommandQuickPickItem({
|
||||||
|
label: `go back \u21A9`,
|
||||||
|
description: `\u00a0 \u2014 \u00a0\u00a0 to commit search`
|
||||||
|
}, Commands.ShowCommitSearch, [
|
||||||
|
uri,
|
||||||
|
{
|
||||||
|
search: originalSearch,
|
||||||
|
goBackCommand: args.goBackCommand
|
||||||
|
} as ShowCommitSearchCommandArgs
|
||||||
|
]);
|
||||||
|
|
||||||
|
const pick = await CommitsQuickPick.show(this.git, log, placeHolder!, currentCommand);
|
||||||
|
if (pick === undefined) return undefined;
|
||||||
|
|
||||||
|
if (pick instanceof CommandQuickPickItem) return pick.execute();
|
||||||
|
|
||||||
|
return commands.executeCommand(Commands.ShowQuickCommitDetails,
|
||||||
|
new GitUri(pick.commit.uri, pick.commit),
|
||||||
|
{
|
||||||
|
sha: pick.commit.sha,
|
||||||
|
commit: pick.commit,
|
||||||
|
goBackCommand: new CommandQuickPickItem({
|
||||||
|
label: `go back \u21A9`,
|
||||||
|
description: `\u00a0 \u2014 \u00a0\u00a0 to search for ${placeHolder}`
|
||||||
|
}, Commands.ShowCommitSearch, [
|
||||||
|
uri,
|
||||||
|
args
|
||||||
|
])
|
||||||
|
} as ShowQuickCommitDetailsCommandArgs);
|
||||||
|
}
|
||||||
|
catch (ex) {
|
||||||
|
Logger.error(ex, 'ShowCommitSearchCommand');
|
||||||
|
return window.showErrorMessage(`Unable to find commits. See output channel for more details`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
42
src/commands/showFileHistory.ts
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
'use strict';
|
||||||
|
import { commands, Position, Range, TextEditor, TextEditorEdit, Uri, window } from 'vscode';
|
||||||
|
import { Commands, EditorCommand, getCommandUri } from './common';
|
||||||
|
import { BuiltInCommands } from '../constants';
|
||||||
|
import { GitService, GitUri } from '../gitService';
|
||||||
|
import { Logger } from '../logger';
|
||||||
|
|
||||||
|
export interface ShowFileHistoryCommandArgs {
|
||||||
|
line?: number;
|
||||||
|
position?: Position;
|
||||||
|
sha?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ShowFileHistoryCommand extends EditorCommand {
|
||||||
|
|
||||||
|
constructor(private git: GitService) {
|
||||||
|
super(Commands.ShowFileHistory);
|
||||||
|
}
|
||||||
|
|
||||||
|
async execute(editor: TextEditor, edit: TextEditorEdit, uri?: Uri, args: ShowFileHistoryCommandArgs = {}) {
|
||||||
|
uri = getCommandUri(uri, editor);
|
||||||
|
if (uri === undefined) return undefined;
|
||||||
|
|
||||||
|
if (args.position == null) {
|
||||||
|
// If the command is executed manually -- treat it as a click on the root lens (i.e. show blame for the whole file)
|
||||||
|
args.position = editor.document.validateRange(new Range(0, 0, 0, 1000000)).start;
|
||||||
|
}
|
||||||
|
|
||||||
|
const gitUri = await GitUri.fromUri(uri, this.git);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const locations = await this.git.getLogLocations(gitUri, args.sha, args.line);
|
||||||
|
if (locations === undefined) return window.showWarningMessage(`Unable to show file history. File is probably not under source control`);
|
||||||
|
|
||||||
|
return commands.executeCommand(BuiltInCommands.ShowReferences, uri, args.position, locations);
|
||||||
|
}
|
||||||
|
catch (ex) {
|
||||||
|
Logger.error(ex, 'ShowFileHistoryCommand', 'getLogLocations');
|
||||||
|
return window.showErrorMessage(`Unable to show file history. See output channel for more details`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
24
src/commands/showLastQuickPick.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
'use strict';
|
||||||
|
import { commands, window } from 'vscode';
|
||||||
|
import { Command, Commands, getLastCommand } from './common';
|
||||||
|
import { Logger } from '../logger';
|
||||||
|
|
||||||
|
export class ShowLastQuickPickCommand extends Command {
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super(Commands.ShowLastQuickPick);
|
||||||
|
}
|
||||||
|
|
||||||
|
async execute() {
|
||||||
|
const command = getLastCommand();
|
||||||
|
if (!command) return undefined;
|
||||||
|
|
||||||
|
try {
|
||||||
|
return commands.executeCommand(command.command, ...command.args);
|
||||||
|
}
|
||||||
|
catch (ex) {
|
||||||
|
Logger.error(ex, 'ShowLastQuickPickCommand');
|
||||||
|
return window.showErrorMessage(`Unable to show last quick pick. See output channel for more details`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
90
src/commands/showQuickBranchHistory.ts
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
'use strict';
|
||||||
|
import { commands, TextEditor, Uri, window } from 'vscode';
|
||||||
|
import { ActiveEditorCachedCommand, Commands, getCommandUri } from './common';
|
||||||
|
import { GitService, GitUri, IGitLog } from '../gitService';
|
||||||
|
import { Logger } from '../logger';
|
||||||
|
import { BranchesQuickPick, BranchHistoryQuickPick, CommandQuickPickItem } from '../quickPicks';
|
||||||
|
import { ShowQuickCommitDetailsCommandArgs } from './showQuickCommitDetails';
|
||||||
|
|
||||||
|
export interface ShowQuickBranchHistoryCommandArgs {
|
||||||
|
branch?: string;
|
||||||
|
log?: IGitLog;
|
||||||
|
maxCount?: number;
|
||||||
|
|
||||||
|
goBackCommand?: CommandQuickPickItem;
|
||||||
|
nextPageCommand?: CommandQuickPickItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ShowQuickBranchHistoryCommand extends ActiveEditorCachedCommand {
|
||||||
|
|
||||||
|
constructor(private git: GitService) {
|
||||||
|
super(Commands.ShowQuickBranchHistory);
|
||||||
|
}
|
||||||
|
|
||||||
|
async execute(editor: TextEditor, uri?: Uri, args: ShowQuickBranchHistoryCommandArgs = {}) {
|
||||||
|
uri = getCommandUri(uri, editor);
|
||||||
|
|
||||||
|
const gitUri = uri && await GitUri.fromUri(uri, this.git);
|
||||||
|
|
||||||
|
if (args.maxCount == null) {
|
||||||
|
args.maxCount = this.git.config.advanced.maxQuickHistory;
|
||||||
|
}
|
||||||
|
|
||||||
|
let progressCancellation = args.branch === undefined ? undefined : BranchHistoryQuickPick.showProgress(args.branch);
|
||||||
|
try {
|
||||||
|
const repoPath = gitUri === undefined ? this.git.repoPath : gitUri.repoPath;
|
||||||
|
if (!repoPath) return window.showWarningMessage(`Unable to show branch history`);
|
||||||
|
|
||||||
|
if (args.branch === undefined) {
|
||||||
|
const branches = await this.git.getBranches(repoPath);
|
||||||
|
|
||||||
|
const pick = await BranchesQuickPick.show(branches, `Show history for branch\u2026`);
|
||||||
|
if (pick === undefined) return undefined;
|
||||||
|
|
||||||
|
if (pick instanceof CommandQuickPickItem) return pick.execute();
|
||||||
|
|
||||||
|
args.branch = pick.branch.name;
|
||||||
|
if (args.branch === undefined) return undefined;
|
||||||
|
|
||||||
|
progressCancellation = BranchHistoryQuickPick.showProgress(args.branch);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.log === undefined) {
|
||||||
|
args.log = await this.git.getLogForRepo(repoPath, (gitUri && gitUri.sha) || args.branch, args.maxCount);
|
||||||
|
if (args.log === undefined) return window.showWarningMessage(`Unable to show branch history`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (progressCancellation !== undefined && progressCancellation.token.isCancellationRequested) return undefined;
|
||||||
|
|
||||||
|
const pick = await BranchHistoryQuickPick.show(this.git, args.log, gitUri, args.branch, progressCancellation!, args.goBackCommand, args.nextPageCommand);
|
||||||
|
if (pick === undefined) return undefined;
|
||||||
|
|
||||||
|
if (pick instanceof CommandQuickPickItem) return pick.execute();
|
||||||
|
|
||||||
|
// Create a command to get back to here
|
||||||
|
const currentCommand = new CommandQuickPickItem({
|
||||||
|
label: `go back \u21A9`,
|
||||||
|
description: `\u00a0 \u2014 \u00a0\u00a0 to \u00a0$(git-branch) ${args.branch} history`
|
||||||
|
}, Commands.ShowQuickBranchHistory, [
|
||||||
|
uri,
|
||||||
|
args
|
||||||
|
]);
|
||||||
|
|
||||||
|
return commands.executeCommand(Commands.ShowQuickCommitDetails,
|
||||||
|
new GitUri(pick.commit.uri, pick.commit),
|
||||||
|
{
|
||||||
|
sha: pick.commit.sha,
|
||||||
|
commit: pick.commit,
|
||||||
|
repoLog: args.log,
|
||||||
|
goBackCommand: currentCommand
|
||||||
|
} as ShowQuickCommitDetailsCommandArgs);
|
||||||
|
}
|
||||||
|
catch (ex) {
|
||||||
|
Logger.error(ex, 'ShowQuickBranchHistoryCommand');
|
||||||
|
return window.showErrorMessage(`Unable to show branch history. See output channel for more details`);
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
progressCancellation && progressCancellation.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
116
src/commands/showQuickCommitDetails.ts
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
'use strict';
|
||||||
|
import { commands, TextEditor, Uri, window } from 'vscode';
|
||||||
|
import { ActiveEditorCachedCommand, Commands, getCommandUri } from './common';
|
||||||
|
import { GitCommit, GitLogCommit, GitService, GitUri, IGitLog } from '../gitService';
|
||||||
|
import { Logger } from '../logger';
|
||||||
|
import { CommandQuickPickItem, CommitDetailsQuickPick, CommitWithFileStatusQuickPickItem } from '../quickPicks';
|
||||||
|
import { ShowQuickCommitFileDetailsCommandArgs } from './showQuickCommitFileDetails';
|
||||||
|
import * as path from 'path';
|
||||||
|
|
||||||
|
export interface ShowQuickCommitDetailsCommandArgs {
|
||||||
|
sha?: string;
|
||||||
|
commit?: GitCommit | GitLogCommit;
|
||||||
|
repoLog?: IGitLog;
|
||||||
|
|
||||||
|
goBackCommand?: CommandQuickPickItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ShowQuickCommitDetailsCommand extends ActiveEditorCachedCommand {
|
||||||
|
|
||||||
|
constructor(private git: GitService) {
|
||||||
|
super(Commands.ShowQuickCommitDetails);
|
||||||
|
}
|
||||||
|
|
||||||
|
async execute(editor: TextEditor, uri?: Uri, args: ShowQuickCommitDetailsCommandArgs = {}) {
|
||||||
|
uri = getCommandUri(uri, editor);
|
||||||
|
if (uri === undefined) return undefined;
|
||||||
|
|
||||||
|
const gitUri = await GitUri.fromUri(uri, this.git);
|
||||||
|
|
||||||
|
let repoPath = gitUri.repoPath;
|
||||||
|
let workingFileName = path.relative(repoPath || '', gitUri.fsPath);
|
||||||
|
|
||||||
|
if (args.sha === undefined) {
|
||||||
|
if (editor === undefined) return undefined;
|
||||||
|
|
||||||
|
const blameline = editor.selection.active.line - gitUri.offset;
|
||||||
|
if (blameline < 0) return undefined;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const blame = await this.git.getBlameForLine(gitUri, blameline);
|
||||||
|
if (blame === undefined) return window.showWarningMessage(`Unable to show commit details. File is probably not under source control`);
|
||||||
|
|
||||||
|
args.sha = blame.commit.isUncommitted ? blame.commit.previousSha : blame.commit.sha;
|
||||||
|
repoPath = blame.commit.repoPath;
|
||||||
|
workingFileName = blame.commit.fileName;
|
||||||
|
|
||||||
|
args.commit = blame.commit;
|
||||||
|
}
|
||||||
|
catch (ex) {
|
||||||
|
Logger.error(ex, 'ShowQuickCommitDetailsCommand', `getBlameForLine(${blameline})`);
|
||||||
|
return window.showErrorMessage(`Unable to show commit details. See output channel for more details`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (args.commit === undefined || (args.commit.type !== 'branch' && args.commit.type !== 'stash')) {
|
||||||
|
if (args.repoLog !== undefined) {
|
||||||
|
args.commit = args.repoLog.commits.get(args.sha!);
|
||||||
|
// If we can't find the commit, kill the repoLog
|
||||||
|
if (args.commit === undefined) {
|
||||||
|
args.repoLog = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.repoLog === undefined) {
|
||||||
|
const log = await this.git.getLogForRepo(repoPath!, args.sha, 2);
|
||||||
|
if (log === undefined) return window.showWarningMessage(`Unable to show commit details`);
|
||||||
|
|
||||||
|
args.commit = log.commits.get(args.sha!);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.commit === undefined) return window.showWarningMessage(`Unable to show commit details`);
|
||||||
|
|
||||||
|
if (args.commit.workingFileName === undefined) {
|
||||||
|
args.commit.workingFileName = workingFileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.goBackCommand === undefined) {
|
||||||
|
// Create a command to get back to the branch history
|
||||||
|
args.goBackCommand = new CommandQuickPickItem({
|
||||||
|
label: `go back \u21A9`,
|
||||||
|
description: `\u00a0 \u2014 \u00a0\u00a0 to branch history`
|
||||||
|
}, Commands.ShowQuickCurrentBranchHistory, [
|
||||||
|
new GitUri(args.commit.uri, args.commit)
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a command to get back to where we are right now
|
||||||
|
const currentCommand = new CommandQuickPickItem({
|
||||||
|
label: `go back \u21A9`,
|
||||||
|
description: `\u00a0 \u2014 \u00a0\u00a0 to details of \u00a0$(git-commit) ${args.commit.shortSha}`
|
||||||
|
}, Commands.ShowQuickCommitDetails, [
|
||||||
|
new GitUri(args.commit.uri, args.commit),
|
||||||
|
args
|
||||||
|
]);
|
||||||
|
|
||||||
|
const pick = await CommitDetailsQuickPick.show(this.git, args.commit as GitLogCommit, uri, args.goBackCommand, currentCommand, args.repoLog);
|
||||||
|
if (pick === undefined) return undefined;
|
||||||
|
|
||||||
|
if (!(pick instanceof CommitWithFileStatusQuickPickItem)) return pick.execute();
|
||||||
|
|
||||||
|
return commands.executeCommand(Commands.ShowQuickCommitFileDetails,
|
||||||
|
pick.gitUri,
|
||||||
|
{
|
||||||
|
commit: args.commit,
|
||||||
|
sha: pick.sha,
|
||||||
|
goBackCommand: currentCommand
|
||||||
|
} as ShowQuickCommitFileDetailsCommandArgs);
|
||||||
|
}
|
||||||
|
catch (ex) {
|
||||||
|
Logger.error(ex, 'ShowQuickCommitDetailsCommand');
|
||||||
|
return window.showErrorMessage(`Unable to show commit details. See output channel for more details`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
116
src/commands/showQuickCommitFileDetails.ts
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
'use strict';
|
||||||
|
import { TextEditor, Uri, window } from 'vscode';
|
||||||
|
import { ActiveEditorCachedCommand, Commands, getCommandUri } from './common';
|
||||||
|
import { GitCommit, GitLogCommit, GitService, GitUri, IGitLog } from '../gitService';
|
||||||
|
import { Logger } from '../logger';
|
||||||
|
import { CommandQuickPickItem, CommitFileDetailsQuickPick } from '../quickPicks';
|
||||||
|
import { ShowQuickCommitDetailsCommandArgs } from './showQuickCommitDetails';
|
||||||
|
import * as path from 'path';
|
||||||
|
|
||||||
|
export interface ShowQuickCommitFileDetailsCommandArgs {
|
||||||
|
sha?: string;
|
||||||
|
commit?: GitCommit | GitLogCommit;
|
||||||
|
fileLog?: IGitLog;
|
||||||
|
|
||||||
|
goBackCommand?: CommandQuickPickItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ShowQuickCommitFileDetailsCommand extends ActiveEditorCachedCommand {
|
||||||
|
|
||||||
|
constructor(private git: GitService) {
|
||||||
|
super(Commands.ShowQuickCommitFileDetails);
|
||||||
|
}
|
||||||
|
|
||||||
|
async execute(editor: TextEditor, uri?: Uri, args: ShowQuickCommitFileDetailsCommandArgs = {}) {
|
||||||
|
uri = getCommandUri(uri, editor);
|
||||||
|
if (uri === undefined) return undefined;
|
||||||
|
|
||||||
|
let workingFileName = args.commit && args.commit.workingFileName;
|
||||||
|
|
||||||
|
const gitUri = await GitUri.fromUri(uri, this.git);
|
||||||
|
|
||||||
|
if (args.sha === undefined) {
|
||||||
|
if (editor === undefined) return undefined;
|
||||||
|
|
||||||
|
const blameline = editor.selection.active.line - gitUri.offset;
|
||||||
|
if (blameline < 0) return undefined;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const blame = await this.git.getBlameForLine(gitUri, blameline);
|
||||||
|
if (blame === undefined) return window.showWarningMessage(`Unable to show commit file details. File is probably not under source control`);
|
||||||
|
|
||||||
|
args.sha = blame.commit.isUncommitted ? blame.commit.previousSha : blame.commit.sha;
|
||||||
|
|
||||||
|
args.commit = blame.commit;
|
||||||
|
workingFileName = path.relative(args.commit.repoPath, gitUri.fsPath);
|
||||||
|
}
|
||||||
|
catch (ex) {
|
||||||
|
Logger.error(ex, 'ShowQuickCommitFileDetailsCommand', `getBlameForLine(${blameline})`);
|
||||||
|
return window.showErrorMessage(`Unable to show commit file details. See output channel for more details`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (args.commit === undefined || args.commit.type !== 'file') {
|
||||||
|
if (args.commit !== undefined) {
|
||||||
|
workingFileName = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.fileLog !== undefined) {
|
||||||
|
args.commit = args.fileLog.commits.get(args.sha!);
|
||||||
|
// If we can't find the commit, kill the fileLog
|
||||||
|
if (args.commit === undefined) {
|
||||||
|
args.fileLog = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.fileLog === undefined) {
|
||||||
|
args.commit = await this.git.getLogCommit(args.commit ? args.commit.repoPath : gitUri.repoPath, gitUri.fsPath, args.sha, { previous: true });
|
||||||
|
if (args.commit === undefined) return window.showWarningMessage(`Unable to show commit file details`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.commit === undefined) return window.showWarningMessage(`Unable to show commit file details`);
|
||||||
|
|
||||||
|
// Attempt to the most recent commit -- so that we can find the real working filename if there was a rename
|
||||||
|
args.commit.workingFileName = workingFileName;
|
||||||
|
args.commit.workingFileName = await this.git.findWorkingFileName(args.commit);
|
||||||
|
|
||||||
|
const shortSha = args.sha!.substring(0, 8);
|
||||||
|
|
||||||
|
if (args.goBackCommand === undefined) {
|
||||||
|
// Create a command to get back to the commit details
|
||||||
|
args.goBackCommand = new CommandQuickPickItem({
|
||||||
|
label: `go back \u21A9`,
|
||||||
|
description: `\u00a0 \u2014 \u00a0\u00a0 to details of \u00a0$(git-commit) ${shortSha}`
|
||||||
|
}, Commands.ShowQuickCommitDetails, [
|
||||||
|
new GitUri(args.commit.uri, args.commit),
|
||||||
|
{
|
||||||
|
commit: args.commit,
|
||||||
|
sha: args.sha
|
||||||
|
} as ShowQuickCommitDetailsCommandArgs
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a command to get back to where we are right now
|
||||||
|
const currentCommand = new CommandQuickPickItem({
|
||||||
|
label: `go back \u21A9`,
|
||||||
|
description: `\u00a0 \u2014 \u00a0\u00a0 to details of \u00a0$(file-text) ${path.basename(args.commit.fileName)} in \u00a0$(git-commit) ${shortSha}`
|
||||||
|
}, Commands.ShowQuickCommitFileDetails, [
|
||||||
|
new GitUri(args.commit.uri, args.commit),
|
||||||
|
args
|
||||||
|
]);
|
||||||
|
|
||||||
|
const pick = await CommitFileDetailsQuickPick.show(this.git, args.commit as GitLogCommit, uri, args.goBackCommand, currentCommand, args.fileLog);
|
||||||
|
if (pick === undefined) return undefined;
|
||||||
|
|
||||||
|
if (pick instanceof CommandQuickPickItem) return pick.execute();
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
catch (ex) {
|
||||||
|
Logger.error(ex, 'ShowQuickCommitFileDetailsCommand');
|
||||||
|
return window.showErrorMessage(`Unable to show commit file details. See output channel for more details`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
41
src/commands/showQuickCurrentBranchHistory.ts
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
'use strict';
|
||||||
|
import { commands, TextEditor, Uri, window } from 'vscode';
|
||||||
|
import { ActiveEditorCachedCommand, Commands, getCommandUri } from './common';
|
||||||
|
import { ShowQuickBranchHistoryCommandArgs } from './showQuickBranchHistory';
|
||||||
|
import { GitService } from '../gitService';
|
||||||
|
import { Logger } from '../logger';
|
||||||
|
import { CommandQuickPickItem } from '../quickPicks';
|
||||||
|
|
||||||
|
export interface ShowQuickCurrentBranchHistoryCommandArgs {
|
||||||
|
goBackCommand?: CommandQuickPickItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ShowQuickCurrentBranchHistoryCommand extends ActiveEditorCachedCommand {
|
||||||
|
|
||||||
|
constructor(private git: GitService) {
|
||||||
|
super(Commands.ShowQuickCurrentBranchHistory);
|
||||||
|
}
|
||||||
|
|
||||||
|
async execute(editor: TextEditor, uri?: Uri, args: ShowQuickCurrentBranchHistoryCommandArgs = {}) {
|
||||||
|
uri = getCommandUri(uri, editor);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const repoPath = await this.git.getRepoPathFromUri(uri);
|
||||||
|
if (!repoPath) return window.showWarningMessage(`Unable to show branch history`);
|
||||||
|
|
||||||
|
const branch = await this.git.getBranch(repoPath);
|
||||||
|
if (branch === undefined) return undefined;
|
||||||
|
|
||||||
|
return commands.executeCommand(Commands.ShowQuickBranchHistory,
|
||||||
|
uri,
|
||||||
|
{
|
||||||
|
branch: branch.name,
|
||||||
|
goBackCommand: args.goBackCommand
|
||||||
|
} as ShowQuickBranchHistoryCommandArgs);
|
||||||
|
}
|
||||||
|
catch (ex) {
|
||||||
|
Logger.error(ex, 'ShowQuickCurrentBranchHistoryCommand');
|
||||||
|
return window.showErrorMessage(`Unable to show branch history. See output channel for more details`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
75
src/commands/showQuickFileHistory.ts
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
'use strict';
|
||||||
|
import { commands, Range, TextEditor, Uri, window } from 'vscode';
|
||||||
|
import { ActiveEditorCachedCommand, Commands, getCommandUri } from './common';
|
||||||
|
import { GitService, GitUri, IGitLog } from '../gitService';
|
||||||
|
import { Logger } from '../logger';
|
||||||
|
import { CommandQuickPickItem, FileHistoryQuickPick } from '../quickPicks';
|
||||||
|
import { ShowQuickCommitFileDetailsCommandArgs } from './showQuickCommitFileDetails';
|
||||||
|
import * as path from 'path';
|
||||||
|
|
||||||
|
export interface ShowQuickFileHistoryCommandArgs {
|
||||||
|
log?: IGitLog;
|
||||||
|
maxCount?: number;
|
||||||
|
range?: Range;
|
||||||
|
|
||||||
|
goBackCommand?: CommandQuickPickItem;
|
||||||
|
nextPageCommand?: CommandQuickPickItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ShowQuickFileHistoryCommand extends ActiveEditorCachedCommand {
|
||||||
|
|
||||||
|
constructor(private git: GitService) {
|
||||||
|
super(Commands.ShowQuickFileHistory);
|
||||||
|
}
|
||||||
|
|
||||||
|
async execute(editor: TextEditor, uri?: Uri, args: ShowQuickFileHistoryCommandArgs = {}) {
|
||||||
|
uri = getCommandUri(uri, editor);
|
||||||
|
if (uri === undefined) return commands.executeCommand(Commands.ShowQuickCurrentBranchHistory);
|
||||||
|
|
||||||
|
const gitUri = await GitUri.fromUri(uri, this.git);
|
||||||
|
|
||||||
|
if (args.maxCount == null) {
|
||||||
|
args.maxCount = this.git.config.advanced.maxQuickHistory;
|
||||||
|
}
|
||||||
|
|
||||||
|
const progressCancellation = FileHistoryQuickPick.showProgress(gitUri);
|
||||||
|
try {
|
||||||
|
if (args.log === undefined) {
|
||||||
|
args.log = await this.git.getLogForFile(gitUri.repoPath, gitUri.fsPath, gitUri.sha, args.maxCount, args.range);
|
||||||
|
if (args.log === undefined) return window.showWarningMessage(`Unable to show file history. File is probably not under source control`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (progressCancellation.token.isCancellationRequested) return undefined;
|
||||||
|
|
||||||
|
const pick = await FileHistoryQuickPick.show(this.git, args.log, gitUri, progressCancellation, args.goBackCommand, args.nextPageCommand);
|
||||||
|
if (pick === undefined) return undefined;
|
||||||
|
|
||||||
|
if (pick instanceof CommandQuickPickItem) return pick.execute();
|
||||||
|
|
||||||
|
// Create a command to get back to where we are right now
|
||||||
|
const currentCommand = new CommandQuickPickItem({
|
||||||
|
label: `go back \u21A9`,
|
||||||
|
description: `\u00a0 \u2014 \u00a0\u00a0 to history of \u00a0$(file-text) ${path.basename(pick.commit.fileName)}${gitUri.sha ? ` from \u00a0$(git-commit) ${gitUri.shortSha}` : ''}`
|
||||||
|
}, Commands.ShowQuickFileHistory, [
|
||||||
|
uri,
|
||||||
|
args
|
||||||
|
]);
|
||||||
|
|
||||||
|
return commands.executeCommand(Commands.ShowQuickCommitFileDetails,
|
||||||
|
new GitUri(pick.commit.uri, pick.commit),
|
||||||
|
{
|
||||||
|
commit: pick.commit,
|
||||||
|
fileLog: args.log,
|
||||||
|
sha: pick.commit.sha,
|
||||||
|
goBackCommand: currentCommand
|
||||||
|
} as ShowQuickCommitFileDetailsCommandArgs);
|
||||||
|
}
|
||||||
|
catch (ex) {
|
||||||
|
Logger.error(ex, 'ShowQuickFileHistoryCommand');
|
||||||
|
return window.showErrorMessage(`Unable to show file history. See output channel for more details`);
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
progressCancellation.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
40
src/commands/showQuickRepoStatus.ts
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
'use strict';
|
||||||
|
import { TextEditor, Uri, window } from 'vscode';
|
||||||
|
import { ActiveEditorCachedCommand, Commands, getCommandUri } from './common';
|
||||||
|
import { GitService } from '../gitService';
|
||||||
|
import { Logger } from '../logger';
|
||||||
|
import { CommandQuickPickItem, RepoStatusQuickPick } from '../quickPicks';
|
||||||
|
|
||||||
|
export interface ShowQuickRepoStatusCommandArgs {
|
||||||
|
goBackCommand?: CommandQuickPickItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ShowQuickRepoStatusCommand extends ActiveEditorCachedCommand {
|
||||||
|
|
||||||
|
constructor(private git: GitService) {
|
||||||
|
super(Commands.ShowQuickRepoStatus);
|
||||||
|
}
|
||||||
|
|
||||||
|
async execute(editor: TextEditor, uri?: Uri, args: ShowQuickRepoStatusCommandArgs = {}) {
|
||||||
|
uri = getCommandUri(uri, editor);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const repoPath = await this.git.getRepoPathFromUri(uri);
|
||||||
|
if (!repoPath) return window.showWarningMessage(`Unable to show repository status`);
|
||||||
|
|
||||||
|
const status = await this.git.getStatusForRepo(repoPath);
|
||||||
|
if (status === undefined) return window.showWarningMessage(`Unable to show repository status`);
|
||||||
|
|
||||||
|
const pick = await RepoStatusQuickPick.show(status, args.goBackCommand);
|
||||||
|
if (pick === undefined) return undefined;
|
||||||
|
|
||||||
|
if (pick instanceof CommandQuickPickItem) return pick.execute();
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
catch (ex) {
|
||||||
|
Logger.error(ex, 'ShowQuickRepoStatusCommand');
|
||||||
|
return window.showErrorMessage(`Unable to show repository status. See output channel for more details`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
58
src/commands/showQuickStashList.ts
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
'use strict';
|
||||||
|
import { commands, TextEditor, Uri, window } from 'vscode';
|
||||||
|
import { ActiveEditorCachedCommand, Commands, getCommandUri } from './common';
|
||||||
|
import { GitService, GitUri } from '../gitService';
|
||||||
|
import { Logger } from '../logger';
|
||||||
|
import { CommandQuickPickItem, StashListQuickPick } from '../quickPicks';
|
||||||
|
import { ShowQuickCommitDetailsCommandArgs } from './showQuickCommitDetails';
|
||||||
|
|
||||||
|
export interface ShowQuickStashListCommandArgs {
|
||||||
|
goBackCommand?: CommandQuickPickItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ShowQuickStashListCommand extends ActiveEditorCachedCommand {
|
||||||
|
|
||||||
|
constructor(private git: GitService) {
|
||||||
|
super(Commands.ShowQuickStashList);
|
||||||
|
}
|
||||||
|
|
||||||
|
async execute(editor: TextEditor, uri?: Uri, args: ShowQuickStashListCommandArgs = {}) {
|
||||||
|
uri = getCommandUri(uri, editor);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const repoPath = await this.git.getRepoPathFromUri(uri);
|
||||||
|
if (!repoPath) return window.showWarningMessage(`Unable to show stashed changes`);
|
||||||
|
|
||||||
|
const stash = await this.git.getStashList(repoPath);
|
||||||
|
if (stash === undefined) return window.showWarningMessage(`Unable to show stashed changes`);
|
||||||
|
|
||||||
|
// Create a command to get back to here
|
||||||
|
const currentCommand = new CommandQuickPickItem({
|
||||||
|
label: `go back \u21A9`,
|
||||||
|
description: `\u00a0 \u2014 \u00a0\u00a0 to stashed changes`
|
||||||
|
}, Commands.ShowQuickStashList, [
|
||||||
|
uri,
|
||||||
|
{
|
||||||
|
goBackCommand: args.goBackCommand
|
||||||
|
} as ShowQuickStashListCommandArgs
|
||||||
|
]);
|
||||||
|
|
||||||
|
const pick = await StashListQuickPick.show(this.git, stash, 'list', args.goBackCommand, currentCommand);
|
||||||
|
if (pick === undefined) return undefined;
|
||||||
|
|
||||||
|
if (pick instanceof CommandQuickPickItem) return pick.execute();
|
||||||
|
|
||||||
|
return commands.executeCommand(Commands.ShowQuickCommitDetails,
|
||||||
|
new GitUri(pick.commit.uri, pick.commit),
|
||||||
|
{
|
||||||
|
commit: pick.commit,
|
||||||
|
sha: pick.commit.sha,
|
||||||
|
goBackCommand: currentCommand
|
||||||
|
} as ShowQuickCommitDetailsCommandArgs);
|
||||||
|
}
|
||||||
|
catch (ex) {
|
||||||
|
Logger.error(ex, 'ShowQuickStashListCommand');
|
||||||
|
return window.showErrorMessage(`Unable to show stashed changes. See output channel for more details`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
64
src/commands/stashApply.ts
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
'use strict';
|
||||||
|
import { MessageItem, window } from 'vscode';
|
||||||
|
import { GitService, GitStashCommit } from '../gitService';
|
||||||
|
import { Command, Commands } from './common';
|
||||||
|
import { CommitQuickPickItem, StashListQuickPick } from '../quickPicks';
|
||||||
|
import { Logger } from '../logger';
|
||||||
|
import { CommandQuickPickItem } from '../quickPicks';
|
||||||
|
|
||||||
|
export interface StashApplyCommandArgs {
|
||||||
|
confirm?: boolean;
|
||||||
|
deleteAfter?: boolean;
|
||||||
|
stashItem?: { stashName: string, message: string };
|
||||||
|
|
||||||
|
goBackCommand?: CommandQuickPickItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class StashApplyCommand extends Command {
|
||||||
|
|
||||||
|
constructor(private git: GitService) {
|
||||||
|
super(Commands.StashApply);
|
||||||
|
}
|
||||||
|
|
||||||
|
async execute(args: StashApplyCommandArgs = { confirm: true, deleteAfter: false }) {
|
||||||
|
if (!this.git.config.insiders) return undefined;
|
||||||
|
if (!this.git.repoPath) return undefined;
|
||||||
|
|
||||||
|
if (args.stashItem === undefined || args.stashItem.stashName === undefined) {
|
||||||
|
const stash = await this.git.getStashList(this.git.repoPath);
|
||||||
|
if (stash === undefined) return window.showInformationMessage(`There are no stashed changes`);
|
||||||
|
|
||||||
|
const currentCommand = new CommandQuickPickItem({
|
||||||
|
label: `go back \u21A9`,
|
||||||
|
description: `\u00a0 \u2014 \u00a0\u00a0 to apply stashed changes`
|
||||||
|
}, Commands.StashApply, [args]);
|
||||||
|
|
||||||
|
const pick = await StashListQuickPick.show(this.git, stash, 'apply', args.goBackCommand, currentCommand);
|
||||||
|
if (pick === undefined || !(pick instanceof CommitQuickPickItem)) return args.goBackCommand === undefined ? undefined : args.goBackCommand.execute();
|
||||||
|
|
||||||
|
args.goBackCommand = currentCommand;
|
||||||
|
args.stashItem = pick.commit as GitStashCommit;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (args.confirm) {
|
||||||
|
const message = args.stashItem.message.length > 80 ? `${args.stashItem.message.substring(0, 80)}\u2026` : args.stashItem.message;
|
||||||
|
const result = await window.showWarningMessage(`Apply stashed changes '${message}' to your working tree?`, { title: 'Yes, delete after applying' } as MessageItem, { title: 'Yes' } as MessageItem, { title: 'No', isCloseAffordance: true } as MessageItem);
|
||||||
|
if (result === undefined || result.title === 'No') return args.goBackCommand === undefined ? undefined : args.goBackCommand.execute();
|
||||||
|
|
||||||
|
args.deleteAfter = result.title !== 'Yes';
|
||||||
|
}
|
||||||
|
|
||||||
|
return await this.git.stashApply(this.git.repoPath, args.stashItem.stashName, args.deleteAfter);
|
||||||
|
}
|
||||||
|
catch (ex) {
|
||||||
|
Logger.error(ex, 'StashApplyCommand');
|
||||||
|
if (ex.message.includes('Your local changes to the following files would be overwritten by merge')) {
|
||||||
|
return window.showErrorMessage(`Unable to apply stash. Your working tree changes would be overwritten.`);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return window.showErrorMessage(`Unable to apply stash. See output channel for more details`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
45
src/commands/stashDelete.ts
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
'use strict';
|
||||||
|
import { MessageItem, window } from 'vscode';
|
||||||
|
import { GitService } from '../gitService';
|
||||||
|
import { Command, Commands } from './common';
|
||||||
|
import { Logger } from '../logger';
|
||||||
|
import { CommandQuickPickItem } from '../quickPicks';
|
||||||
|
|
||||||
|
export interface StashDeleteCommandArgs {
|
||||||
|
confirm?: boolean;
|
||||||
|
stashItem?: { stashName: string, message: string };
|
||||||
|
|
||||||
|
goBackCommand?: CommandQuickPickItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class StashDeleteCommand extends Command {
|
||||||
|
|
||||||
|
constructor(private git: GitService) {
|
||||||
|
super(Commands.StashDelete);
|
||||||
|
}
|
||||||
|
|
||||||
|
async execute(args: StashDeleteCommandArgs = { confirm: true }) {
|
||||||
|
if (!this.git.config.insiders) return undefined;
|
||||||
|
if (!this.git.repoPath) return undefined;
|
||||||
|
|
||||||
|
if (args.stashItem === undefined || args.stashItem.stashName === undefined) return undefined;
|
||||||
|
|
||||||
|
if (args.confirm === undefined) {
|
||||||
|
args.confirm = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (args.confirm) {
|
||||||
|
const message = args.stashItem.message.length > 80 ? `${args.stashItem.message.substring(0, 80)}\u2026` : args.stashItem.message;
|
||||||
|
const result = await window.showWarningMessage(`Delete stashed changes '${message}'?`, { title: 'Yes' } as MessageItem, { title: 'No', isCloseAffordance: true } as MessageItem);
|
||||||
|
if (result === undefined || result.title !== 'Yes') return args.goBackCommand === undefined ? undefined : args.goBackCommand.execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
return await this.git.stashDelete(this.git.repoPath, args.stashItem.stashName);
|
||||||
|
}
|
||||||
|
catch (ex) {
|
||||||
|
Logger.error(ex, 'StashDeleteCommand');
|
||||||
|
return window.showErrorMessage(`Unable to delete stash. See output channel for more details`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
45
src/commands/stashSave.ts
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
'use strict';
|
||||||
|
import { InputBoxOptions, window } from 'vscode';
|
||||||
|
import { GitService } from '../gitService';
|
||||||
|
import { Command, Commands } from './common';
|
||||||
|
import { Logger } from '../logger';
|
||||||
|
import { CommandQuickPickItem } from '../quickPicks';
|
||||||
|
|
||||||
|
export interface StashSaveCommandArgs {
|
||||||
|
message?: string;
|
||||||
|
unstagedOnly?: boolean;
|
||||||
|
|
||||||
|
goBackCommand?: CommandQuickPickItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class StashSaveCommand extends Command {
|
||||||
|
|
||||||
|
constructor(private git: GitService) {
|
||||||
|
super(Commands.StashSave);
|
||||||
|
}
|
||||||
|
|
||||||
|
async execute(args: StashSaveCommandArgs = { unstagedOnly : false }) {
|
||||||
|
if (!this.git.config.insiders) return undefined;
|
||||||
|
if (!this.git.repoPath) return undefined;
|
||||||
|
|
||||||
|
if (args.unstagedOnly === undefined) {
|
||||||
|
args.unstagedOnly = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (args.message == null) {
|
||||||
|
args.message = await window.showInputBox({
|
||||||
|
prompt: `Please provide a stash message`,
|
||||||
|
placeHolder: `Stash message`
|
||||||
|
} as InputBoxOptions);
|
||||||
|
if (args.message === undefined) return args.goBackCommand === undefined ? undefined : args.goBackCommand.execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
return await this.git.stashSave(this.git.repoPath, args.message, args.unstagedOnly);
|
||||||
|
}
|
||||||
|
catch (ex) {
|
||||||
|
Logger.error(ex, 'StashSaveCommand');
|
||||||
|
return window.showErrorMessage(`Unable to save stash. See output channel for more details`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
28
src/commands/toggleBlame.ts
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
'use strict';
|
||||||
|
import { TextEditor, TextEditorEdit, Uri, window } from 'vscode';
|
||||||
|
import { BlameAnnotationController } from '../blameAnnotationController';
|
||||||
|
import { Commands, EditorCommand } from './common';
|
||||||
|
import { Logger } from '../logger';
|
||||||
|
|
||||||
|
export interface ToggleBlameCommandArgs {
|
||||||
|
sha?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ToggleBlameCommand extends EditorCommand {
|
||||||
|
|
||||||
|
constructor(private annotationController: BlameAnnotationController) {
|
||||||
|
super(Commands.ToggleBlame);
|
||||||
|
}
|
||||||
|
|
||||||
|
async execute(editor: TextEditor, edit: TextEditorEdit, uri?: Uri, args: ToggleBlameCommandArgs = {}): Promise<any> {
|
||||||
|
if (editor !== undefined && editor.document !== undefined && editor.document.isDirty) return undefined;
|
||||||
|
|
||||||
|
try {
|
||||||
|
return this.annotationController.toggleBlameAnnotation(editor, args.sha !== undefined ? args.sha : editor.selection.active.line);
|
||||||
|
}
|
||||||
|
catch (ex) {
|
||||||
|
Logger.error(ex, 'ToggleBlameCommand');
|
||||||
|
return window.showErrorMessage(`Unable to show blame annotations. See output channel for more details`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
15
src/commands/toggleCodeLens.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
'use strict';
|
||||||
|
import { TextEditor, TextEditorEdit } from 'vscode';
|
||||||
|
import { Commands, EditorCommand } from './common';
|
||||||
|
import { GitService } from '../gitService';
|
||||||
|
|
||||||
|
export class ToggleCodeLensCommand extends EditorCommand {
|
||||||
|
|
||||||
|
constructor(private git: GitService) {
|
||||||
|
super(Commands.ToggleCodeLens);
|
||||||
|
}
|
||||||
|
|
||||||
|
execute(editor: TextEditor, edit: TextEditorEdit) {
|
||||||
|
return this.git.toggleCodeLens(editor);
|
||||||
|
}
|
||||||
|
}
|
||||||
53
src/comparers.ts
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
'use strict';
|
||||||
|
import { TextDocument, TextEditor, Uri } from 'vscode';
|
||||||
|
|
||||||
|
abstract class Comparer<T> {
|
||||||
|
abstract equals(lhs: T, rhs: T): boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
class UriComparer extends Comparer<Uri> {
|
||||||
|
|
||||||
|
equals(lhs: Uri | undefined, rhs: Uri | undefined) {
|
||||||
|
if (lhs === undefined && rhs === undefined) return true;
|
||||||
|
if (lhs === undefined || rhs === undefined) return false;
|
||||||
|
|
||||||
|
return lhs.scheme === rhs.scheme && lhs.fsPath === rhs.fsPath;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class TextDocumentComparer extends Comparer<TextDocument> {
|
||||||
|
|
||||||
|
equals(lhs: TextDocument | undefined, rhs: TextDocument | undefined) {
|
||||||
|
if (lhs === undefined && rhs === undefined) return true;
|
||||||
|
if (lhs === undefined || rhs === undefined) return false;
|
||||||
|
|
||||||
|
return uriComparer.equals(lhs.uri, rhs.uri);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class TextEditorComparer extends Comparer<TextEditor> {
|
||||||
|
|
||||||
|
equals(lhs: TextEditor | undefined, rhs: TextEditor | undefined, options: { useId: boolean, usePosition: boolean } = { useId: false, usePosition: false }) {
|
||||||
|
if (lhs === undefined && rhs === undefined) return true;
|
||||||
|
if (lhs === undefined || rhs === undefined) return false;
|
||||||
|
|
||||||
|
if (options.usePosition && (lhs.viewColumn !== rhs.viewColumn)) return false;
|
||||||
|
|
||||||
|
if (options.useId && (!lhs.document || !rhs.document)) {
|
||||||
|
if ((lhs as any)._id !== (rhs as any)._id) return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return textDocumentComparer.equals(lhs.document, rhs.document);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const textDocumentComparer = new TextDocumentComparer();
|
||||||
|
const textEditorComparer = new TextEditorComparer();
|
||||||
|
const uriComparer = new UriComparer();
|
||||||
|
export {
|
||||||
|
textDocumentComparer as TextDocumentComparer,
|
||||||
|
textEditorComparer as TextEditorComparer,
|
||||||
|
uriComparer as UriComparer
|
||||||
|
};
|
||||||
125
src/configuration.ts
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
'use strict';
|
||||||
|
import { Commands } from './commands';
|
||||||
|
import { OutputLevel } from './logger';
|
||||||
|
|
||||||
|
export type BlameAnnotationStyle = 'compact' | 'expanded' | 'trailing';
|
||||||
|
export const BlameAnnotationStyle = {
|
||||||
|
Compact: 'compact' as BlameAnnotationStyle,
|
||||||
|
Expanded: 'expanded' as BlameAnnotationStyle,
|
||||||
|
Trailing: 'trailing' as BlameAnnotationStyle
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface IBlameConfig {
|
||||||
|
annotation: {
|
||||||
|
style: BlameAnnotationStyle;
|
||||||
|
highlight: 'none' | 'gutter' | 'line' | 'both';
|
||||||
|
sha: boolean;
|
||||||
|
author: boolean;
|
||||||
|
date: 'off' | 'relative' | 'absolute';
|
||||||
|
dateFormat: string;
|
||||||
|
message: boolean;
|
||||||
|
activeLine: 'off' | 'inline' | 'hover' | 'both';
|
||||||
|
activeLineDarkColor: string;
|
||||||
|
activeLineLightColor: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CodeLensCommand = 'gitlens.toggleBlame' | 'gitlens.showBlameHistory' | 'gitlens.showFileHistory' | 'gitlens.diffWithPrevious' | 'gitlens.showQuickCommitDetails' | 'gitlens.showQuickCommitFileDetails' | 'gitlens.showQuickFileHistory' | 'gitlens.showQuickRepoHistory';
|
||||||
|
export const CodeLensCommand = {
|
||||||
|
BlameAnnotate: Commands.ToggleBlame as CodeLensCommand,
|
||||||
|
ShowBlameHistory: Commands.ShowBlameHistory as CodeLensCommand,
|
||||||
|
ShowFileHistory: Commands.ShowFileHistory as CodeLensCommand,
|
||||||
|
DiffWithPrevious: Commands.DiffWithPrevious as CodeLensCommand,
|
||||||
|
ShowQuickCommitDetails: Commands.ShowQuickCommitDetails as CodeLensCommand,
|
||||||
|
ShowQuickCommitFileDetails: Commands.ShowQuickCommitFileDetails as CodeLensCommand,
|
||||||
|
ShowQuickFileHistory: Commands.ShowQuickFileHistory as CodeLensCommand,
|
||||||
|
ShowQuickCurrentBranchHistory: Commands.ShowQuickCurrentBranchHistory as CodeLensCommand
|
||||||
|
};
|
||||||
|
|
||||||
|
export type CodeLensLocation = 'all' | 'document+containers' | 'document' | 'custom' | 'none';
|
||||||
|
export const CodeLensLocation = {
|
||||||
|
All: 'all' as CodeLensLocation,
|
||||||
|
DocumentAndContainers: 'document+containers' as CodeLensLocation,
|
||||||
|
Document: 'document' as CodeLensLocation,
|
||||||
|
Custom: 'custom' as CodeLensLocation,
|
||||||
|
None: 'none' as CodeLensLocation
|
||||||
|
};
|
||||||
|
|
||||||
|
export type CodeLensVisibility = 'auto' | 'ondemand' | 'off';
|
||||||
|
export const CodeLensVisibility = {
|
||||||
|
Auto: 'auto' as CodeLensVisibility,
|
||||||
|
OnDemand: 'ondemand' as CodeLensVisibility,
|
||||||
|
Off: 'off' as CodeLensVisibility
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface ICodeLensConfig {
|
||||||
|
enabled: boolean;
|
||||||
|
command: CodeLensCommand;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ICodeLensLanguageLocation {
|
||||||
|
language: string | undefined;
|
||||||
|
location: CodeLensLocation;
|
||||||
|
customSymbols?: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ICodeLensesConfig {
|
||||||
|
debug: boolean;
|
||||||
|
visibility: CodeLensVisibility;
|
||||||
|
location: CodeLensLocation;
|
||||||
|
locationCustomSymbols: string[];
|
||||||
|
languageLocations: ICodeLensLanguageLocation[];
|
||||||
|
recentChange: ICodeLensConfig;
|
||||||
|
authors: ICodeLensConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type StatusBarCommand = 'gitlens.toggleBlame' | 'gitlens.showBlameHistory' | 'gitlens.showFileHistory' | 'gitlens.toggleCodeLens' | 'gitlens.diffWithPrevious' | 'gitlens.showQuickCommitDetails' | 'gitlens.showQuickCommitFileDetails' | 'gitlens.showQuickFileHistory' | 'gitlens.showQuickRepoHistory';
|
||||||
|
export const StatusBarCommand = {
|
||||||
|
BlameAnnotate: Commands.ToggleBlame as StatusBarCommand,
|
||||||
|
ShowBlameHistory: Commands.ShowBlameHistory as StatusBarCommand,
|
||||||
|
ShowFileHistory: Commands.ShowFileHistory as CodeLensCommand,
|
||||||
|
DiffWithPrevious: Commands.DiffWithPrevious as StatusBarCommand,
|
||||||
|
ToggleCodeLens: Commands.ToggleCodeLens as StatusBarCommand,
|
||||||
|
ShowQuickCommitDetails: Commands.ShowQuickCommitDetails as StatusBarCommand,
|
||||||
|
ShowQuickCommitFileDetails: Commands.ShowQuickCommitFileDetails as StatusBarCommand,
|
||||||
|
ShowQuickFileHistory: Commands.ShowQuickFileHistory as StatusBarCommand,
|
||||||
|
ShowQuickCurrentBranchHistory: Commands.ShowQuickCurrentBranchHistory as StatusBarCommand
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface IStatusBarConfig {
|
||||||
|
enabled: boolean;
|
||||||
|
command: StatusBarCommand;
|
||||||
|
date: 'off' | 'relative' | 'absolute';
|
||||||
|
dateFormat: string;
|
||||||
|
alignment: 'left' | 'right';
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IAdvancedConfig {
|
||||||
|
caching: {
|
||||||
|
enabled: boolean;
|
||||||
|
statusBar: {
|
||||||
|
maxLines: number;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
git: string;
|
||||||
|
gitignore: {
|
||||||
|
enabled: boolean;
|
||||||
|
};
|
||||||
|
maxQuickHistory: number;
|
||||||
|
quickPick: {
|
||||||
|
closeOnFocusOut: boolean;
|
||||||
|
};
|
||||||
|
toggleWhitespace: {
|
||||||
|
enabled: boolean;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IConfig {
|
||||||
|
debug: boolean;
|
||||||
|
outputLevel: OutputLevel;
|
||||||
|
blame: IBlameConfig;
|
||||||
|
codeLens: ICodeLensesConfig;
|
||||||
|
statusBar: IStatusBarConfig;
|
||||||
|
advanced: IAdvancedConfig;
|
||||||
|
insiders: boolean;
|
||||||
|
}
|
||||||
@@ -1,30 +1,39 @@
|
|||||||
'use strict'
|
'use strict';
|
||||||
|
|
||||||
export type WorkspaceState = 'hasGitHistoryExtension' | 'repoPath';
|
export const ExtensionId = 'gitlens';
|
||||||
export const WorkspaceState = {
|
export const ExtensionKey = ExtensionId;
|
||||||
HasGitHistoryExtension: 'hasGitHistoryExtension' as WorkspaceState,
|
export const ExtensionOutputChannelName = 'GitLens';
|
||||||
RepoPath: 'repoPath' as WorkspaceState
|
export const QualifiedExtensionId = `eamodio.${ExtensionId}`;
|
||||||
}
|
|
||||||
|
|
||||||
export const RepoPath: string = 'repoPath';
|
export const ApplicationInsightsKey = 'a9c302f8-6483-4d01-b92c-c159c799c679';
|
||||||
|
|
||||||
export type Commands = 'git.action.diffWithPrevious' | 'git.action.diffWithWorking' | 'git.action.showBlameHistory';
|
export type BuiltInCommands = 'cursorMove' | 'editor.action.showReferences' | 'editor.action.toggleRenderWhitespace' | 'editorScroll' | 'revealLine' | 'setContext' | 'vscode.diff' | 'vscode.executeDocumentSymbolProvider' | 'vscode.executeCodeLensProvider' | 'vscode.open' | 'vscode.previewHtml' | 'workbench.action.closeActiveEditor' | 'workbench.action.nextEditor';
|
||||||
export const Commands = {
|
export const BuiltInCommands = {
|
||||||
DiffWithPrevious: 'git.action.diffWithPrevious' as Commands,
|
CloseActiveEditor: 'workbench.action.closeActiveEditor' as BuiltInCommands,
|
||||||
DiffWithWorking: 'git.action.diffWithWorking' as Commands,
|
CursorMove: 'cursorMove' as BuiltInCommands,
|
||||||
ShowBlameHistory: 'git.action.showBlameHistory' as Commands
|
Diff: 'vscode.diff' as BuiltInCommands,
|
||||||
}
|
EditorScroll: 'editorScroll' as BuiltInCommands,
|
||||||
|
ExecuteDocumentSymbolProvider: 'vscode.executeDocumentSymbolProvider' as BuiltInCommands,
|
||||||
|
ExecuteCodeLensProvider: 'vscode.executeCodeLensProvider' as BuiltInCommands,
|
||||||
|
Open: 'vscode.open' as BuiltInCommands,
|
||||||
|
NextEditor: 'workbench.action.nextEditor' as BuiltInCommands,
|
||||||
|
PreviewHtml: 'vscode.previewHtml' as BuiltInCommands,
|
||||||
|
RevealLine: 'revealLine' as BuiltInCommands,
|
||||||
|
SetContext: 'setContext' as BuiltInCommands,
|
||||||
|
ShowReferences: 'editor.action.showReferences' as BuiltInCommands,
|
||||||
|
ToggleRenderWhitespace: 'editor.action.toggleRenderWhitespace' as BuiltInCommands
|
||||||
|
};
|
||||||
|
|
||||||
export type DocumentSchemes = 'file' | 'gitblame';
|
export type DocumentSchemes = 'file' | 'git' | 'gitlens-git';
|
||||||
export const DocumentSchemes = {
|
export const DocumentSchemes = {
|
||||||
File: 'file' as DocumentSchemes,
|
File: 'file' as DocumentSchemes,
|
||||||
GitBlame: 'gitblame' as DocumentSchemes
|
Git: 'git' as DocumentSchemes,
|
||||||
}
|
GitLensGit: 'gitlens-git' as DocumentSchemes
|
||||||
|
};
|
||||||
|
|
||||||
export type VsCodeCommands = 'vscode.diff' | 'vscode.executeDocumentSymbolProvider' | 'vscode.executeCodeLensProvider' | 'editor.action.showReferences';
|
export type WorkspaceState = 'repoPath' | 'suppressGitVersionWarning' | 'suppressUpdateNotice';
|
||||||
export const VsCodeCommands = {
|
export const WorkspaceState = {
|
||||||
Diff: 'vscode.diff' as VsCodeCommands,
|
GitLensVersion: 'gitlensVersion' as WorkspaceState,
|
||||||
ExecuteDocumentSymbolProvider: 'vscode.executeDocumentSymbolProvider' as VsCodeCommands,
|
SuppressGitVersionWarning: 'suppressGitVersionWarning' as WorkspaceState,
|
||||||
ExecuteCodeLensProvider: 'vscode.executeCodeLensProvider' as VsCodeCommands,
|
SuppressUpdateNotice: 'suppressUpdateNotice' as WorkspaceState
|
||||||
ShowReferences: 'editor.action.showReferences' as VsCodeCommands
|
};
|
||||||
}
|
|
||||||
166
src/extension.ts
@@ -1,39 +1,157 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
import {CodeLens, DocumentSelector, ExtensionContext, extensions, languages, workspace} from 'vscode';
|
import { Objects } from './system';
|
||||||
import GitCodeLensProvider from './gitCodeLensProvider';
|
import { commands, ExtensionContext, extensions, languages, Uri, window, workspace } from 'vscode';
|
||||||
import GitBlameCodeLensProvider from './gitBlameCodeLensProvider';
|
import { BlameActiveLineController } from './blameActiveLineController';
|
||||||
import GitBlameContentProvider from './gitBlameContentProvider';
|
import { BlameAnnotationController } from './blameAnnotationController';
|
||||||
import GitProvider from './gitProvider';
|
import { CommandContext, setCommandContext } from './commands';
|
||||||
import {BlameCommand, DiffWithPreviousCommand, DiffWithWorkingCommand} from './commands';
|
import { CloseUnchangedFilesCommand, OpenChangedFilesCommand } from './commands';
|
||||||
import {WorkspaceState} from './constants';
|
import { OpenBranchInRemoteCommand, OpenCommitInRemoteCommand, OpenFileInRemoteCommand, OpenInRemoteCommand, OpenRepoInRemoteCommand } from './commands';
|
||||||
|
import { CopyMessageToClipboardCommand, CopyShaToClipboardCommand } from './commands';
|
||||||
|
import { DiffDirectoryCommand, DiffLineWithPreviousCommand, DiffLineWithWorkingCommand, DiffWithBranchCommand, DiffWithNextCommand, DiffWithPreviousCommand, DiffWithWorkingCommand} from './commands';
|
||||||
|
import { ShowBlameCommand, ToggleBlameCommand } from './commands';
|
||||||
|
import { ShowBlameHistoryCommand, ShowFileHistoryCommand } from './commands';
|
||||||
|
import { ShowLastQuickPickCommand } from './commands';
|
||||||
|
import { ShowQuickBranchHistoryCommand, ShowQuickCurrentBranchHistoryCommand, ShowQuickFileHistoryCommand } from './commands';
|
||||||
|
import { ShowQuickCommitDetailsCommand, ShowQuickCommitFileDetailsCommand, ShowCommitSearchCommand } from './commands';
|
||||||
|
import { ShowQuickRepoStatusCommand, ShowQuickStashListCommand } from './commands';
|
||||||
|
import { StashApplyCommand, StashDeleteCommand, StashSaveCommand } from './commands';
|
||||||
|
import { ToggleCodeLensCommand } from './commands';
|
||||||
|
import { Keyboard } from './commands';
|
||||||
|
import { IConfig } from './configuration';
|
||||||
|
import { ApplicationInsightsKey, BuiltInCommands, ExtensionKey, QualifiedExtensionId, WorkspaceState } from './constants';
|
||||||
|
import { GitContentProvider } from './gitContentProvider';
|
||||||
|
import { GitContextTracker, GitService } from './gitService';
|
||||||
|
import { GitRevisionCodeLensProvider } from './gitRevisionCodeLensProvider';
|
||||||
|
import { Logger } from './logger';
|
||||||
|
import { Telemetry } from './telemetry';
|
||||||
|
|
||||||
// this method is called when your extension is activated
|
// this method is called when your extension is activated
|
||||||
export function activate(context: ExtensionContext) {
|
export async function activate(context: ExtensionContext) {
|
||||||
// Workspace not using a folder. No access to git repo.
|
Logger.configure(context);
|
||||||
if (!workspace.rootPath) {
|
Telemetry.configure(ApplicationInsightsKey);
|
||||||
console.warn('GitLens inactive: no rootPath');
|
|
||||||
|
|
||||||
|
const gitlens = extensions.getExtension(QualifiedExtensionId)!;
|
||||||
|
const gitlensVersion = gitlens.packageJSON.version;
|
||||||
|
|
||||||
|
const rootPath = workspace.rootPath && workspace.rootPath.replace(/\\/g, '/');
|
||||||
|
Logger.log(`GitLens(v${gitlensVersion}) active: ${rootPath}`);
|
||||||
|
|
||||||
|
const cfg = workspace.getConfiguration().get<IConfig>(ExtensionKey)!;
|
||||||
|
const gitPath = cfg.advanced.git;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await GitService.getGitPath(gitPath);
|
||||||
|
}
|
||||||
|
catch (ex) {
|
||||||
|
Logger.error(ex, 'Extension.activate');
|
||||||
|
if (ex.message.includes('Unable to find git')) {
|
||||||
|
await window.showErrorMessage(`GitLens was unable to find Git. Please make sure Git is installed. Also ensure that Git is either in the PATH, or that 'gitlens.advanced.git' is pointed to its installed location.`);
|
||||||
|
}
|
||||||
|
setCommandContext(CommandContext.Enabled, false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`GitLens active: ${workspace.rootPath}`);
|
const repoPath = await GitService.getRepoPath(rootPath);
|
||||||
|
|
||||||
const git = new GitProvider(context);
|
const gitVersion = GitService.getGitVersion();
|
||||||
|
Logger.log(`Git version: ${gitVersion}`);
|
||||||
|
|
||||||
|
const telemetryContext: { [id: string]: any } = Object.create(null);
|
||||||
|
telemetryContext.version = gitlensVersion;
|
||||||
|
telemetryContext['git.version'] = gitVersion;
|
||||||
|
Telemetry.setContext(telemetryContext);
|
||||||
|
|
||||||
|
notifyOnUnsupportedGitVersion(context, gitVersion);
|
||||||
|
notifyOnNewGitLensVersion(context, gitlensVersion);
|
||||||
|
|
||||||
|
const git = new GitService(context, repoPath);
|
||||||
context.subscriptions.push(git);
|
context.subscriptions.push(git);
|
||||||
|
|
||||||
git.getRepoPath(workspace.rootPath).then(repoPath => {
|
const gitContextTracker = new GitContextTracker(git);
|
||||||
context.workspaceState.update(WorkspaceState.RepoPath, repoPath);
|
context.subscriptions.push(gitContextTracker);
|
||||||
context.workspaceState.update(WorkspaceState.HasGitHistoryExtension, extensions.getExtension('donjayamanne.githistory') !== undefined);
|
|
||||||
|
|
||||||
context.subscriptions.push(workspace.registerTextDocumentContentProvider(GitBlameContentProvider.scheme, new GitBlameContentProvider(context, git)));
|
context.subscriptions.push(workspace.registerTextDocumentContentProvider(GitContentProvider.scheme, new GitContentProvider(context, git)));
|
||||||
context.subscriptions.push(languages.registerCodeLensProvider(GitCodeLensProvider.selector, new GitCodeLensProvider(context, git)));
|
|
||||||
context.subscriptions.push(languages.registerCodeLensProvider(GitBlameCodeLensProvider.selector, new GitBlameCodeLensProvider(context, git)));
|
context.subscriptions.push(languages.registerCodeLensProvider(GitRevisionCodeLensProvider.selector, new GitRevisionCodeLensProvider(context, git)));
|
||||||
context.subscriptions.push(new BlameCommand(git));
|
|
||||||
context.subscriptions.push(new DiffWithPreviousCommand(git));
|
const annotationController = new BlameAnnotationController(context, git, gitContextTracker);
|
||||||
context.subscriptions.push(new DiffWithWorkingCommand(git));
|
context.subscriptions.push(annotationController);
|
||||||
}).catch(reason => console.warn(reason));
|
|
||||||
|
const activeLineController = new BlameActiveLineController(context, git, gitContextTracker, annotationController);
|
||||||
|
context.subscriptions.push(activeLineController);
|
||||||
|
|
||||||
|
context.subscriptions.push(new Keyboard());
|
||||||
|
|
||||||
|
context.subscriptions.push(new CloseUnchangedFilesCommand(git));
|
||||||
|
context.subscriptions.push(new OpenChangedFilesCommand(git));
|
||||||
|
context.subscriptions.push(new CopyMessageToClipboardCommand(git));
|
||||||
|
context.subscriptions.push(new CopyShaToClipboardCommand(git));
|
||||||
|
context.subscriptions.push(new DiffDirectoryCommand(git));
|
||||||
|
context.subscriptions.push(new DiffLineWithPreviousCommand(git));
|
||||||
|
context.subscriptions.push(new DiffLineWithWorkingCommand(git));
|
||||||
|
context.subscriptions.push(new DiffWithBranchCommand(git));
|
||||||
|
context.subscriptions.push(new DiffWithNextCommand(git));
|
||||||
|
context.subscriptions.push(new DiffWithPreviousCommand(git));
|
||||||
|
context.subscriptions.push(new DiffWithWorkingCommand(git));
|
||||||
|
context.subscriptions.push(new OpenBranchInRemoteCommand(git));
|
||||||
|
context.subscriptions.push(new OpenCommitInRemoteCommand(git));
|
||||||
|
context.subscriptions.push(new OpenFileInRemoteCommand(git));
|
||||||
|
context.subscriptions.push(new OpenInRemoteCommand());
|
||||||
|
context.subscriptions.push(new OpenRepoInRemoteCommand(git));
|
||||||
|
context.subscriptions.push(new ShowBlameCommand(annotationController));
|
||||||
|
context.subscriptions.push(new ToggleBlameCommand(annotationController));
|
||||||
|
context.subscriptions.push(new ShowBlameHistoryCommand(git));
|
||||||
|
context.subscriptions.push(new ShowFileHistoryCommand(git));
|
||||||
|
context.subscriptions.push(new ShowLastQuickPickCommand());
|
||||||
|
context.subscriptions.push(new ShowQuickBranchHistoryCommand(git));
|
||||||
|
context.subscriptions.push(new ShowQuickCurrentBranchHistoryCommand(git));
|
||||||
|
context.subscriptions.push(new ShowQuickCommitDetailsCommand(git));
|
||||||
|
context.subscriptions.push(new ShowQuickCommitFileDetailsCommand(git));
|
||||||
|
context.subscriptions.push(new ShowCommitSearchCommand(git));
|
||||||
|
context.subscriptions.push(new ShowQuickFileHistoryCommand(git));
|
||||||
|
context.subscriptions.push(new ShowQuickRepoStatusCommand(git));
|
||||||
|
context.subscriptions.push(new ShowQuickStashListCommand(git));
|
||||||
|
context.subscriptions.push(new StashApplyCommand(git));
|
||||||
|
context.subscriptions.push(new StashDeleteCommand(git));
|
||||||
|
context.subscriptions.push(new StashSaveCommand(git));
|
||||||
|
context.subscriptions.push(new ToggleCodeLensCommand(git));
|
||||||
|
|
||||||
|
Telemetry.trackEvent('initialized', Objects.flatten(cfg, 'config', true));
|
||||||
}
|
}
|
||||||
|
|
||||||
// this method is called when your extension is deactivated
|
// this method is called when your extension is deactivated
|
||||||
export function deactivate() {
|
export function deactivate() { }
|
||||||
|
|
||||||
|
async function notifyOnNewGitLensVersion(context: ExtensionContext, version: string) {
|
||||||
|
if (context.globalState.get(WorkspaceState.SuppressUpdateNotice, false)) return;
|
||||||
|
|
||||||
|
const previousVersion = context.globalState.get<string>(WorkspaceState.GitLensVersion);
|
||||||
|
|
||||||
|
await context.globalState.update(WorkspaceState.GitLensVersion, version);
|
||||||
|
|
||||||
|
if (previousVersion) {
|
||||||
|
const [major, minor] = version.split('.');
|
||||||
|
const [prevMajor, prevMinor] = previousVersion.split('.');
|
||||||
|
if (major === prevMajor && minor === prevMinor) return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await window.showInformationMessage(`GitLens has been updated to v${version}`, 'View Release Notes', `Don't Show Again`);
|
||||||
|
if (result === 'View Release Notes') {
|
||||||
|
commands.executeCommand(BuiltInCommands.Open, Uri.parse('https://marketplace.visualstudio.com/items/eamodio.gitlens/changelog'));
|
||||||
|
}
|
||||||
|
else if (result === `Don't Show Again`) {
|
||||||
|
context.globalState.update(WorkspaceState.SuppressUpdateNotice, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function notifyOnUnsupportedGitVersion(context: ExtensionContext, version: string) {
|
||||||
|
if (context.globalState.get(WorkspaceState.SuppressGitVersionWarning, false)) return;
|
||||||
|
|
||||||
|
// If git is less than v2.2.0
|
||||||
|
if (!GitService.validateGitVersion(2, 2)) {
|
||||||
|
const result = await window.showErrorMessage(`GitLens requires a newer version of Git (>= 2.2.0) than is currently installed (${version}). Please install a more recent version of Git.`, `Don't Show Again`);
|
||||||
|
if (result === `Don't Show Again`) {
|
||||||
|
context.globalState.update(WorkspaceState.SuppressGitVersionWarning, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
78
src/git.ts
@@ -1,78 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
import {basename, dirname, extname, isAbsolute, relative} from 'path';
|
|
||||||
import * as fs from 'fs';
|
|
||||||
import * as tmp from 'tmp';
|
|
||||||
import {spawnPromise} from 'spawn-rx';
|
|
||||||
|
|
||||||
function gitCommand(cwd: string, ...args) {
|
|
||||||
return spawnPromise('git', args, { cwd: cwd });
|
|
||||||
}
|
|
||||||
|
|
||||||
export default class Git {
|
|
||||||
static normalizePath(fileName: string, repoPath: string) {
|
|
||||||
fileName = fileName.replace(/\\/g, '/');
|
|
||||||
return isAbsolute(fileName) ? relative(repoPath, fileName) : fileName;
|
|
||||||
}
|
|
||||||
|
|
||||||
static repoPath(cwd: string) {
|
|
||||||
return gitCommand(cwd, 'rev-parse', '--show-toplevel').then(data => data.replace(/\r?\n|\r/g, ''));
|
|
||||||
}
|
|
||||||
|
|
||||||
static blame(fileName: string, repoPath: string, sha?: string) {
|
|
||||||
fileName = Git.normalizePath(fileName, repoPath);
|
|
||||||
|
|
||||||
if (sha) {
|
|
||||||
console.log('git', 'blame', '-fnw', '--root', `${sha}^`, '--', fileName);
|
|
||||||
return gitCommand(repoPath, 'blame', '-fnw', '--root', `${sha}^`, '--', fileName);
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('git', 'blame', '-fnw', '--root', '--', fileName);
|
|
||||||
return gitCommand(repoPath, 'blame', '-fnw', '--root', '--', fileName);
|
|
||||||
// .then(s => { console.log(s); return s; })
|
|
||||||
// .catch(ex => console.error(ex));
|
|
||||||
}
|
|
||||||
|
|
||||||
static getVersionedFile(fileName: string, repoPath: string, sha: string) {
|
|
||||||
return new Promise<string>((resolve, reject) => {
|
|
||||||
Git.getVersionedFileText(fileName, repoPath, sha).then(data => {
|
|
||||||
let ext = extname(fileName);
|
|
||||||
tmp.file({ prefix: `${basename(fileName, ext)}-${sha}_`, postfix: ext }, (err, destination, fd, cleanupCallback) => {
|
|
||||||
if (err) {
|
|
||||||
reject(err);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log("File: ", destination);
|
|
||||||
console.log("Filedescriptor: ", fd);
|
|
||||||
|
|
||||||
fs.appendFile(destination, data, err => {
|
|
||||||
if (err) {
|
|
||||||
reject(err);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
resolve(destination);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
static getVersionedFileText(fileName: string, repoPath: string, sha: string) {
|
|
||||||
fileName = Git.normalizePath(fileName, repoPath);
|
|
||||||
sha = sha.replace('^', '');
|
|
||||||
|
|
||||||
console.log('git', 'show', `${sha}:${fileName}`);
|
|
||||||
return gitCommand(repoPath, 'show', `${sha}:${fileName}`);
|
|
||||||
// .then(s => { console.log(s); return s; })
|
|
||||||
// .catch(ex => console.error(ex));
|
|
||||||
}
|
|
||||||
|
|
||||||
static getCommitMessage(sha: string, repoPath: string) {
|
|
||||||
sha = sha.replace('^', '');
|
|
||||||
|
|
||||||
console.log('git', 'show', '-s', '--format=%B', sha);
|
|
||||||
return gitCommand(repoPath, 'show', '-s', '--format=%B', sha);
|
|
||||||
// .then(s => { console.log(s); return s; })
|
|
||||||
// .catch(ex => console.error(ex));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
326
src/git/git.ts
Normal file
@@ -0,0 +1,326 @@
|
|||||||
|
'use strict';
|
||||||
|
import { findGitPath, IGit } from './gitLocator';
|
||||||
|
import { Logger } from '../logger';
|
||||||
|
import { spawnPromise } from 'spawn-rx';
|
||||||
|
import * as fs from 'fs';
|
||||||
|
import * as path from 'path';
|
||||||
|
import * as tmp from 'tmp';
|
||||||
|
|
||||||
|
export { IGit };
|
||||||
|
export * from './models/models';
|
||||||
|
export * from './parsers/blameParser';
|
||||||
|
export * from './parsers/diffParser';
|
||||||
|
export * from './parsers/logParser';
|
||||||
|
export * from './parsers/stashParser';
|
||||||
|
export * from './parsers/statusParser';
|
||||||
|
export * from './remotes/provider';
|
||||||
|
|
||||||
|
let git: IGit;
|
||||||
|
|
||||||
|
// `--format=%H -%nauthor %an%nauthor-date %ai%ncommitter %cn%ncommitter-date %ci%nparents %P%nsummary %B%nfilename ?`
|
||||||
|
const defaultLogParams = [`log`, `--name-status`, `--full-history`, `-M`, `--date=iso8601`, `--format=%H -%nauthor %an%nauthor-date %ai%nparents %P%nsummary %B%nfilename ?`];
|
||||||
|
const defaultStashParams = [`stash`, `list`, `--name-status`, `--full-history`, `-M`, `--format=%H -%nauthor-date %ai%nreflog-selector %gd%nsummary %B%nfilename ?`];
|
||||||
|
|
||||||
|
async function gitCommand(cwd: string, ...args: any[]) {
|
||||||
|
try {
|
||||||
|
// Fixes https://github.com/eamodio/vscode-gitlens/issues/73
|
||||||
|
// See https://stackoverflow.com/questions/4144417/how-to-handle-asian-characters-in-file-names-in-git-on-os-x
|
||||||
|
args.splice(0, 0, '-c', 'core.quotepath=false');
|
||||||
|
|
||||||
|
const s = await spawnPromise(git.path, args, { cwd: cwd });
|
||||||
|
Logger.log('git', ...args, ` cwd='${cwd}'`);
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
catch (ex) {
|
||||||
|
const msg = ex && ex.toString();
|
||||||
|
if (msg && (msg.includes('Not a git repository') || msg.includes('is outside repository') || msg.includes('no such path') || msg.includes('does not have any commits'))) {
|
||||||
|
Logger.warn('git', ...args, ` cwd='${cwd}'`, msg && `\n ${msg.replace(/\r?\n|\r/g, ' ')}`);
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Logger.error(ex, 'git', ...args, ` cwd='${cwd}'`, msg && `\n ${msg.replace(/\r?\n|\r/g, ' ')}`);
|
||||||
|
}
|
||||||
|
throw ex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Git {
|
||||||
|
|
||||||
|
static shaRegex = /^[0-9a-f]{40}( -)?$/;
|
||||||
|
static uncommittedRegex = /^[0]+$/;
|
||||||
|
|
||||||
|
static gitInfo(): IGit {
|
||||||
|
return git;
|
||||||
|
}
|
||||||
|
|
||||||
|
static async getGitPath(gitPath?: string): Promise<IGit> {
|
||||||
|
git = await findGitPath(gitPath);
|
||||||
|
Logger.log(`Git found: ${git.version} @ ${git.path === 'git' ? 'PATH' : git.path}`);
|
||||||
|
return git;
|
||||||
|
}
|
||||||
|
|
||||||
|
static async getRepoPath(cwd: string | undefined) {
|
||||||
|
if (cwd === undefined) return '';
|
||||||
|
|
||||||
|
const data = await gitCommand(cwd, 'rev-parse', '--show-toplevel');
|
||||||
|
if (!data) return '';
|
||||||
|
|
||||||
|
return data.replace(/\r?\n|\r/g, '').replace(/\\/g, '/');
|
||||||
|
}
|
||||||
|
|
||||||
|
static async getVersionedFile(repoPath: string | undefined, fileName: string, branchOrSha: string) {
|
||||||
|
const data = await Git.show(repoPath, fileName, branchOrSha);
|
||||||
|
|
||||||
|
const suffix = Git.isSha(branchOrSha) ? branchOrSha.substring(0, 8) : branchOrSha;
|
||||||
|
const ext = path.extname(fileName);
|
||||||
|
return new Promise<string>((resolve, reject) => {
|
||||||
|
tmp.file({ prefix: `${path.basename(fileName, ext)}-${suffix}__`, postfix: ext },
|
||||||
|
(err, destination, fd, cleanupCallback) => {
|
||||||
|
if (err) {
|
||||||
|
reject(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.log(`getVersionedFile('${repoPath}', '${fileName}', ${branchOrSha}); destination=${destination}`);
|
||||||
|
fs.appendFile(destination, data, err => {
|
||||||
|
if (err) {
|
||||||
|
reject(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve(destination);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static isSha(sha: string) {
|
||||||
|
return Git.shaRegex.test(sha);
|
||||||
|
}
|
||||||
|
|
||||||
|
static isUncommitted(sha: string) {
|
||||||
|
return Git.uncommittedRegex.test(sha);
|
||||||
|
}
|
||||||
|
|
||||||
|
static normalizePath(fileName: string, repoPath?: string) {
|
||||||
|
return fileName && fileName.replace(/\\/g, '/');
|
||||||
|
}
|
||||||
|
|
||||||
|
static splitPath(fileName: string, repoPath: string | undefined, extract: boolean = true): [string, string] {
|
||||||
|
if (repoPath) {
|
||||||
|
fileName = this.normalizePath(fileName);
|
||||||
|
repoPath = this.normalizePath(repoPath);
|
||||||
|
|
||||||
|
const normalizedRepoPath = (repoPath.endsWith('/') ? repoPath : `${repoPath}/`).toLowerCase();
|
||||||
|
if (fileName.toLowerCase().startsWith(normalizedRepoPath)) {
|
||||||
|
fileName = fileName.substring(normalizedRepoPath.length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
repoPath = this.normalizePath(extract ? path.dirname(fileName) : repoPath!);
|
||||||
|
fileName = this.normalizePath(extract ? path.basename(fileName) : fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
return [ fileName, repoPath ];
|
||||||
|
}
|
||||||
|
|
||||||
|
static validateVersion(major: number, minor: number): boolean {
|
||||||
|
const [gitMajor, gitMinor] = git.version.split('.');
|
||||||
|
return (parseInt(gitMajor, 10) >= major && parseInt(gitMinor, 10) >= minor);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Git commands
|
||||||
|
|
||||||
|
static blame(repoPath: string | undefined, fileName: string, sha?: string, startLine?: number, endLine?: number) {
|
||||||
|
const [file, root] = Git.splitPath(fileName, repoPath);
|
||||||
|
|
||||||
|
const params = [`blame`, `--root`, `--incremental`];
|
||||||
|
|
||||||
|
if (startLine != null && endLine != null) {
|
||||||
|
params.push(`-L ${startLine},${endLine}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sha) {
|
||||||
|
params.push(sha);
|
||||||
|
}
|
||||||
|
|
||||||
|
return gitCommand(root, ...params, `--`, file);
|
||||||
|
}
|
||||||
|
|
||||||
|
static branch(repoPath: string, all: boolean) {
|
||||||
|
const params = [`branch`];
|
||||||
|
if (all) {
|
||||||
|
params.push(`-a`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return gitCommand(repoPath, ...params);
|
||||||
|
}
|
||||||
|
|
||||||
|
static async config_get(key: string, repoPath?: string) {
|
||||||
|
try {
|
||||||
|
return await gitCommand(repoPath || '', `config`, `--get`, key);
|
||||||
|
}
|
||||||
|
catch (ex) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static diff(repoPath: string, fileName: string, sha1?: string, sha2?: string) {
|
||||||
|
const params = [`diff`, `--diff-filter=M`, `-M`];
|
||||||
|
if (sha1) {
|
||||||
|
params.push(sha1);
|
||||||
|
}
|
||||||
|
if (sha2) {
|
||||||
|
params.push(sha2);
|
||||||
|
}
|
||||||
|
|
||||||
|
return gitCommand(repoPath, ...params, '--', fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
static diff_nameStatus(repoPath: string, sha1?: string, sha2?: string) {
|
||||||
|
const params = [`diff`, `--name-status`, `-M`];
|
||||||
|
if (sha1) {
|
||||||
|
params.push(sha1);
|
||||||
|
}
|
||||||
|
if (sha2) {
|
||||||
|
params.push(sha2);
|
||||||
|
}
|
||||||
|
|
||||||
|
return gitCommand(repoPath, ...params);
|
||||||
|
}
|
||||||
|
|
||||||
|
static difftool_dirDiff(repoPath: string, sha1: string, sha2?: string) {
|
||||||
|
const params = [`difftool`, `--dir-diff`, sha1];
|
||||||
|
if (sha2) {
|
||||||
|
params.push(sha2);
|
||||||
|
}
|
||||||
|
|
||||||
|
return gitCommand(repoPath, ...params);
|
||||||
|
}
|
||||||
|
|
||||||
|
static log(repoPath: string, sha?: string, maxCount?: number, reverse: boolean = false) {
|
||||||
|
const params = [...defaultLogParams, `-m`];
|
||||||
|
if (maxCount && !reverse) {
|
||||||
|
params.push(`-n${maxCount}`);
|
||||||
|
}
|
||||||
|
if (sha) {
|
||||||
|
if (reverse) {
|
||||||
|
params.push(`--reverse`);
|
||||||
|
params.push(`--ancestry-path`);
|
||||||
|
params.push(`${sha}..HEAD`);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
params.push(sha);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return gitCommand(repoPath, ...params);
|
||||||
|
}
|
||||||
|
|
||||||
|
static log_file(repoPath: string, fileName: string, sha?: string, maxCount?: number, reverse: boolean = false, startLine?: number, endLine?: number) {
|
||||||
|
const [file, root] = Git.splitPath(fileName, repoPath);
|
||||||
|
|
||||||
|
const params = [...defaultLogParams, `--follow`];
|
||||||
|
if (maxCount && !reverse) {
|
||||||
|
params.push(`-n${maxCount}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we are looking for a specific sha don't exclude merge commits
|
||||||
|
if (!sha || maxCount! > 2) {
|
||||||
|
params.push(`--no-merges`);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
params.push(`-m`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sha) {
|
||||||
|
if (reverse) {
|
||||||
|
params.push(`--reverse`);
|
||||||
|
params.push(`--ancestry-path`);
|
||||||
|
params.push(`${sha}..HEAD`);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
params.push(sha);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (startLine != null && endLine != null) {
|
||||||
|
params.push(`-L ${startLine},${endLine}:${file}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
params.push(`--`);
|
||||||
|
params.push(file);
|
||||||
|
|
||||||
|
return gitCommand(root, ...params);
|
||||||
|
}
|
||||||
|
|
||||||
|
static log_search(repoPath: string, search: string[] = [], maxCount?: number) {
|
||||||
|
const params = [...defaultLogParams, `-m`, `-i`];
|
||||||
|
if (maxCount) {
|
||||||
|
params.push(`-n${maxCount}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return gitCommand(repoPath, ...params, ...search);
|
||||||
|
}
|
||||||
|
|
||||||
|
static async ls_files(repoPath: string, fileName: string): Promise<string> {
|
||||||
|
try {
|
||||||
|
return await gitCommand(repoPath, 'ls-files', fileName);
|
||||||
|
}
|
||||||
|
catch (ex) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static remote(repoPath: string): Promise<string> {
|
||||||
|
return gitCommand(repoPath, 'remote', '-v');
|
||||||
|
}
|
||||||
|
|
||||||
|
static remote_url(repoPath: string, remote: string): Promise<string> {
|
||||||
|
return gitCommand(repoPath, 'remote', 'get-url', remote);
|
||||||
|
}
|
||||||
|
|
||||||
|
static show(repoPath: string | undefined, fileName: string, branchOrSha: string) {
|
||||||
|
const [file, root] = Git.splitPath(fileName, repoPath);
|
||||||
|
branchOrSha = branchOrSha.replace('^', '');
|
||||||
|
|
||||||
|
if (Git.isUncommitted(branchOrSha)) return Promise.reject(new Error(`sha=${branchOrSha} is uncommitted`));
|
||||||
|
return gitCommand(root, 'show', `${branchOrSha}:./${file}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
static stash_apply(repoPath: string, stashName: string, deleteAfter: boolean) {
|
||||||
|
if (!stashName) return undefined;
|
||||||
|
return gitCommand(repoPath, 'stash', deleteAfter ? 'pop' : 'apply', stashName);
|
||||||
|
}
|
||||||
|
|
||||||
|
static stash_delete(repoPath: string, stashName: string) {
|
||||||
|
if (!stashName) return undefined;
|
||||||
|
return gitCommand(repoPath, 'stash', 'drop', stashName);
|
||||||
|
}
|
||||||
|
|
||||||
|
static stash_list(repoPath: string) {
|
||||||
|
return gitCommand(repoPath, ...defaultStashParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
static stash_save(repoPath: string, message?: string, unstagedOnly: boolean = false) {
|
||||||
|
const params = [`stash`, `save`, `--include-untracked`];
|
||||||
|
if (unstagedOnly) {
|
||||||
|
params.push(`--keep-index`);
|
||||||
|
}
|
||||||
|
if (message) {
|
||||||
|
params.push(message);
|
||||||
|
}
|
||||||
|
return gitCommand(repoPath, ...params);
|
||||||
|
}
|
||||||
|
|
||||||
|
static status(repoPath: string, porcelainVersion: number = 1): Promise<string> {
|
||||||
|
const porcelain = porcelainVersion >= 2 ? `--porcelain=v${porcelainVersion}` : '--porcelain';
|
||||||
|
return gitCommand(repoPath, 'status', porcelain, '--branch');
|
||||||
|
}
|
||||||
|
|
||||||
|
static status_file(repoPath: string, fileName: string, porcelainVersion: number = 1): Promise<string> {
|
||||||
|
const [file, root] = Git.splitPath(fileName, repoPath);
|
||||||
|
|
||||||
|
const porcelain = porcelainVersion >= 2 ? `--porcelain=v${porcelainVersion}` : '--porcelain';
|
||||||
|
return gitCommand(root, 'status', porcelain, file);
|
||||||
|
}
|
||||||
|
}
|
||||||
163
src/git/gitContextTracker.ts
Normal file
@@ -0,0 +1,163 @@
|
|||||||
|
'use strict';
|
||||||
|
import { Disposable, Event, EventEmitter, TextDocument, TextDocumentChangeEvent, TextEditor, window, workspace } from 'vscode';
|
||||||
|
import { CommandContext, setCommandContext } from '../commands';
|
||||||
|
import { TextDocumentComparer } from '../comparers';
|
||||||
|
import { GitService, GitUri } from '../gitService';
|
||||||
|
import { Logger } from '../logger';
|
||||||
|
|
||||||
|
export interface BlameabilityChangeEvent {
|
||||||
|
blameable: boolean;
|
||||||
|
editor: TextEditor | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class GitContextTracker extends Disposable {
|
||||||
|
|
||||||
|
private _onDidBlameabilityChange = new EventEmitter<BlameabilityChangeEvent>();
|
||||||
|
get onDidBlameabilityChange(): Event<BlameabilityChangeEvent> {
|
||||||
|
return this._onDidBlameabilityChange.event;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _disposable: Disposable;
|
||||||
|
private _documentChangeDisposable: Disposable | undefined;
|
||||||
|
private _editor: TextEditor | undefined;
|
||||||
|
private _gitEnabled: boolean;
|
||||||
|
private _isBlameable: boolean;
|
||||||
|
|
||||||
|
constructor(private git: GitService) {
|
||||||
|
super(() => this.dispose());
|
||||||
|
|
||||||
|
const subscriptions: Disposable[] = [];
|
||||||
|
|
||||||
|
subscriptions.push(window.onDidChangeActiveTextEditor(this._onActiveTextEditorChanged, this));
|
||||||
|
subscriptions.push(workspace.onDidChangeConfiguration(this._onConfigurationChanged, this));
|
||||||
|
subscriptions.push(workspace.onDidSaveTextDocument(this._onTextDocumentSaved, this));
|
||||||
|
subscriptions.push(this.git.onDidBlameFail(this._onBlameFailed, this));
|
||||||
|
|
||||||
|
this._disposable = Disposable.from(...subscriptions);
|
||||||
|
|
||||||
|
setCommandContext(CommandContext.IsRepository, !!this.git.repoPath);
|
||||||
|
|
||||||
|
this._onConfigurationChanged();
|
||||||
|
this._onActiveTextEditorChanged(window.activeTextEditor);
|
||||||
|
}
|
||||||
|
|
||||||
|
dispose() {
|
||||||
|
this._disposable && this._disposable.dispose();
|
||||||
|
this._documentChangeDisposable && this._documentChangeDisposable.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
_onConfigurationChanged() {
|
||||||
|
const gitEnabled = workspace.getConfiguration('git').get<boolean>('enabled', true);
|
||||||
|
if (this._gitEnabled !== gitEnabled) {
|
||||||
|
this._gitEnabled = gitEnabled;
|
||||||
|
setCommandContext(CommandContext.Enabled, gitEnabled);
|
||||||
|
this._onActiveTextEditorChanged(window.activeTextEditor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _onActiveTextEditorChanged(editor: TextEditor | undefined) {
|
||||||
|
this._editor = editor;
|
||||||
|
this._updateContext(this._gitEnabled ? editor : undefined);
|
||||||
|
this._subscribeToDocumentChanges();
|
||||||
|
}
|
||||||
|
|
||||||
|
private _onBlameFailed(key: string) {
|
||||||
|
const fileName = this._editor && this._editor.document && this._editor.document.fileName;
|
||||||
|
if (!fileName || key !== this.git.getCacheEntryKey(fileName)) return;
|
||||||
|
|
||||||
|
this._updateBlameability(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _onTextDocumentChanged(e: TextDocumentChangeEvent) {
|
||||||
|
if (!TextDocumentComparer.equals(this._editor && this._editor.document, e && e.document)) return;
|
||||||
|
|
||||||
|
// Can't unsubscribe here because undo doesn't trigger any other event
|
||||||
|
//this._unsubscribeToDocumentChanges();
|
||||||
|
//this.updateBlameability(false);
|
||||||
|
|
||||||
|
// We have to defer because isDirty is not reliable inside this event
|
||||||
|
setTimeout(() => this._updateBlameability(!e.document.isDirty), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _onTextDocumentSaved(e: TextDocument) {
|
||||||
|
if (!TextDocumentComparer.equals(this._editor && this._editor.document, e)) return;
|
||||||
|
|
||||||
|
// Don't need to resubscribe as we aren't unsubscribing on document changes anymore
|
||||||
|
//this._subscribeToDocumentChanges();
|
||||||
|
this._updateBlameability(!e.isDirty);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _subscribeToDocumentChanges() {
|
||||||
|
this._unsubscribeToDocumentChanges();
|
||||||
|
this._documentChangeDisposable = workspace.onDidChangeTextDocument(this._onTextDocumentChanged, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _unsubscribeToDocumentChanges() {
|
||||||
|
this._documentChangeDisposable && this._documentChangeDisposable.dispose();
|
||||||
|
this._documentChangeDisposable = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _updateContext(editor: TextEditor | undefined) {
|
||||||
|
try {
|
||||||
|
const gitUri = editor === undefined ? undefined : await GitUri.fromUri(editor.document.uri, this.git);
|
||||||
|
|
||||||
|
await Promise.all([
|
||||||
|
this._updateEditorContext(gitUri, editor),
|
||||||
|
this._updateContextHasRemotes(gitUri)
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
catch (ex) {
|
||||||
|
Logger.error(ex, 'GitEditorTracker._updateContext');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _updateContextHasRemotes(uri: GitUri | undefined) {
|
||||||
|
try {
|
||||||
|
let hasRemotes = false;
|
||||||
|
if (uri && this.git.isTrackable(uri)) {
|
||||||
|
const repoPath = uri.repoPath || this.git.repoPath;
|
||||||
|
if (repoPath) {
|
||||||
|
const remotes = await this.git.getRemotes(repoPath);
|
||||||
|
hasRemotes = remotes.length !== 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setCommandContext(CommandContext.HasRemotes, hasRemotes);
|
||||||
|
}
|
||||||
|
catch (ex) {
|
||||||
|
Logger.error(ex, 'GitEditorTracker._updateContextHasRemotes');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _updateEditorContext(uri: GitUri | undefined, editor: TextEditor | undefined) {
|
||||||
|
try {
|
||||||
|
const tracked = uri === undefined ? false : await this.git.isTracked(uri);
|
||||||
|
setCommandContext(CommandContext.IsTracked, tracked);
|
||||||
|
|
||||||
|
let blameable = tracked && (editor !== undefined && editor.document !== undefined && !editor.document.isDirty);
|
||||||
|
if (blameable) {
|
||||||
|
blameable = uri === undefined ? false : await this.git.getBlameability(uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
this._updateBlameability(blameable, true);
|
||||||
|
}
|
||||||
|
catch (ex) {
|
||||||
|
Logger.error(ex, 'GitEditorTracker._updateEditorContext');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _updateBlameability(blameable: boolean, force: boolean = false) {
|
||||||
|
if (!force && this._isBlameable === blameable) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
setCommandContext(CommandContext.IsBlameable, blameable);
|
||||||
|
this._onDidBlameabilityChange.fire({
|
||||||
|
blameable: blameable,
|
||||||
|
editor: this._editor
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (ex) {
|
||||||
|
Logger.error(ex, 'GitEditorTracker._updateBlameability');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
80
src/git/gitLocator.ts
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
'use strict';
|
||||||
|
import { findActualExecutable, spawnPromise } from 'spawn-rx';
|
||||||
|
import * as path from 'path';
|
||||||
|
|
||||||
|
export interface IGit {
|
||||||
|
path: string;
|
||||||
|
version: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseVersion(raw: string): string {
|
||||||
|
return raw.replace(/^git version /, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
async function findSpecificGit(path: string): Promise<IGit> {
|
||||||
|
const version = await spawnPromise(path, ['--version']);
|
||||||
|
// If needed, let's update our path to avoid the search on every command
|
||||||
|
if (!path || path === 'git') {
|
||||||
|
path = findActualExecutable(path, ['--version']).cmd;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
path,
|
||||||
|
version: parseVersion(version.trim())
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function findGitDarwin(): Promise<IGit> {
|
||||||
|
try {
|
||||||
|
let path = await spawnPromise('which', ['git']);
|
||||||
|
path = path.replace(/^\s+|\s+$/g, '');
|
||||||
|
|
||||||
|
if (path !== '/usr/bin/git') {
|
||||||
|
return findSpecificGit(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await spawnPromise('xcode-select', ['-p']);
|
||||||
|
return findSpecificGit(path);
|
||||||
|
}
|
||||||
|
catch (ex) {
|
||||||
|
if (ex.code === 2) {
|
||||||
|
return Promise.reject(new Error('Unable to find git'));
|
||||||
|
}
|
||||||
|
return findSpecificGit(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (ex) {
|
||||||
|
return Promise.reject(new Error('Unable to find git'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function findSystemGitWin32(basePath: string): Promise<IGit> {
|
||||||
|
if (!basePath) return Promise.reject(new Error('Unable to find git'));
|
||||||
|
return findSpecificGit(path.join(basePath, 'Git', 'cmd', 'git.exe'));
|
||||||
|
}
|
||||||
|
|
||||||
|
function findGitWin32(): Promise<IGit> {
|
||||||
|
return findSystemGitWin32(process.env['ProgramW6432'])
|
||||||
|
.then(null, () => findSystemGitWin32(process.env['ProgramFiles(x86)']))
|
||||||
|
.then(null, () => findSystemGitWin32(process.env['ProgramFiles']))
|
||||||
|
.then(null, () => findSpecificGit('git'));
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function findGitPath(path?: string): Promise<IGit> {
|
||||||
|
try {
|
||||||
|
return await findSpecificGit(path || 'git');
|
||||||
|
}
|
||||||
|
catch (ex) {
|
||||||
|
try {
|
||||||
|
switch (process.platform) {
|
||||||
|
case 'darwin': return await findGitDarwin();
|
||||||
|
case 'win32': return await findGitWin32();
|
||||||
|
default: return Promise.reject('Unable to find git');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (ex) {
|
||||||
|
return Promise.reject(new Error('Unable to find git'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
121
src/git/gitUri.ts
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
'use strict';
|
||||||
|
import { Uri } from 'vscode';
|
||||||
|
import { DocumentSchemes } from '../constants';
|
||||||
|
import { GitService, IGitStatusFile } from '../gitService';
|
||||||
|
import * as path from 'path';
|
||||||
|
|
||||||
|
export class GitUri extends Uri {
|
||||||
|
|
||||||
|
offset: number;
|
||||||
|
repoPath?: string | undefined;
|
||||||
|
sha?: string | undefined;
|
||||||
|
|
||||||
|
constructor(uri?: Uri, commit?: IGitCommitInfo);
|
||||||
|
constructor(uri?: Uri, repoPath?: string);
|
||||||
|
constructor(uri?: Uri, commitOrRepoPath?: IGitCommitInfo | string);
|
||||||
|
constructor(uri?: Uri, commitOrRepoPath?: IGitCommitInfo | string) {
|
||||||
|
super();
|
||||||
|
if (!uri) return;
|
||||||
|
|
||||||
|
const base = this as any;
|
||||||
|
base._scheme = uri.scheme;
|
||||||
|
base._authority = uri.authority;
|
||||||
|
base._path = uri.path;
|
||||||
|
base._query = uri.query;
|
||||||
|
base._fragment = uri.fragment;
|
||||||
|
|
||||||
|
this.offset = 0;
|
||||||
|
if (uri.scheme === DocumentSchemes.GitLensGit) {
|
||||||
|
const data = GitService.fromGitContentUri(uri);
|
||||||
|
base._fsPath = path.resolve(data.repoPath, data.originalFileName || data.fileName);
|
||||||
|
|
||||||
|
this.offset = (data.decoration && data.decoration.split('\n').length) || 0;
|
||||||
|
if (!GitService.isUncommitted(data.sha)) {
|
||||||
|
this.sha = data.sha;
|
||||||
|
this.repoPath = data.repoPath;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (commitOrRepoPath) {
|
||||||
|
if (typeof commitOrRepoPath === 'string') {
|
||||||
|
this.repoPath = commitOrRepoPath;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const commit = commitOrRepoPath;
|
||||||
|
base._fsPath = path.resolve(commit.repoPath, commit.originalFileName || commit.fileName);
|
||||||
|
|
||||||
|
if (commit.repoPath !== undefined) {
|
||||||
|
this.repoPath = commit.repoPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (commit.sha !== undefined && !GitService.isUncommitted(commit.sha)) {
|
||||||
|
this.sha = commit.sha;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get shortSha() {
|
||||||
|
return this.sha && this.sha.substring(0, 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
fileUri() {
|
||||||
|
return Uri.file(this.sha ? this.path : this.fsPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
getFormattedPath(separator: string = ' \u00a0\u2022\u00a0 '): string {
|
||||||
|
let directory = path.dirname(this.fsPath);
|
||||||
|
if (this.repoPath) {
|
||||||
|
directory = path.relative(this.repoPath, directory);
|
||||||
|
}
|
||||||
|
directory = GitService.normalizePath(directory);
|
||||||
|
|
||||||
|
return (!directory || directory === '.')
|
||||||
|
? path.basename(this.fsPath)
|
||||||
|
: `${path.basename(this.fsPath)}${separator}${directory}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
getRelativePath(): string {
|
||||||
|
return GitService.normalizePath(path.relative(this.repoPath || '', this.fsPath));
|
||||||
|
}
|
||||||
|
|
||||||
|
static async fromUri(uri: Uri, git: GitService) {
|
||||||
|
if (uri instanceof GitUri) return uri;
|
||||||
|
|
||||||
|
if (!git.isTrackable(uri)) return new GitUri(uri, git.repoPath);
|
||||||
|
|
||||||
|
const gitUri = git.getGitUriForFile(uri.fsPath);
|
||||||
|
if (gitUri) return gitUri;
|
||||||
|
|
||||||
|
// If this is a git uri, assume it is showing the most recent commit
|
||||||
|
if (uri.scheme === DocumentSchemes.Git && uri.query === '~') {
|
||||||
|
const commit = await git.getLogCommit(undefined, uri.fsPath);
|
||||||
|
if (commit) return new GitUri(uri, commit);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new GitUri(uri, (await git.getRepoPathFromFile(uri.fsPath)) || git.repoPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromFileStatus(status: IGitStatusFile, repoPath: string, original?: boolean): GitUri;
|
||||||
|
static fromFileStatus(status: IGitStatusFile, commit: IGitCommitInfo, original?: boolean): GitUri;
|
||||||
|
static fromFileStatus(status: IGitStatusFile, repoPathOrCommit: string | IGitCommitInfo, original: boolean = false): GitUri {
|
||||||
|
const repoPath = typeof repoPathOrCommit === 'string' ? repoPathOrCommit : repoPathOrCommit.repoPath;
|
||||||
|
const uri = Uri.file(path.resolve(repoPath, original ? status.originalFileName || status.fileName : status.fileName));
|
||||||
|
return new GitUri(uri, repoPathOrCommit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IGitCommitInfo {
|
||||||
|
fileName: string;
|
||||||
|
repoPath: string;
|
||||||
|
sha?: string;
|
||||||
|
originalFileName?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IGitUriData {
|
||||||
|
sha: string;
|
||||||
|
fileName: string;
|
||||||
|
repoPath: string;
|
||||||
|
originalFileName?: string;
|
||||||
|
index?: number;
|
||||||
|
decoration?: string;
|
||||||
|
}
|
||||||
25
src/git/models/blame.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
'use strict';
|
||||||
|
import { GitCommit, IGitAuthor, IGitCommitLine } from './commit';
|
||||||
|
|
||||||
|
export interface IGitBlame {
|
||||||
|
repoPath: string;
|
||||||
|
authors: Map<string, IGitAuthor>;
|
||||||
|
commits: Map<string, GitCommit>;
|
||||||
|
lines: IGitCommitLine[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IGitBlameLine {
|
||||||
|
author: IGitAuthor;
|
||||||
|
commit: GitCommit;
|
||||||
|
line: IGitCommitLine;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IGitBlameLines extends IGitBlame {
|
||||||
|
allLines: IGitCommitLine[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IGitBlameCommitLines {
|
||||||
|
author: IGitAuthor;
|
||||||
|
commit: GitCommit;
|
||||||
|
lines: IGitCommitLine[];
|
||||||
|
}
|
||||||
29
src/git/models/branch.ts
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
export class GitBranch {
|
||||||
|
|
||||||
|
current: boolean;
|
||||||
|
name: string;
|
||||||
|
remote: boolean;
|
||||||
|
|
||||||
|
constructor(branch: string) {
|
||||||
|
branch = branch.trim();
|
||||||
|
|
||||||
|
if (branch.startsWith('* ')) {
|
||||||
|
branch = branch.substring(2);
|
||||||
|
this.current = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (branch.startsWith('remotes/')) {
|
||||||
|
branch = branch.substring(8);
|
||||||
|
this.remote = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const index = branch.indexOf(' ');
|
||||||
|
if (index !== -1) {
|
||||||
|
branch = branch.substring(0, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.name = branch;
|
||||||
|
}
|
||||||
|
}
|
||||||
100
src/git/models/commit.ts
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
'use strict';
|
||||||
|
import { Uri } from 'vscode';
|
||||||
|
import { Git } from '../git';
|
||||||
|
import * as path from 'path';
|
||||||
|
|
||||||
|
export interface IGitAuthor {
|
||||||
|
name: string;
|
||||||
|
lineCount: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IGitCommit {
|
||||||
|
type: GitCommitType;
|
||||||
|
repoPath: string;
|
||||||
|
sha: string;
|
||||||
|
fileName: string;
|
||||||
|
author?: string;
|
||||||
|
date: Date;
|
||||||
|
message: string;
|
||||||
|
lines: IGitCommitLine[];
|
||||||
|
originalFileName?: string;
|
||||||
|
previousSha?: string;
|
||||||
|
previousFileName?: string;
|
||||||
|
|
||||||
|
readonly isUncommitted: boolean;
|
||||||
|
previousUri: Uri;
|
||||||
|
uri: Uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IGitCommitLine {
|
||||||
|
sha: string;
|
||||||
|
previousSha?: string;
|
||||||
|
line: number;
|
||||||
|
originalLine: number;
|
||||||
|
code?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type GitCommitType = 'blame' | 'branch' | 'file' | 'stash';
|
||||||
|
|
||||||
|
export class GitCommit implements IGitCommit {
|
||||||
|
|
||||||
|
type: GitCommitType;
|
||||||
|
lines: IGitCommitLine[];
|
||||||
|
originalFileName?: string;
|
||||||
|
previousSha?: string;
|
||||||
|
previousFileName?: string;
|
||||||
|
workingFileName?: string;
|
||||||
|
private _isUncommitted: boolean | undefined;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
type: GitCommitType,
|
||||||
|
public repoPath: string,
|
||||||
|
public sha: string,
|
||||||
|
public fileName: string,
|
||||||
|
public author: string,
|
||||||
|
public date: Date,
|
||||||
|
public message: string,
|
||||||
|
lines?: IGitCommitLine[],
|
||||||
|
originalFileName?: string,
|
||||||
|
previousSha?: string,
|
||||||
|
previousFileName?: string
|
||||||
|
) {
|
||||||
|
this.type = type;
|
||||||
|
this.fileName = this.fileName && this.fileName.replace(/, ?$/, '');
|
||||||
|
|
||||||
|
this.lines = lines || [];
|
||||||
|
this.originalFileName = originalFileName;
|
||||||
|
this.previousSha = previousSha;
|
||||||
|
this.previousFileName = previousFileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
get shortSha() {
|
||||||
|
return this.sha.substring(0, 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
get isUncommitted(): boolean {
|
||||||
|
if (this._isUncommitted === undefined) {
|
||||||
|
this._isUncommitted = Git.isUncommitted(this.sha);
|
||||||
|
}
|
||||||
|
return this._isUncommitted;
|
||||||
|
}
|
||||||
|
|
||||||
|
get previousShortSha() {
|
||||||
|
return this.previousSha && this.previousSha.substring(0, 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
get previousUri(): Uri {
|
||||||
|
return this.previousFileName ? Uri.file(path.resolve(this.repoPath, this.previousFileName)) : this.uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
get uri(): Uri {
|
||||||
|
return Uri.file(path.resolve(this.repoPath, this.originalFileName || this.fileName));
|
||||||
|
}
|
||||||
|
|
||||||
|
getFormattedPath(separator: string = ' \u00a0\u2022\u00a0 '): string {
|
||||||
|
const directory = Git.normalizePath(path.dirname(this.fileName));
|
||||||
|
return (!directory || directory === '.')
|
||||||
|
? path.basename(this.fileName)
|
||||||
|
: `${path.basename(this.fileName)}${separator}${directory}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
18
src/git/models/diff.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
export interface IGitDiffChunk {
|
||||||
|
chunk?: string;
|
||||||
|
|
||||||
|
original: (string | undefined)[];
|
||||||
|
originalStart: number;
|
||||||
|
originalEnd: number;
|
||||||
|
|
||||||
|
changes: (string | undefined)[];
|
||||||
|
changesStart: number;
|
||||||
|
changesEnd: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IGitDiff {
|
||||||
|
diff?: string;
|
||||||
|
chunks: IGitDiffChunk[];
|
||||||
|
}
|
||||||
15
src/git/models/log.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
'use strict';
|
||||||
|
import { Range } from 'vscode';
|
||||||
|
import { IGitAuthor } from './commit';
|
||||||
|
import { GitLogCommit } from './logCommit';
|
||||||
|
|
||||||
|
export interface IGitLog {
|
||||||
|
repoPath: string;
|
||||||
|
authors: Map<string, IGitAuthor>;
|
||||||
|
commits: Map<string, GitLogCommit>;
|
||||||
|
|
||||||
|
sha: string | undefined;
|
||||||
|
maxCount: number | undefined;
|
||||||
|
range: Range;
|
||||||
|
truncated: boolean;
|
||||||
|
}
|
||||||
66
src/git/models/logCommit.ts
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
'use strict';
|
||||||
|
import { Uri } from 'vscode';
|
||||||
|
import { GitCommit, GitCommitType, IGitCommitLine } from './commit';
|
||||||
|
import { IGitStatusFile, GitStatusFileStatus } from './status';
|
||||||
|
import * as path from 'path';
|
||||||
|
|
||||||
|
export class GitLogCommit extends GitCommit {
|
||||||
|
|
||||||
|
fileNames: string;
|
||||||
|
fileStatuses: IGitStatusFile[];
|
||||||
|
nextSha?: string;
|
||||||
|
nextFileName?: string;
|
||||||
|
parentShas: string[];
|
||||||
|
status?: GitStatusFileStatus;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
type: GitCommitType,
|
||||||
|
repoPath: string,
|
||||||
|
sha: string,
|
||||||
|
fileName: string,
|
||||||
|
author: string,
|
||||||
|
date: Date,
|
||||||
|
message: string,
|
||||||
|
status?: GitStatusFileStatus,
|
||||||
|
fileStatuses?: IGitStatusFile[],
|
||||||
|
lines?: IGitCommitLine[],
|
||||||
|
originalFileName?: string,
|
||||||
|
previousSha?: string,
|
||||||
|
previousFileName?: string
|
||||||
|
) {
|
||||||
|
super(type, repoPath, sha, fileName, author, date, message, lines, originalFileName, previousSha, previousFileName);
|
||||||
|
|
||||||
|
this.fileNames = this.fileName;
|
||||||
|
|
||||||
|
if (fileStatuses) {
|
||||||
|
this.fileStatuses = fileStatuses.filter(_ => !!_.fileName);
|
||||||
|
|
||||||
|
const fileStatus = this.fileStatuses[0];
|
||||||
|
this.fileName = fileStatus.fileName;
|
||||||
|
this.status = fileStatus.status;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.fileStatuses = [{ status: status, fileName: fileName, originalFileName: originalFileName } as IGitStatusFile];
|
||||||
|
this.status = status;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get isMerge() {
|
||||||
|
return this.parentShas && this.parentShas.length > 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
get nextShortSha() {
|
||||||
|
return this.nextSha && this.nextSha.substring(0, 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
get nextUri(): Uri {
|
||||||
|
return this.nextFileName ? Uri.file(path.resolve(this.repoPath, this.nextFileName)) : this.uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
getDiffStatus(): string {
|
||||||
|
const added = this.fileStatuses.filter(_ => _.status === 'A' || _.status === '?').length;
|
||||||
|
const deleted = this.fileStatuses.filter(_ => _.status === 'D').length;
|
||||||
|
const changed = this.fileStatuses.filter(_ => _.status !== 'A' && _.status !== '?' && _.status !== 'D').length;
|
||||||
|
return `+${added} ~${changed} -${deleted}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
11
src/git/models/models.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
'use strict';
|
||||||
|
export * from './blame';
|
||||||
|
export * from './branch';
|
||||||
|
export * from './commit';
|
||||||
|
export * from './diff';
|
||||||
|
export * from './log';
|
||||||
|
export * from './logCommit';
|
||||||
|
export * from './remote';
|
||||||
|
export * from './stash';
|
||||||
|
export * from './stashCommit';
|
||||||
|
export * from './status';
|
||||||
27
src/git/models/remote.ts
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
'use strict';
|
||||||
|
import { RemoteProvider, RemoteProviderFactory } from '../remotes/factory';
|
||||||
|
|
||||||
|
export type GitRemoteType = 'fetch' | 'push';
|
||||||
|
|
||||||
|
export class GitRemote {
|
||||||
|
|
||||||
|
name: string;
|
||||||
|
url: string;
|
||||||
|
type: GitRemoteType;
|
||||||
|
|
||||||
|
provider?: RemoteProvider;
|
||||||
|
|
||||||
|
constructor(remote: string) {
|
||||||
|
remote = remote.trim();
|
||||||
|
|
||||||
|
const [name, info] = remote.split('\t');
|
||||||
|
this.name = name;
|
||||||
|
|
||||||
|
const [url, typeInfo] = info.split(' ');
|
||||||
|
this.url = url;
|
||||||
|
|
||||||
|
this.type = typeInfo.substring(1, typeInfo.length - 1) as GitRemoteType;
|
||||||
|
|
||||||
|
this.provider = RemoteProviderFactory.getRemoteProvider(this.url);
|
||||||
|
}
|
||||||
|
}
|
||||||
7
src/git/models/stash.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
'use strict';
|
||||||
|
import { GitStashCommit } from './stashCommit';
|
||||||
|
|
||||||
|
export interface IGitStash {
|
||||||
|
repoPath: string;
|
||||||
|
commits: Map<string, GitStashCommit>;
|
||||||
|
}
|
||||||
28
src/git/models/stashCommit.ts
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
'use strict';
|
||||||
|
import { IGitCommitLine } from './commit';
|
||||||
|
import { GitLogCommit } from './logCommit';
|
||||||
|
import { IGitStatusFile, GitStatusFileStatus } from './status';
|
||||||
|
|
||||||
|
export class GitStashCommit extends GitLogCommit {
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
public stashName: string,
|
||||||
|
repoPath: string,
|
||||||
|
sha: string,
|
||||||
|
fileName: string,
|
||||||
|
date: Date,
|
||||||
|
message: string,
|
||||||
|
status?: GitStatusFileStatus,
|
||||||
|
fileStatuses?: IGitStatusFile[],
|
||||||
|
lines?: IGitCommitLine[],
|
||||||
|
originalFileName?: string,
|
||||||
|
previousSha?: string,
|
||||||
|
previousFileName?: string
|
||||||
|
) {
|
||||||
|
super('stash', repoPath, sha, fileName, 'You', date, message, status, fileStatuses, lines, originalFileName, previousSha, previousFileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
get shortSha() {
|
||||||
|
return this.stashName;
|
||||||
|
}
|
||||||
|
}
|
||||||
57
src/git/models/status.ts
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
'use strict';
|
||||||
|
import { Uri } from 'vscode';
|
||||||
|
import * as path from 'path';
|
||||||
|
|
||||||
|
export interface IGitStatus {
|
||||||
|
|
||||||
|
branch: string;
|
||||||
|
repoPath: string;
|
||||||
|
sha: string;
|
||||||
|
state: {
|
||||||
|
ahead: number;
|
||||||
|
behind: number;
|
||||||
|
};
|
||||||
|
upstream?: string;
|
||||||
|
|
||||||
|
files: GitStatusFile[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export declare type GitStatusFileStatus = '!' | '?' | 'A' | 'C' | 'D' | 'M' | 'R' | 'U';
|
||||||
|
|
||||||
|
export interface IGitStatusFile {
|
||||||
|
status: GitStatusFileStatus;
|
||||||
|
fileName: string;
|
||||||
|
originalFileName?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class GitStatusFile implements IGitStatusFile {
|
||||||
|
|
||||||
|
originalFileName?: string;
|
||||||
|
|
||||||
|
constructor(public repoPath: string, public status: GitStatusFileStatus, public fileName: string, public staged: boolean, originalFileName?: string) {
|
||||||
|
this.originalFileName = originalFileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
getIcon() {
|
||||||
|
return getGitStatusIcon(this.status);
|
||||||
|
}
|
||||||
|
|
||||||
|
get Uri(): Uri {
|
||||||
|
return Uri.file(path.resolve(this.repoPath, this.fileName));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const statusOcticonsMap = {
|
||||||
|
'!': '$(diff-ignored)',
|
||||||
|
'?': '$(diff-added)',
|
||||||
|
A: '$(diff-added)',
|
||||||
|
C: '$(diff-added)',
|
||||||
|
D: '$(diff-removed)',
|
||||||
|
M: '$(diff-modified)',
|
||||||
|
R: '$(diff-renamed)',
|
||||||
|
U: '$(question)'
|
||||||
|
};
|
||||||
|
|
||||||
|
export function getGitStatusIcon(status: GitStatusFileStatus, missing: string = '\u00a0\u00a0\u00a0\u00a0'): string {
|
||||||
|
return statusOcticonsMap[status] || missing;
|
||||||
|
}
|
||||||
209
src/git/parsers/blameParser.ts
Normal file
@@ -0,0 +1,209 @@
|
|||||||
|
'use strict';
|
||||||
|
import { Git, GitCommit, IGitAuthor, IGitBlame, IGitCommitLine } from './../git';
|
||||||
|
import * as moment from 'moment';
|
||||||
|
import * as path from 'path';
|
||||||
|
|
||||||
|
interface IBlameEntry {
|
||||||
|
sha: string;
|
||||||
|
|
||||||
|
line: number;
|
||||||
|
originalLine: number;
|
||||||
|
lineCount: number;
|
||||||
|
|
||||||
|
author: string;
|
||||||
|
// authorEmail?: string;
|
||||||
|
authorDate?: string;
|
||||||
|
authorTimeZone?: string;
|
||||||
|
|
||||||
|
// committer?: string;
|
||||||
|
// committerEmail?: string;
|
||||||
|
// committerDate?: string;
|
||||||
|
// committerTimeZone?: string;
|
||||||
|
|
||||||
|
previousSha?: string;
|
||||||
|
previousFileName?: string;
|
||||||
|
|
||||||
|
fileName?: string;
|
||||||
|
|
||||||
|
summary?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class GitBlameParser {
|
||||||
|
|
||||||
|
private static _parseEntries(data: string): IBlameEntry[] | undefined {
|
||||||
|
if (!data) return undefined;
|
||||||
|
|
||||||
|
const lines = data.split('\n');
|
||||||
|
if (!lines.length) return undefined;
|
||||||
|
|
||||||
|
const entries: IBlameEntry[] = [];
|
||||||
|
|
||||||
|
let entry: IBlameEntry | undefined = undefined;
|
||||||
|
let position = -1;
|
||||||
|
while (++position < lines.length) {
|
||||||
|
let lineParts = lines[position].split(' ');
|
||||||
|
if (lineParts.length < 2) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entry === undefined) {
|
||||||
|
entry = {
|
||||||
|
sha: lineParts[0],
|
||||||
|
originalLine: parseInt(lineParts[1], 10) - 1,
|
||||||
|
line: parseInt(lineParts[2], 10) - 1,
|
||||||
|
lineCount: parseInt(lineParts[3], 10)
|
||||||
|
} as IBlameEntry;
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (lineParts[0]) {
|
||||||
|
case 'author':
|
||||||
|
entry.author = Git.isUncommitted(entry.sha)
|
||||||
|
? 'Uncommitted'
|
||||||
|
: lineParts.slice(1).join(' ').trim();
|
||||||
|
break;
|
||||||
|
|
||||||
|
// case 'author-mail':
|
||||||
|
// entry.authorEmail = lineParts[1].trim();
|
||||||
|
// break;
|
||||||
|
|
||||||
|
case 'author-time':
|
||||||
|
entry.authorDate = lineParts[1];
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'author-tz':
|
||||||
|
entry.authorTimeZone = lineParts[1];
|
||||||
|
break;
|
||||||
|
|
||||||
|
// case 'committer':
|
||||||
|
// entry.committer = lineParts.slice(1).join(' ').trim();
|
||||||
|
// break;
|
||||||
|
|
||||||
|
// case 'committer-mail':
|
||||||
|
// entry.committerEmail = lineParts[1].trim();
|
||||||
|
// break;
|
||||||
|
|
||||||
|
// case 'committer-time':
|
||||||
|
// entry.committerDate = lineParts[1];
|
||||||
|
// break;
|
||||||
|
|
||||||
|
// case 'committer-tz':
|
||||||
|
// entry.committerTimeZone = lineParts[1];
|
||||||
|
// break;
|
||||||
|
|
||||||
|
case 'summary':
|
||||||
|
entry.summary = lineParts.slice(1).join(' ').trim();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'previous':
|
||||||
|
entry.previousSha = lineParts[1];
|
||||||
|
entry.previousFileName = lineParts.slice(2).join(' ');
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'filename':
|
||||||
|
entry.fileName = lineParts.slice(1).join(' ');
|
||||||
|
|
||||||
|
entries.push(entry);
|
||||||
|
entry = undefined;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return entries;
|
||||||
|
}
|
||||||
|
|
||||||
|
static parse(data: string, repoPath: string | undefined, fileName: string): IGitBlame | undefined {
|
||||||
|
const entries = this._parseEntries(data);
|
||||||
|
if (!entries) return undefined;
|
||||||
|
|
||||||
|
const authors: Map<string, IGitAuthor> = new Map();
|
||||||
|
const commits: Map<string, GitCommit> = new Map();
|
||||||
|
const lines: Array<IGitCommitLine> = [];
|
||||||
|
|
||||||
|
let relativeFileName = repoPath && fileName;
|
||||||
|
|
||||||
|
for (let i = 0, len = entries.length; i < len; i++) {
|
||||||
|
const entry = entries[i];
|
||||||
|
|
||||||
|
if (i === 0 && repoPath === undefined) {
|
||||||
|
// Try to get the repoPath from the most recent commit
|
||||||
|
repoPath = Git.normalizePath(fileName.replace(fileName.startsWith('/') ? `/${entry.fileName}` : entry.fileName!, ''));
|
||||||
|
relativeFileName = Git.normalizePath(path.relative(repoPath, fileName));
|
||||||
|
}
|
||||||
|
|
||||||
|
let commit = commits.get(entry.sha);
|
||||||
|
if (commit === undefined) {
|
||||||
|
if (entry.author !== undefined) {
|
||||||
|
let author = authors.get(entry.author);
|
||||||
|
if (author === undefined) {
|
||||||
|
author = {
|
||||||
|
name: entry.author,
|
||||||
|
lineCount: 0
|
||||||
|
};
|
||||||
|
authors.set(entry.author, author);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
commit = new GitCommit('blame', repoPath!, entry.sha, relativeFileName!, entry.author, moment(`${entry.authorDate} ${entry.authorTimeZone}`, 'X +-HHmm').toDate(), entry.summary!);
|
||||||
|
|
||||||
|
if (relativeFileName !== entry.fileName) {
|
||||||
|
commit.originalFileName = entry.fileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entry.previousSha) {
|
||||||
|
commit.previousSha = entry.previousSha;
|
||||||
|
commit.previousFileName = entry.previousFileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
commits.set(entry.sha, commit);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let j = 0, len = entry.lineCount; j < len; j++) {
|
||||||
|
const line: IGitCommitLine = {
|
||||||
|
sha: entry.sha,
|
||||||
|
line: entry.line + j,
|
||||||
|
originalLine: entry.originalLine + j
|
||||||
|
};
|
||||||
|
|
||||||
|
if (commit.previousSha) {
|
||||||
|
line.previousSha = commit.previousSha;
|
||||||
|
}
|
||||||
|
|
||||||
|
commit.lines.push(line);
|
||||||
|
lines[line.line] = line;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
commits.forEach(c => {
|
||||||
|
if (c.author === undefined) return;
|
||||||
|
|
||||||
|
const author = authors.get(c.author);
|
||||||
|
if (author === undefined) return;
|
||||||
|
|
||||||
|
author.lineCount += c.lines.length;
|
||||||
|
});
|
||||||
|
|
||||||
|
const sortedAuthors: Map<string, IGitAuthor> = new Map();
|
||||||
|
// const values =
|
||||||
|
Array.from(authors.values())
|
||||||
|
.sort((a, b) => b.lineCount - a.lineCount)
|
||||||
|
.forEach(a => sortedAuthors.set(a.name, a));
|
||||||
|
|
||||||
|
// const sortedCommits: Map<string, IGitCommit> = new Map();
|
||||||
|
// Array.from(commits.values())
|
||||||
|
// .sort((a, b) => b.date.getTime() - a.date.getTime())
|
||||||
|
// .forEach(c => sortedCommits.set(c.sha, c));
|
||||||
|
|
||||||
|
return {
|
||||||
|
repoPath: repoPath,
|
||||||
|
authors: sortedAuthors,
|
||||||
|
// commits: sortedCommits,
|
||||||
|
commits: commits,
|
||||||
|
lines: lines
|
||||||
|
} as IGitBlame;
|
||||||
|
}
|
||||||
|
}
|
||||||
45
src/git/parsers/diffParser.ts
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
'use strict';
|
||||||
|
import { IGitDiff, IGitDiffChunk } from './../git';
|
||||||
|
|
||||||
|
const unifiedDiffRegex = /^@@ -([\d]+),([\d]+) [+]([\d]+),([\d]+) @@([\s\S]*?)(?=^@@)/gm;
|
||||||
|
|
||||||
|
export class GitDiffParser {
|
||||||
|
|
||||||
|
static parse(data: string, debug: boolean = false): IGitDiff | undefined {
|
||||||
|
if (!data) return undefined;
|
||||||
|
|
||||||
|
const chunks: IGitDiffChunk[] = [];
|
||||||
|
|
||||||
|
let match: RegExpExecArray | null = null;
|
||||||
|
do {
|
||||||
|
match = unifiedDiffRegex.exec(`${data}\n@@`);
|
||||||
|
if (match == null) break;
|
||||||
|
|
||||||
|
const originalStart = +match[1];
|
||||||
|
const changedStart = +match[3];
|
||||||
|
|
||||||
|
const chunk = match[5];
|
||||||
|
const lines = chunk.split('\n').slice(1);
|
||||||
|
const original = lines.filter(l => l[0] !== '+').map(l => (l[0] === '-') ? l.substring(1) : undefined);
|
||||||
|
const changed = lines.filter(l => l[0] !== '-').map(l => (l[0] === '+') ? l.substring(1) : undefined);
|
||||||
|
|
||||||
|
chunks.push({
|
||||||
|
chunk: debug ? chunk : undefined,
|
||||||
|
original: original,
|
||||||
|
originalStart: originalStart,
|
||||||
|
originalEnd: originalStart + +match[2],
|
||||||
|
changes: changed,
|
||||||
|
changesStart: changedStart,
|
||||||
|
changesEnd: changedStart + +match[4]
|
||||||
|
});
|
||||||
|
} while (match != null);
|
||||||
|
|
||||||
|
if (!chunks.length) return undefined;
|
||||||
|
|
||||||
|
const diff = {
|
||||||
|
diff: debug ? data : undefined,
|
||||||
|
chunks: chunks
|
||||||
|
} as IGitDiff;
|
||||||
|
return diff;
|
||||||
|
}
|
||||||
|
}
|
||||||
286
src/git/parsers/logParser.ts
Normal file
@@ -0,0 +1,286 @@
|
|||||||
|
'use strict';
|
||||||
|
import { Range } from 'vscode';
|
||||||
|
import { Git, GitStatusFileStatus, GitLogCommit, GitCommitType, IGitAuthor, IGitLog, IGitStatusFile } from './../git';
|
||||||
|
// import { Logger } from '../../logger';
|
||||||
|
import * as moment from 'moment';
|
||||||
|
import * as path from 'path';
|
||||||
|
|
||||||
|
interface ILogEntry {
|
||||||
|
sha: string;
|
||||||
|
|
||||||
|
author: string;
|
||||||
|
authorDate?: string;
|
||||||
|
|
||||||
|
// committer?: string;
|
||||||
|
// committerDate?: string;
|
||||||
|
|
||||||
|
parentShas?: string[];
|
||||||
|
|
||||||
|
fileName?: string;
|
||||||
|
originalFileName?: string;
|
||||||
|
fileStatuses?: IGitStatusFile[];
|
||||||
|
|
||||||
|
status?: GitStatusFileStatus;
|
||||||
|
|
||||||
|
summary?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const diffRegex = /diff --git a\/(.*) b\/(.*)/;
|
||||||
|
|
||||||
|
export class GitLogParser {
|
||||||
|
|
||||||
|
private static _parseEntries(data: string, type: GitCommitType, maxCount: number | undefined, reverse: boolean): ILogEntry[] | undefined {
|
||||||
|
if (!data) return undefined;
|
||||||
|
|
||||||
|
const lines = data.split('\n');
|
||||||
|
if (!lines.length) return undefined;
|
||||||
|
|
||||||
|
const entries: ILogEntry[] = [];
|
||||||
|
|
||||||
|
let entry: ILogEntry | undefined = undefined;
|
||||||
|
let position = -1;
|
||||||
|
while (++position < lines.length) {
|
||||||
|
// Since log --reverse doesn't properly honor a max count -- enforce it here
|
||||||
|
if (reverse && maxCount && (entries.length >= maxCount)) break;
|
||||||
|
|
||||||
|
let lineParts = lines[position].split(' ');
|
||||||
|
if (lineParts.length < 2) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entry === undefined) {
|
||||||
|
if (!Git.shaRegex.test(lineParts[0])) continue;
|
||||||
|
|
||||||
|
entry = {
|
||||||
|
sha: lineParts[0]
|
||||||
|
} as ILogEntry;
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (lineParts[0]) {
|
||||||
|
case 'author':
|
||||||
|
entry.author = Git.isUncommitted(entry.sha)
|
||||||
|
? 'Uncommitted'
|
||||||
|
: lineParts.slice(1).join(' ').trim();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'author-date':
|
||||||
|
entry.authorDate = `${lineParts[1]}T${lineParts[2]}${lineParts[3]}`;
|
||||||
|
break;
|
||||||
|
|
||||||
|
// case 'committer':
|
||||||
|
// entry.committer = lineParts.slice(1).join(' ').trim();
|
||||||
|
// break;
|
||||||
|
|
||||||
|
// case 'committer-date':
|
||||||
|
// entry.committerDate = lineParts.slice(1).join(' ').trim();
|
||||||
|
// break;
|
||||||
|
|
||||||
|
case 'parents':
|
||||||
|
entry.parentShas = lineParts.slice(1);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'summary':
|
||||||
|
entry.summary = lineParts.slice(1).join(' ').trim();
|
||||||
|
while (++position < lines.length) {
|
||||||
|
const next = lines[position];
|
||||||
|
if (!next) break;
|
||||||
|
if (next === 'filename ?') {
|
||||||
|
position--;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
entry.summary += `\n${lines[position]}`;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'filename':
|
||||||
|
if (type === 'branch') {
|
||||||
|
const nextLine = lines[position + 1];
|
||||||
|
// If the next line isn't blank, make sure it isn't starting a new commit
|
||||||
|
if (nextLine && Git.shaRegex.test(nextLine)) continue;
|
||||||
|
|
||||||
|
position++;
|
||||||
|
|
||||||
|
let diff = false;
|
||||||
|
while (++position < lines.length) {
|
||||||
|
const line = lines[position];
|
||||||
|
lineParts = line.split(' ');
|
||||||
|
|
||||||
|
if (Git.shaRegex.test(lineParts[0])) {
|
||||||
|
position--;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (diff) continue;
|
||||||
|
|
||||||
|
if (lineParts[0] === 'diff') {
|
||||||
|
diff = true;
|
||||||
|
const matches = diffRegex.exec(line);
|
||||||
|
if (matches != null) {
|
||||||
|
entry.fileName = matches[1];
|
||||||
|
const originalFileName = matches[2];
|
||||||
|
if (entry.fileName !== originalFileName) {
|
||||||
|
entry.originalFileName = originalFileName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entry.fileStatuses == null) {
|
||||||
|
entry.fileStatuses = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const status = {
|
||||||
|
status: line[0] as GitStatusFileStatus,
|
||||||
|
fileName: line.substring(1),
|
||||||
|
originalFileName: undefined
|
||||||
|
} as IGitStatusFile;
|
||||||
|
this._parseFileName(status);
|
||||||
|
|
||||||
|
entry.fileStatuses.push(status);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entry.fileStatuses) {
|
||||||
|
entry.fileName = entry.fileStatuses.filter(_ => !!_.fileName).map(_ => _.fileName).join(', ');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
position += 2;
|
||||||
|
const line = lines[position];
|
||||||
|
entry.status = line[0] as GitStatusFileStatus;
|
||||||
|
entry.fileName = line.substring(1);
|
||||||
|
this._parseFileName(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
entries.push(entry);
|
||||||
|
entry = undefined;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return entries;
|
||||||
|
}
|
||||||
|
|
||||||
|
static parse(data: string, type: GitCommitType, repoPath: string | undefined, fileName: string | undefined, sha: string | undefined, maxCount: number | undefined, reverse: boolean, range: Range | undefined): IGitLog | undefined {
|
||||||
|
const entries = this._parseEntries(data, type, maxCount, reverse);
|
||||||
|
if (!entries) return undefined;
|
||||||
|
|
||||||
|
const authors: Map<string, IGitAuthor> = new Map();
|
||||||
|
const commits: Map<string, GitLogCommit> = new Map();
|
||||||
|
|
||||||
|
let relativeFileName: string;
|
||||||
|
let recentCommit: GitLogCommit | undefined = undefined;
|
||||||
|
|
||||||
|
if (repoPath !== undefined) {
|
||||||
|
repoPath = Git.normalizePath(repoPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0, len = entries.length; i < len; i++) {
|
||||||
|
// Since log --reverse doesn't properly honor a max count -- enforce it here
|
||||||
|
if (reverse && maxCount && (i >= maxCount)) break;
|
||||||
|
|
||||||
|
const entry = entries[i];
|
||||||
|
|
||||||
|
if (i === 0 && repoPath === undefined && type === 'file' && fileName !== undefined) {
|
||||||
|
// Try to get the repoPath from the most recent commit
|
||||||
|
repoPath = Git.normalizePath(fileName.replace(fileName.startsWith('/') ? `/${entry.fileName}` : entry.fileName!, ''));
|
||||||
|
relativeFileName = Git.normalizePath(path.relative(repoPath, fileName));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
relativeFileName = entry.fileName!;
|
||||||
|
}
|
||||||
|
|
||||||
|
let commit = commits.get(entry.sha);
|
||||||
|
if (commit === undefined) {
|
||||||
|
if (entry.author !== undefined) {
|
||||||
|
let author = authors.get(entry.author);
|
||||||
|
if (author === undefined) {
|
||||||
|
author = {
|
||||||
|
name: entry.author,
|
||||||
|
lineCount: 0
|
||||||
|
};
|
||||||
|
authors.set(entry.author, author);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
commit = new GitLogCommit(type, repoPath!, entry.sha, relativeFileName, entry.author, moment(entry.authorDate).toDate(), entry.summary!, entry.status, entry.fileStatuses, undefined, entry.originalFileName);
|
||||||
|
commit.parentShas = entry.parentShas!;
|
||||||
|
|
||||||
|
if (relativeFileName !== entry.fileName) {
|
||||||
|
commit.originalFileName = entry.fileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
commits.set(entry.sha, commit);
|
||||||
|
}
|
||||||
|
// else {
|
||||||
|
// Logger.log(`merge commit? ${entry.sha}`);
|
||||||
|
// }
|
||||||
|
|
||||||
|
if (recentCommit !== undefined) {
|
||||||
|
recentCommit.previousSha = commit.sha;
|
||||||
|
|
||||||
|
// If the commit sha's match (merge commit), just forward it along
|
||||||
|
commit.nextSha = commit.sha !== recentCommit.sha ? recentCommit.sha : recentCommit.nextSha;
|
||||||
|
|
||||||
|
// Only add a filename if this is a file log
|
||||||
|
if (type === 'file') {
|
||||||
|
recentCommit.previousFileName = commit.originalFileName || commit.fileName;
|
||||||
|
commit.nextFileName = recentCommit.originalFileName || recentCommit.fileName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
recentCommit = commit;
|
||||||
|
}
|
||||||
|
|
||||||
|
commits.forEach(c => {
|
||||||
|
if (c.author === undefined) return;
|
||||||
|
|
||||||
|
const author = authors.get(c.author);
|
||||||
|
if (author === undefined) return;
|
||||||
|
|
||||||
|
author.lineCount += c.lines.length;
|
||||||
|
});
|
||||||
|
|
||||||
|
const sortedAuthors: Map<string, IGitAuthor> = new Map();
|
||||||
|
// const values =
|
||||||
|
Array.from(authors.values())
|
||||||
|
.sort((a, b) => b.lineCount - a.lineCount)
|
||||||
|
.forEach(a => sortedAuthors.set(a.name, a));
|
||||||
|
|
||||||
|
// const sortedCommits: Map<string, IGitCommit> = new Map();
|
||||||
|
// Array.from(commits.values())
|
||||||
|
// .sort((a, b) => b.date.getTime() - a.date.getTime())
|
||||||
|
// .forEach(c => sortedCommits.set(c.sha, c));
|
||||||
|
|
||||||
|
return {
|
||||||
|
repoPath: repoPath,
|
||||||
|
authors: sortedAuthors,
|
||||||
|
// commits: sortedCommits,
|
||||||
|
commits: commits,
|
||||||
|
sha: sha,
|
||||||
|
maxCount: maxCount,
|
||||||
|
range: range,
|
||||||
|
truncated: !!(maxCount && entries.length >= maxCount)
|
||||||
|
} as IGitLog;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static _parseFileName(entry: { fileName?: string, originalFileName?: string }) {
|
||||||
|
if (entry.fileName === undefined) return;
|
||||||
|
|
||||||
|
const index = entry.fileName.indexOf('\t') + 1;
|
||||||
|
if (index > 0) {
|
||||||
|
const next = entry.fileName.indexOf('\t', index) + 1;
|
||||||
|
if (next > 0) {
|
||||||
|
entry.originalFileName = entry.fileName.substring(index, next - 1);
|
||||||
|
entry.fileName = entry.fileName.substring(next);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
entry.fileName = entry.fileName.substring(index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
149
src/git/parsers/stashParser.ts
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
'use strict';
|
||||||
|
import { Git, GitStashCommit, GitStatusFileStatus, IGitStash, IGitStatusFile } from './../git';
|
||||||
|
// import { Logger } from '../../logger';
|
||||||
|
import * as moment from 'moment';
|
||||||
|
|
||||||
|
interface IStashEntry {
|
||||||
|
sha: string;
|
||||||
|
date?: string;
|
||||||
|
fileNames: string;
|
||||||
|
fileStatuses?: IGitStatusFile[];
|
||||||
|
summary: string;
|
||||||
|
stashName: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class GitStashParser {
|
||||||
|
|
||||||
|
private static _parseEntries(data: string): IStashEntry[] | undefined {
|
||||||
|
if (!data) return undefined;
|
||||||
|
|
||||||
|
const lines = data.split('\n');
|
||||||
|
if (!lines.length) return undefined;
|
||||||
|
|
||||||
|
const entries: IStashEntry[] = [];
|
||||||
|
|
||||||
|
let entry: IStashEntry | undefined = undefined;
|
||||||
|
let position = -1;
|
||||||
|
while (++position < lines.length) {
|
||||||
|
let lineParts = lines[position].split(' ');
|
||||||
|
if (lineParts.length < 2) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entry === undefined) {
|
||||||
|
if (!Git.shaRegex.test(lineParts[0])) continue;
|
||||||
|
|
||||||
|
entry = {
|
||||||
|
sha: lineParts[0]
|
||||||
|
} as IStashEntry;
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (lineParts[0]) {
|
||||||
|
case 'author-date':
|
||||||
|
entry.date = `${lineParts[1]}T${lineParts[2]}${lineParts[3]}`;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'summary':
|
||||||
|
entry.summary = lineParts.slice(1).join(' ').trim();
|
||||||
|
while (++position < lines.length) {
|
||||||
|
const next = lines[position];
|
||||||
|
if (!next) break;
|
||||||
|
if (next === 'filename ?') {
|
||||||
|
position--;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
entry.summary += `\n${lines[position]}`;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'reflog-selector':
|
||||||
|
entry.stashName = lineParts.slice(1).join(' ').trim();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'filename':
|
||||||
|
const nextLine = lines[position + 1];
|
||||||
|
// If the next line isn't blank, make sure it isn't starting a new commit
|
||||||
|
if (nextLine && Git.shaRegex.test(nextLine)) continue;
|
||||||
|
|
||||||
|
position++;
|
||||||
|
|
||||||
|
while (++position < lines.length) {
|
||||||
|
const line = lines[position];
|
||||||
|
lineParts = line.split(' ');
|
||||||
|
|
||||||
|
if (Git.shaRegex.test(lineParts[0])) {
|
||||||
|
position--;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entry.fileStatuses == null) {
|
||||||
|
entry.fileStatuses = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const status = {
|
||||||
|
status: line[0] as GitStatusFileStatus,
|
||||||
|
fileName: line.substring(1),
|
||||||
|
originalFileName: undefined
|
||||||
|
} as IGitStatusFile;
|
||||||
|
this._parseFileName(status);
|
||||||
|
|
||||||
|
entry.fileStatuses.push(status);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entry.fileStatuses) {
|
||||||
|
entry.fileNames = entry.fileStatuses.filter(_ => !!_.fileName).map(_ => _.fileName).join(', ');
|
||||||
|
}
|
||||||
|
|
||||||
|
entries.push(entry);
|
||||||
|
entry = undefined;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return entries;
|
||||||
|
}
|
||||||
|
|
||||||
|
static parse(data: string, repoPath: string): IGitStash | undefined {
|
||||||
|
const entries = this._parseEntries(data);
|
||||||
|
if (entries === undefined) return undefined;
|
||||||
|
|
||||||
|
const commits: Map<string, GitStashCommit> = new Map();
|
||||||
|
|
||||||
|
for (let i = 0, len = entries.length; i < len; i++) {
|
||||||
|
const entry = entries[i];
|
||||||
|
|
||||||
|
let commit = commits.get(entry.sha);
|
||||||
|
if (commit === undefined) {
|
||||||
|
commit = new GitStashCommit(entry.stashName, repoPath, entry.sha, entry.fileNames, moment(entry.date).toDate(), entry.summary, undefined, entry.fileStatuses) as GitStashCommit;
|
||||||
|
commits.set(entry.sha, commit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
repoPath: repoPath,
|
||||||
|
commits: commits
|
||||||
|
} as IGitStash;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static _parseFileName(entry: { fileName?: string, originalFileName?: string }) {
|
||||||
|
if (entry.fileName === undefined) return;
|
||||||
|
|
||||||
|
const index = entry.fileName.indexOf('\t') + 1;
|
||||||
|
if (index > 0) {
|
||||||
|
const next = entry.fileName.indexOf('\t', index) + 1;
|
||||||
|
if (next > 0) {
|
||||||
|
entry.originalFileName = entry.fileName.substring(index, next - 1);
|
||||||
|
entry.fileName = entry.fileName.substring(next);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
entry.fileName = entry.fileName.substring(index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
137
src/git/parsers/statusParser.ts
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
'use strict';
|
||||||
|
import { Git, GitStatusFileStatus, GitStatusFile, IGitStatus } from './../git';
|
||||||
|
|
||||||
|
interface IFileStatusEntry {
|
||||||
|
staged: boolean;
|
||||||
|
status: GitStatusFileStatus;
|
||||||
|
fileName: string;
|
||||||
|
originalFileName: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const aheadStatusV1Regex = /(?:ahead ([0-9]+))/;
|
||||||
|
const behindStatusV1Regex = /(?:behind ([0-9]+))/;
|
||||||
|
|
||||||
|
export class GitStatusParser {
|
||||||
|
|
||||||
|
static parse(data: string, repoPath: string, porcelainVersion: number): IGitStatus | undefined {
|
||||||
|
if (!data) return undefined;
|
||||||
|
|
||||||
|
const lines = data.split('\n').filter(_ => !!_);
|
||||||
|
if (!lines.length) return undefined;
|
||||||
|
|
||||||
|
const status = {
|
||||||
|
branch: '',
|
||||||
|
repoPath: Git.normalizePath(repoPath),
|
||||||
|
sha: '',
|
||||||
|
state: {
|
||||||
|
ahead: 0,
|
||||||
|
behind: 0
|
||||||
|
},
|
||||||
|
files: []
|
||||||
|
};
|
||||||
|
|
||||||
|
if (porcelainVersion >= 2) {
|
||||||
|
this._parseV2(lines, repoPath, status);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this._parseV1(lines, repoPath, status);
|
||||||
|
}
|
||||||
|
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static _parseV1(lines: string[], repoPath: string, status: IGitStatus) {
|
||||||
|
let position = -1;
|
||||||
|
while (++position < lines.length) {
|
||||||
|
const line = lines[position];
|
||||||
|
// Header
|
||||||
|
if (line.startsWith('##')) {
|
||||||
|
const lineParts = line.split(' ');
|
||||||
|
[status.branch, status.upstream] = lineParts[1].split('...');
|
||||||
|
if (lineParts.length > 2) {
|
||||||
|
const upstreamStatus = lineParts.slice(2).join(' ');
|
||||||
|
|
||||||
|
const aheadStatus = aheadStatusV1Regex.exec(upstreamStatus);
|
||||||
|
status.state.ahead = aheadStatus == null ? 0 : +aheadStatus[1] || 0;
|
||||||
|
|
||||||
|
const behindStatus = behindStatusV1Regex.exec(upstreamStatus);
|
||||||
|
status.state.behind = behindStatus == null ? 0 : +behindStatus[1] || 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
let entry: IFileStatusEntry;
|
||||||
|
const rawStatus = line.substring(0, 2);
|
||||||
|
let fileName = line.substring(3);
|
||||||
|
if (rawStatus[0] === 'R') {
|
||||||
|
const [file1, file2] = fileName.replace(/\"/g, '').split('->');
|
||||||
|
entry = this._parseFileEntry(rawStatus, file2.trim(), file1.trim());
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
entry = this._parseFileEntry(rawStatus, fileName);
|
||||||
|
}
|
||||||
|
status.files.push(new GitStatusFile(repoPath, entry.status, entry.fileName, entry.staged, entry.originalFileName));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static _parseV2(lines: string[], repoPath: string, status: IGitStatus) {
|
||||||
|
let position = -1;
|
||||||
|
while (++position < lines.length) {
|
||||||
|
const line = lines[position];
|
||||||
|
// Headers
|
||||||
|
if (line.startsWith('#')) {
|
||||||
|
const lineParts = line.split(' ');
|
||||||
|
switch (lineParts[1]) {
|
||||||
|
case 'branch.oid':
|
||||||
|
status.sha = lineParts[2];
|
||||||
|
break;
|
||||||
|
case 'branch.head':
|
||||||
|
status.branch = lineParts[2];
|
||||||
|
break;
|
||||||
|
case 'branch.upstream':
|
||||||
|
status.upstream = lineParts[2];
|
||||||
|
break;
|
||||||
|
case 'branch.ab':
|
||||||
|
status.state.ahead = +lineParts[2].substring(1);
|
||||||
|
status.state.behind = +lineParts[3].substring(1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
let lineParts = line.split(' ');
|
||||||
|
let entry: IFileStatusEntry | undefined = undefined;
|
||||||
|
switch (lineParts[0][0]) {
|
||||||
|
case '1': // normal
|
||||||
|
entry = this._parseFileEntry(lineParts[1], lineParts.slice(8).join(' '));
|
||||||
|
break;
|
||||||
|
case '2': // rename
|
||||||
|
const file = lineParts.slice(9).join(' ').split('\t');
|
||||||
|
entry = this._parseFileEntry(lineParts[1], file[0], file[1]);
|
||||||
|
break;
|
||||||
|
case 'u': // unmerged
|
||||||
|
entry = this._parseFileEntry(lineParts[1], lineParts.slice(10).join(' '));
|
||||||
|
break;
|
||||||
|
case '?': // untracked
|
||||||
|
entry = this._parseFileEntry(' ?', lineParts.slice(1).join(' '));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entry !== undefined) {
|
||||||
|
status.files.push(new GitStatusFile(repoPath, entry.status, entry.fileName, entry.staged, entry.originalFileName));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static _parseFileEntry(rawStatus: string, fileName: string, originalFileName?: string): IFileStatusEntry {
|
||||||
|
const indexStatus = rawStatus[0] !== '.' ? rawStatus[0].trim() : undefined;
|
||||||
|
const workTreeStatus = rawStatus[1] !== '.' ? rawStatus[1].trim() : undefined;
|
||||||
|
|
||||||
|
return {
|
||||||
|
status: (indexStatus || workTreeStatus || '?') as GitStatusFileStatus,
|
||||||
|
fileName: fileName,
|
||||||
|
originalFileName: originalFileName,
|
||||||
|
staged: !!indexStatus
|
||||||
|
} as IFileStatusEntry;
|
||||||
|
}
|
||||||
|
}
|
||||||
38
src/git/remotes/bitbucket.ts
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
'use strict';
|
||||||
|
import { Range } from 'vscode';
|
||||||
|
import { RemoteProvider } from './provider';
|
||||||
|
|
||||||
|
export class BitbucketService extends RemoteProvider {
|
||||||
|
|
||||||
|
constructor(public domain: string, public path: string) {
|
||||||
|
super(domain, path);
|
||||||
|
}
|
||||||
|
|
||||||
|
get name() {
|
||||||
|
return 'Bitbucket';
|
||||||
|
}
|
||||||
|
|
||||||
|
protected getUrlForBranch(branch: string): string {
|
||||||
|
return `${this.baseUrl}/commits/branch/${branch}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected getUrlForCommit(sha: string): string {
|
||||||
|
return `${this.baseUrl}/commits/${sha}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected getUrlForFile(fileName: string, branch?: string, sha?: string, range?: Range): string {
|
||||||
|
let line: string = '';
|
||||||
|
if (range) {
|
||||||
|
if (range.start.line === range.end.line) {
|
||||||
|
line = `#${fileName}-${range.start.line}`;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
line = `#${fileName}-${range.start.line}:${range.end.line}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sha) return `${this.baseUrl}/src/${sha}/${fileName}${line}`;
|
||||||
|
if (branch) return `${this.baseUrl}/src/${branch}/${fileName}${line}`;
|
||||||
|
return `${this.baseUrl}?path=${fileName}${line}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
44
src/git/remotes/factory.ts
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
'use strict';
|
||||||
|
import { RemoteProvider } from './provider';
|
||||||
|
import { BitbucketService } from './bitbucket';
|
||||||
|
import { GitHubService } from './github';
|
||||||
|
import { GitLabService } from './gitlab';
|
||||||
|
import { VisualStudioService } from './visualStudio';
|
||||||
|
import { Logger } from '../../logger';
|
||||||
|
|
||||||
|
export { RemoteProvider };
|
||||||
|
|
||||||
|
const providerMap = new Map<string, (domain: string, path: string) => RemoteProvider>([
|
||||||
|
['bitbucket.org', (domain: string, path: string) => new BitbucketService(domain, path)],
|
||||||
|
['github.com', (domain: string, path: string) => new GitHubService(domain, path)],
|
||||||
|
['gitlab.com', (domain: string, path: string) => new GitLabService(domain, path)],
|
||||||
|
['visualstudio.com', (domain: string, path: string) => new VisualStudioService(domain, path)]
|
||||||
|
]);
|
||||||
|
|
||||||
|
const UrlRegex = /^(?:git:\/\/(.*?)\/|https:\/\/(.*?)\/|http:\/\/(.*?)\/|git@(.*):\/\/|ssh:\/\/git@(.*?)\/)(.*)$/;
|
||||||
|
|
||||||
|
export class RemoteProviderFactory {
|
||||||
|
|
||||||
|
static getRemoteProvider(url: string): RemoteProvider | undefined {
|
||||||
|
try {
|
||||||
|
const match = UrlRegex.exec(url);
|
||||||
|
if (match == null) return undefined;
|
||||||
|
|
||||||
|
const domain = match[1] || match[2] || match[3] || match[4] || match[5];
|
||||||
|
const path = match[6].replace(/\.git/, '');
|
||||||
|
|
||||||
|
const key = domain.toLowerCase().endsWith('visualstudio.com')
|
||||||
|
? 'visualstudio.com'
|
||||||
|
: domain;
|
||||||
|
|
||||||
|
const creator = providerMap.get(key.toLowerCase());
|
||||||
|
if (!creator) return undefined;
|
||||||
|
|
||||||
|
return creator(domain, path);
|
||||||
|
}
|
||||||
|
catch (ex) {
|
||||||
|
Logger.error(ex, 'RemoteProviderFactory');
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
38
src/git/remotes/github.ts
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
'use strict';
|
||||||
|
import { Range } from 'vscode';
|
||||||
|
import { RemoteProvider } from './provider';
|
||||||
|
|
||||||
|
export class GitHubService extends RemoteProvider {
|
||||||
|
|
||||||
|
constructor(public domain: string, public path: string) {
|
||||||
|
super(domain, path);
|
||||||
|
}
|
||||||
|
|
||||||
|
get name() {
|
||||||
|
return 'GitHub';
|
||||||
|
}
|
||||||
|
|
||||||
|
protected getUrlForBranch(branch: string): string {
|
||||||
|
return `${this.baseUrl}/commits/${branch}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected getUrlForCommit(sha: string): string {
|
||||||
|
return `${this.baseUrl}/commit/${sha}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected getUrlForFile(fileName: string, branch?: string, sha?: string, range?: Range): string {
|
||||||
|
let line: string = '';
|
||||||
|
if (range) {
|
||||||
|
if (range.start.line === range.end.line) {
|
||||||
|
line = `#L${range.start.line}`;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
line = `#L${range.start.line}-L${range.end.line}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sha) return `${this.baseUrl}/blob/${sha}/${fileName}${line}`;
|
||||||
|
if (branch) return `${this.baseUrl}/blob/${branch}/${fileName}${line}`;
|
||||||
|
return `${this.baseUrl}?path=${fileName}${line}`;
|
||||||
|
}
|
||||||
|
}
|
||||||