5 Commits

Author SHA1 Message Date
Eric Amodio
4694fbc1ae Preps v5.3.0 2017-09-26 22:15:45 -04:00
Eric Amodio
b56d101f76 Fixes #153 - untracked folder files don't show properly 2017-09-26 21:52:00 -04:00
Eric Amodio
ce9394297d Adds new file layouts to the custom view 2017-09-26 18:19:53 -04:00
Eric Amodio
4d18bf708d Adds new ${directory} token
Changes ${path} token to be the full path
Adds relative formatting support
2017-09-26 18:19:33 -04:00
Eric Amodio
c44e4c6968 Fixes Objects.values types 2017-09-26 18:13:00 -04:00
19 changed files with 1053 additions and 579 deletions

View File

@@ -4,7 +4,22 @@ 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/). The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/).
## [Unreleased] ## [5.3.0] - 2017-09-26
### Added
- Adds new file layouts to the `GitLens` custom view
- `auto` - automatically switches between displaying files as a `tree` or `list` based on the `gitlens.gitExplorer.files.threshold` setting and the number of files at each nesting level
- `list` - displays files as a list
- `tree` - displays files as a tree
- Adds `gitlens.gitExplorer.files.layout` setting to specify how the `GitLens` custom view will display files
- Adds `gitlens.gitExplorer.files.compact` setting to specify whether or not to compact (flatten) unnecessary file nesting in the `GitLens` custom view
- Adds `gitlens.gitExplorer.files.threshold` setting to specify when to switch between displaying files as a `tree` or `list` based on the number of files in a nesting level in the `GitLens` custom view
- Adds `${directory}` token to the file formatting settings
### Changed
- Changes `${path}` token to be the full file path in the file formatting settings
### Fixed
- Fixes [#153](https://github.com/eamodio/vscode-gitlens/issues/153) - New folders treated as files in "Changed Files" section of the sidebar component
## [5.2.0] - 2017-09-23 ## [5.2.0] - 2017-09-23
### Added ### Added

View File

@@ -288,7 +288,7 @@ GitLens is highly customizable and provides many configuration settings to allow
|Name | Description |Name | Description
|-----|------------ |-----|------------
|`gitlens.defaultDateFormat`|Specifies how all absolute dates will be formatted by default\nSee https://momentjs.com/docs/#/displaying/format/ for valid formats |`gitlens.defaultDateFormat`|Specifies how all absolute dates will be formatted by default<br />See https://momentjs.com/docs/#/displaying/format/ for valid formats
|`gitlens.insiders`|Opts into the insiders channel -- provides access to upcoming features |`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.outputLevel`|Specifies how much (if any) output will be sent to the GitLens output channel
@@ -356,13 +356,16 @@ GitLens is highly customizable and provides many configuration settings to allow
|-----|------------ |-----|------------
|`gitlens.gitExplorer.enabled`|Specifies whether or not to show the `GitLens` custom view" |`gitlens.gitExplorer.enabled`|Specifies whether or not to show the `GitLens` custom view"
|`gitlens.gitExplorer.view`|Specifies the starting view (mode) of the `GitLens` custom view<br /> `auto` - shows the last selected view, defaults to `repository`<br />`history` - shows the commit history of the active file<br />`repository` - shows a repository explorer" |`gitlens.gitExplorer.view`|Specifies the starting view (mode) of the `GitLens` custom view<br /> `auto` - shows the last selected view, defaults to `repository`<br />`history` - shows the commit history of the active file<br />`repository` - shows a repository explorer"
|`gitlens.gitExplorer.files.layout`|Specifies how the `GitLens` custom view will display files<br /> `auto` - automatically switches between displaying files as a `tree` or `list` based on the `gitlens.gitExplorer.files.threshold` setting and the number of files at each nesting level<br /> `list` - displays files as a list<br /> `tree` - displays files as a tree
|`gitlens.gitExplorer.files.compact`|Specifies whether or not to compact (flatten) unnecessary file nesting in the `GitLens` custom view<br />Only applies when displaying files as a `tree` or `auto`
|`gitlens.gitExplorer.files.threshold`|Specifies when to switch between displaying files as a `tree` or `list` based on the number of files in a nesting level in the `GitLens` custom view<br />Only applies when displaying files as `auto`
|`gitlens.gitExplorer.includeWorkingTree`|Specifies whether or not to include working tree files inside the `Repository Status` node of the `GitLens` custom view |`gitlens.gitExplorer.includeWorkingTree`|Specifies whether or not to include working tree files inside the `Repository Status` node of the `GitLens` custom view
|`gitlens.gitExplorer.showTrackingBranch`|Specifies whether or not to show the tracking branch when displaying local branches in the `GitLens` custom view" |`gitlens.gitExplorer.showTrackingBranch`|Specifies whether or not to show the tracking branch when displaying local branches in the `GitLens` custom view"
|`gitlens.gitExplorer.commitFormat`|Specifies the format of committed changes in the `GitLens` custom view<br />Available tokens<br /> ${id} - commit id<br /> ${author} - commit author<br /> ${message} - commit message<br /> ${ago} - relative commit date (e.g. 1 day ago)<br /> ${date} - formatted commit date (format specified by `gitlens.statusBar.dateFormat`)<br /> ${authorAgo} - commit author, relative commit date<br />See https://github.com/eamodio/vscode-gitlens/wiki/Advanced-Formatting for advanced formatting |`gitlens.gitExplorer.commitFormat`|Specifies the format of committed changes in the `GitLens` custom view<br />Available tokens<br /> ${id} - commit id<br /> ${author} - commit author<br /> ${message} - commit message<br /> ${ago} - relative commit date (e.g. 1 day ago)<br /> ${date} - formatted commit date (format specified by `gitlens.statusBar.dateFormat`)<br /> ${authorAgo} - commit author, relative commit date<br />See https://github.com/eamodio/vscode-gitlens/wiki/Advanced-Formatting for advanced formatting
|`gitlens.gitExplorer.commitFileFormat`|Specifies the format of a committed file in the `GitLens` custom view<br />Available tokens<br /> ${file} - file name<br /> ${filePath} - file name and path<br /> ${path} - file path |`gitlens.gitExplorer.commitFileFormat`|Specifies the format of a committed file in the `GitLens` custom view<br />Available tokens<br /> ${directory} - directory name<br /> ${file} - file name<br /> ${filePath} - formatted file name and path<br /> ${path} - full file path
|`gitlens.gitExplorer.stashFormat`|Specifies the format of stashed changes in the `GitLens` custom view<br />Available tokens<br /> ${id} - commit id<br /> ${author} - commit author<br /> ${message} - commit message<br /> ${ago} - relative commit date (e.g. 1 day ago)<br /> ${date} - formatted commit date (format specified by `gitlens.statusBar.dateFormat`)<br /> ${authorAgo} - commit author, relative commit date<br />See https://github.com/eamodio/vscode-gitlens/wiki/Advanced-Formatting for advanced formatting |`gitlens.gitExplorer.stashFormat`|Specifies the format of stashed changes in the `GitLens` custom view<br />Available tokens<br /> ${id} - commit id<br /> ${author} - commit author<br /> ${message} - commit message<br /> ${ago} - relative commit date (e.g. 1 day ago)<br /> ${date} - formatted commit date (format specified by `gitlens.statusBar.dateFormat`)<br /> ${authorAgo} - commit author, relative commit date<br />See https://github.com/eamodio/vscode-gitlens/wiki/Advanced-Formatting for advanced formatting
|`gitlens.gitExplorer.stashFileFormat`|Specifies the format of a stashed file in the `GitLens` custom view<br />Available tokens<br /> ${file} - file name<br /> ${filePath} - file name and path<br /> ${path} - file path |`gitlens.gitExplorer.stashFileFormat`|Specifies the format of a stashed file in the `GitLens` custom view<br />Available tokens<br /> ${directory} - directory name<br /> ${file} - file name<br /> ${filePath} - formatted file name and path<br /> ${path} - full file path
|`gitlens.gitExplorer.statusFileFormat`|Specifies the format of the status of a working or committed file in the `GitLens` custom view<br />Available tokens<br /> ${file} - file name<br /> ${filePath} - file name and path<br /> ${path} - file path<br />${working} - optional indicator if the file is uncommitted |`gitlens.gitExplorer.statusFileFormat`|Specifies the format of the status of a working or committed file in the `GitLens` custom view<br />Available tokens<br /> ${directory} - directory name<br /> ${file} - file name<br /> ${filePath} - formatted file name and path<br /> ${path} - full file path<br />${working} - optional indicator if the file is uncommitted
### Custom Remotes Settings ### Custom Remotes Settings

277
package-lock.json generated
View File

@@ -1,6 +1,6 @@
{ {
"name": "gitlens", "name": "gitlens",
"version": "5.2.0", "version": "5.3.0",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {
@@ -16,7 +16,7 @@
"integrity": "sha1-qjuL2ivlErGuCgV7lC6GnDcKVWk=", "integrity": "sha1-qjuL2ivlErGuCgV7lC6GnDcKVWk=",
"dev": true, "dev": true,
"requires": { "requires": {
"@types/node": "8.0.28" "@types/node": "8.0.31"
} }
}, },
"@types/mocha": { "@types/mocha": {
@@ -26,9 +26,9 @@
"dev": true "dev": true
}, },
"@types/node": { "@types/node": {
"version": "8.0.28", "version": "8.0.31",
"resolved": "https://registry.npmjs.org/@types/node/-/node-8.0.28.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-8.0.31.tgz",
"integrity": "sha512-HupkFXEv3O3KSzcr3Ylfajg0kaerBg1DyaZzRBBQfrU3NN1mTBRE7sCveqHwXLS5Yrjvww8qFzkzYQQakG9FuQ==", "integrity": "sha512-R+LdMJHJQwRd/Ca0Nr5KnwbSWHxTD3DWz4ivqoPeNH+YPcuirMWK+Ti9Mx32jOecmPhHOCd+6CefU5e1eVq2Ew==",
"dev": true "dev": true
}, },
"@types/tmp": { "@types/tmp": {
@@ -38,15 +38,23 @@
"dev": true "dev": true
}, },
"ajv": { "ajv": {
"version": "4.11.8", "version": "5.2.3",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz", "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.2.3.tgz",
"integrity": "sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY=", "integrity": "sha1-wG9Zh3jETGsWGrr+NGa4GtGBTtI=",
"dev": true, "dev": true,
"requires": { "requires": {
"co": "4.6.0", "co": "4.6.0",
"fast-deep-equal": "1.0.0",
"json-schema-traverse": "0.3.1",
"json-stable-stringify": "1.0.1" "json-stable-stringify": "1.0.1"
} }
}, },
"ansi-regex": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
"integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=",
"dev": true
},
"ansi-styles": { "ansi-styles": {
"version": "2.2.1", "version": "2.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
@@ -244,21 +252,6 @@
"supports-color": "2.0.0" "supports-color": "2.0.0"
}, },
"dependencies": { "dependencies": {
"ansi-regex": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
"integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=",
"dev": true
},
"strip-ansi": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
"integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
"dev": true,
"requires": {
"ansi-regex": "2.1.1"
}
},
"supports-color": { "supports-color": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
@@ -380,15 +373,15 @@
} }
}, },
"dateformat": { "dateformat": {
"version": "2.0.0", "version": "2.2.0",
"resolved": "https://registry.npmjs.org/dateformat/-/dateformat-2.0.0.tgz", "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-2.2.0.tgz",
"integrity": "sha1-J0Pjq7XD/CRi5SfcpEXgTp9N7hc=", "integrity": "sha1-QGXiATz5+5Ft39gu+1Bq1MZ2kGI=",
"dev": true "dev": true
}, },
"debug": { "debug": {
"version": "2.6.8", "version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.8.tgz", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha1-5zFTHKLt4n0YgiJCfaF4IdaP9Pw=", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
"requires": { "requires": {
"ms": "2.0.0" "ms": "2.0.0"
} }
@@ -592,6 +585,12 @@
"time-stamp": "1.1.0" "time-stamp": "1.1.0"
} }
}, },
"fast-deep-equal": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz",
"integrity": "sha1-liVqO8l1WV6zbYLpkp0GDYk0Of8=",
"dev": true
},
"fd-slicer": { "fd-slicer": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.0.1.tgz", "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.0.1.tgz",
@@ -679,7 +678,7 @@
"graceful-fs": "4.1.11", "graceful-fs": "4.1.11",
"inherits": "2.0.3", "inherits": "2.0.3",
"mkdirp": "0.5.1", "mkdirp": "0.5.1",
"rimraf": "2.6.1" "rimraf": "2.6.2"
} }
}, },
"generate-function": { "generate-function": {
@@ -998,7 +997,7 @@
"oauth-sign": "0.8.2", "oauth-sign": "0.8.2",
"qs": "6.3.2", "qs": "6.3.2",
"stringstream": "0.0.5", "stringstream": "0.0.5",
"tough-cookie": "2.3.2", "tough-cookie": "2.3.3",
"tunnel-agent": "0.4.3", "tunnel-agent": "0.4.3",
"uuid": "3.1.0" "uuid": "3.1.0"
} }
@@ -1081,7 +1080,7 @@
"array-uniq": "1.0.3", "array-uniq": "1.0.3",
"beeper": "1.1.1", "beeper": "1.1.1",
"chalk": "1.1.3", "chalk": "1.1.3",
"dateformat": "2.0.0", "dateformat": "2.2.0",
"fancy-log": "1.3.0", "fancy-log": "1.3.0",
"gulplog": "1.0.0", "gulplog": "1.0.0",
"has-gulplog": "0.1.0", "has-gulplog": "0.1.0",
@@ -1182,9 +1181,9 @@
} }
}, },
"har-schema": { "har-schema": {
"version": "1.0.5", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/har-schema/-/har-schema-1.0.5.tgz", "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz",
"integrity": "sha1-0mMTX0MwfALGAq/I/pWXDAFRNp4=", "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=",
"dev": true "dev": true
}, },
"har-validator": { "har-validator": {
@@ -1206,14 +1205,6 @@
"dev": true, "dev": true,
"requires": { "requires": {
"ansi-regex": "2.1.1" "ansi-regex": "2.1.1"
},
"dependencies": {
"ansi-regex": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
"integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=",
"dev": true
}
} }
}, },
"has-flag": { "has-flag": {
@@ -1449,6 +1440,12 @@
"integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=",
"dev": true "dev": true
}, },
"json-schema-traverse": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz",
"integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=",
"dev": true
},
"json-stable-stringify": { "json-stable-stringify": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz", "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz",
@@ -1784,9 +1781,9 @@
} }
}, },
"mocha": { "mocha": {
"version": "3.5.2", "version": "3.5.3",
"resolved": "https://registry.npmjs.org/mocha/-/mocha-3.5.2.tgz", "resolved": "https://registry.npmjs.org/mocha/-/mocha-3.5.3.tgz",
"integrity": "sha512-iH5zl7afRZl1GvD0pnrRlazgc9Z/o34pXWmTFi8xNIMFKXgNL1SoBTDDb9sJfbV/sJV/j8X+0gvwY1QS1He7Nw==", "integrity": "sha512-/6na001MJWEtYxHOV1WLfsmR4YIynkUEhBwzsb+fk2qmQ3iqsi258l/Q2MWHJMImAcNpZ8DEdYAK72NHoIQ9Eg==",
"dev": true, "dev": true,
"requires": { "requires": {
"browser-stdout": "1.3.0", "browser-stdout": "1.3.0",
@@ -1801,6 +1798,17 @@
"lodash.create": "3.1.1", "lodash.create": "3.1.1",
"mkdirp": "0.5.1", "mkdirp": "0.5.1",
"supports-color": "3.1.2" "supports-color": "3.1.2"
},
"dependencies": {
"debug": {
"version": "2.6.8",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.8.tgz",
"integrity": "sha1-5zFTHKLt4n0YgiJCfaF4IdaP9Pw=",
"dev": true,
"requires": {
"ms": "2.0.0"
}
}
} }
}, },
"moment": { "moment": {
@@ -1961,9 +1969,9 @@
"dev": true "dev": true
}, },
"performance-now": { "performance-now": {
"version": "0.2.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/performance-now/-/performance-now-0.2.0.tgz", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
"integrity": "sha1-M+8wxcd9TqIcWlOGnZG1bY8lVeU=", "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=",
"dev": true "dev": true
}, },
"pinkie": { "pinkie": {
@@ -2110,57 +2118,147 @@
"dev": true "dev": true
}, },
"request": { "request": {
"version": "2.81.0", "version": "2.82.0",
"resolved": "https://registry.npmjs.org/request/-/request-2.81.0.tgz", "resolved": "https://registry.npmjs.org/request/-/request-2.82.0.tgz",
"integrity": "sha1-xpKJRqDgbF+Nb4qTM0af/aRimKA=", "integrity": "sha512-/QWqfmyTfQ4OYs6EhB1h2wQsX9ZxbuNePCvCm0Mdz/mxw73mjdg0D4QdIl0TQBFs35CZmMXLjk0iCGK395CUDg==",
"dev": true, "dev": true,
"requires": { "requires": {
"aws-sign2": "0.6.0", "aws-sign2": "0.7.0",
"aws4": "1.6.0", "aws4": "1.6.0",
"caseless": "0.12.0", "caseless": "0.12.0",
"combined-stream": "1.0.5", "combined-stream": "1.0.5",
"extend": "3.0.1", "extend": "3.0.1",
"forever-agent": "0.6.1", "forever-agent": "0.6.1",
"form-data": "2.1.4", "form-data": "2.3.1",
"har-validator": "4.2.1", "har-validator": "5.0.3",
"hawk": "3.1.3", "hawk": "6.0.2",
"http-signature": "1.1.1", "http-signature": "1.2.0",
"is-typedarray": "1.0.0", "is-typedarray": "1.0.0",
"isstream": "0.1.2", "isstream": "0.1.2",
"json-stringify-safe": "5.0.1", "json-stringify-safe": "5.0.1",
"mime-types": "2.1.17", "mime-types": "2.1.17",
"oauth-sign": "0.8.2", "oauth-sign": "0.8.2",
"performance-now": "0.2.0", "performance-now": "2.1.0",
"qs": "6.4.0", "qs": "6.5.1",
"safe-buffer": "5.1.1", "safe-buffer": "5.1.1",
"stringstream": "0.0.5", "stringstream": "0.0.5",
"tough-cookie": "2.3.2", "tough-cookie": "2.3.3",
"tunnel-agent": "0.6.0", "tunnel-agent": "0.6.0",
"uuid": "3.1.0" "uuid": "3.1.0"
}, },
"dependencies": { "dependencies": {
"assert-plus": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
"integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=",
"dev": true
},
"aws-sign2": {
"version": "0.7.0",
"resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz",
"integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=",
"dev": true
},
"boom": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/boom/-/boom-4.3.1.tgz",
"integrity": "sha1-T4owBctKfjiJ90kDD9JbluAdLjE=",
"dev": true,
"requires": {
"hoek": "4.2.0"
}
},
"caseless": { "caseless": {
"version": "0.12.0", "version": "0.12.0",
"resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
"integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=",
"dev": true "dev": true
}, },
"har-validator": { "cryptiles": {
"version": "4.2.1", "version": "3.1.2",
"resolved": "https://registry.npmjs.org/har-validator/-/har-validator-4.2.1.tgz", "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-3.1.2.tgz",
"integrity": "sha1-M0gdDxu/9gDdID11gSpqX7oALio=", "integrity": "sha1-qJ+7Ig9c4l7FboxKqKT9e1sNKf4=",
"dev": true, "dev": true,
"requires": { "requires": {
"ajv": "4.11.8", "boom": "5.2.0"
"har-schema": "1.0.5" },
"dependencies": {
"boom": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/boom/-/boom-5.2.0.tgz",
"integrity": "sha512-Z5BTk6ZRe4tXXQlkqftmsAUANpXmuwlsF5Oov8ThoMbQRzdGTA1ngYRW160GexgOgjsFOKJz0LYhoNi+2AMBUw==",
"dev": true,
"requires": {
"hoek": "4.2.0"
}
}
}
},
"form-data": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.1.tgz",
"integrity": "sha1-b7lPvXGIUwbXPRXMSX/kzE7NRL8=",
"dev": true,
"requires": {
"asynckit": "0.4.0",
"combined-stream": "1.0.5",
"mime-types": "2.1.17"
}
},
"har-validator": {
"version": "5.0.3",
"resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.0.3.tgz",
"integrity": "sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0=",
"dev": true,
"requires": {
"ajv": "5.2.3",
"har-schema": "2.0.0"
}
},
"hawk": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/hawk/-/hawk-6.0.2.tgz",
"integrity": "sha512-miowhl2+U7Qle4vdLqDdPt9m09K6yZhkLDTWGoUiUzrQCn+mHHSmfJgAyGaLRZbPmTqfFFjRV1QWCW0VWUJBbQ==",
"dev": true,
"requires": {
"boom": "4.3.1",
"cryptiles": "3.1.2",
"hoek": "4.2.0",
"sntp": "2.0.2"
}
},
"hoek": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/hoek/-/hoek-4.2.0.tgz",
"integrity": "sha512-v0XCLxICi9nPfYrS9RL8HbYnXi9obYAeLbSP00BmnZwCK9+Ih9WOjoZ8YoHCoav2csqn4FOz4Orldsy2dmDwmQ==",
"dev": true
},
"http-signature": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz",
"integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=",
"dev": true,
"requires": {
"assert-plus": "1.0.0",
"jsprim": "1.4.1",
"sshpk": "1.13.1"
} }
}, },
"qs": { "qs": {
"version": "6.4.0", "version": "6.5.1",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.4.0.tgz", "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz",
"integrity": "sha1-E+JtKK1rD/qpExLNO/cI7TUecjM=", "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==",
"dev": true "dev": true
}, },
"sntp": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/sntp/-/sntp-2.0.2.tgz",
"integrity": "sha1-UGQRDwr4X3z9t9a2ekACjOUrSys=",
"dev": true,
"requires": {
"hoek": "4.2.0"
}
},
"tunnel-agent": { "tunnel-agent": {
"version": "0.6.0", "version": "0.6.0",
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
@@ -2188,9 +2286,9 @@
} }
}, },
"rimraf": { "rimraf": {
"version": "2.6.1", "version": "2.6.2",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.1.tgz", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz",
"integrity": "sha1-wjOOxkPfeht/5cVPqG9XQopV8z0=", "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==",
"dev": true, "dev": true,
"requires": { "requires": {
"glob": "7.1.1" "glob": "7.1.1"
@@ -2250,7 +2348,7 @@
"resolved": "https://registry.npmjs.org/spawn-rx/-/spawn-rx-2.0.11.tgz", "resolved": "https://registry.npmjs.org/spawn-rx/-/spawn-rx-2.0.11.tgz",
"integrity": "sha1-ZUUa1lZigB2up1VJgyp4LeAEjb8=", "integrity": "sha1-ZUUa1lZigB2up1VJgyp4LeAEjb8=",
"requires": { "requires": {
"debug": "2.6.8", "debug": "2.6.9",
"lodash.assign": "4.2.0", "lodash.assign": "4.2.0",
"rxjs": "5.4.3" "rxjs": "5.4.3"
} }
@@ -2339,6 +2437,15 @@
"integrity": "sha1-TkhM1N5aC7vuGORjB3EKioFiGHg=", "integrity": "sha1-TkhM1N5aC7vuGORjB3EKioFiGHg=",
"dev": true "dev": true
}, },
"strip-ansi": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
"integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
"dev": true,
"requires": {
"ansi-regex": "2.1.1"
}
},
"strip-bom": { "strip-bom": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz",
@@ -2439,9 +2546,9 @@
} }
}, },
"tough-cookie": { "tough-cookie": {
"version": "2.3.2", "version": "2.3.3",
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.2.tgz", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.3.tgz",
"integrity": "sha1-8IH3bkyFcg5sN6X6ztc3FQ2EByo=", "integrity": "sha1-C2GKVWW23qkL80JdBNVe3EdadWE=",
"dev": true, "dev": true,
"requires": { "requires": {
"punycode": "1.4.1" "punycode": "1.4.1"
@@ -2468,13 +2575,13 @@
"resolve": "1.4.0", "resolve": "1.4.0",
"semver": "5.4.1", "semver": "5.4.1",
"tslib": "1.7.1", "tslib": "1.7.1",
"tsutils": "2.8.2" "tsutils": "2.9.0"
} }
}, },
"tsutils": { "tsutils": {
"version": "2.8.2", "version": "2.9.0",
"resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.8.2.tgz", "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.9.0.tgz",
"integrity": "sha1-LBSGukMSYIRbCsb5Aq/Z1wio6mo=", "integrity": "sha1-fhU3tVa6tocvp+ZIXf9FsHbVUz0=",
"dev": true, "dev": true,
"requires": { "requires": {
"tslib": "1.7.1" "tslib": "1.7.1"
@@ -2494,9 +2601,9 @@
"optional": true "optional": true
}, },
"typescript": { "typescript": {
"version": "2.5.2", "version": "2.5.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-2.5.2.tgz", "resolved": "https://registry.npmjs.org/typescript/-/typescript-2.5.3.tgz",
"integrity": "sha1-A4qV99m7tCCxvzW6MdTFwd0//jQ=", "integrity": "sha512-ptLSQs2S4QuS6/OD1eAKG+S5G8QQtrU5RT32JULdZQtM1L3WTi34Wsu48Yndzi8xsObRAB9RPt/KhA9wlpEF6w==",
"dev": true "dev": true
}, },
"unique-stream": { "unique-stream": {
@@ -2687,8 +2794,8 @@
"gulp-symdest": "1.1.0", "gulp-symdest": "1.1.0",
"gulp-untar": "0.0.6", "gulp-untar": "0.0.6",
"gulp-vinyl-zip": "1.4.0", "gulp-vinyl-zip": "1.4.0",
"mocha": "3.5.2", "mocha": "3.5.3",
"request": "2.81.0", "request": "2.82.0",
"semver": "5.4.1", "semver": "5.4.1",
"source-map-support": "0.4.18", "source-map-support": "0.4.18",
"url-parse": "1.1.9", "url-parse": "1.1.9",

View File

@@ -1,6 +1,6 @@
{ {
"name": "gitlens", "name": "gitlens",
"version": "5.2.0", "version": "5.3.0",
"author": { "author": {
"name": "Eric Amodio", "name": "Eric Amodio",
"email": "eamodio@gmail.com" "email": "eamodio@gmail.com"
@@ -421,13 +421,33 @@
"gitlens.gitExplorer.commitFileFormat": { "gitlens.gitExplorer.commitFileFormat": {
"type": "string", "type": "string",
"default": "${filePath}", "default": "${filePath}",
"description": "Specifies the format of a committed file in the `GitLens` custom view\nAvailable tokens\n ${file} - file name\n ${filePath} - file name and path\n ${path} - file path" "description": "Specifies the format of a committed file in the `GitLens` custom view\nAvailable tokens\n ${directory} - directory name\n ${file} - file name\n ${filePath} - formatted file name and path\n ${path} - full file path"
}, },
"gitlens.gitExplorer.enabled": { "gitlens.gitExplorer.enabled": {
"type": "boolean", "type": "boolean",
"default": true, "default": true,
"description": "Specifies whether or not to show the `GitLens` custom view" "description": "Specifies whether or not to show the `GitLens` custom view"
}, },
"gitlens.gitExplorer.files.layout": {
"type": "string",
"default": "auto",
"enum": [
"auto",
"list",
"tree"
],
"description": "Specifies how the `GitLens` custom view will display files\n `auto` - automatically switches between displaying files as a `tree` or `list` based on the `gitlens.gitExplorer.files.threshold` setting and the number of files at each nesting level\n `list` - displays files as a list\n `tree` - displays files as a tree"
},
"gitlens.gitExplorer.files.compact": {
"type": "boolean",
"default": true,
"description": "Specifies whether or not to compact (flatten) unnecessary file nesting in the `GitLens` custom view\nOnly applies when displaying files as a `tree` or `auto`"
},
"gitlens.gitExplorer.files.threshold": {
"type": "number",
"default": 5,
"description": "Specifies when to switch between displaying files as a `tree` or `list` based on the number of files in a nesting level in the `GitLens` custom view\nOnly applies when displaying files as `auto`"
},
"gitlens.gitExplorer.includeWorkingTree": { "gitlens.gitExplorer.includeWorkingTree": {
"type": "boolean", "type": "boolean",
"default": true, "default": true,
@@ -446,12 +466,12 @@
"gitlens.gitExplorer.stashFileFormat": { "gitlens.gitExplorer.stashFileFormat": {
"type": "string", "type": "string",
"default": "${filePath}", "default": "${filePath}",
"description": "Specifies the format of a stashed file in the `GitLens` custom view\nAvailable tokens\n ${file} - file name\n ${filePath} - file name and path\n ${path} - file path" "description": "Specifies the format of a stashed file in the `GitLens` custom view\nAvailable tokens\n ${directory} - directory name\n ${file} - file name\n ${filePath} - formatted file name and path\n ${path} - full file path"
}, },
"gitlens.gitExplorer.statusFileFormat": { "gitlens.gitExplorer.statusFileFormat": {
"type": "string", "type": "string",
"default": "${working}${filePath}", "default": "${working}${filePath}",
"description": "Specifies the format of the status of a working or committed file in the `GitLens` custom view\nAvailable tokens\n ${file} - file name\n ${filePath} - file name and path\n ${path} - file path\n ${working} - optional indicator if the file is uncommitted" "description": "Specifies the format of the status of a working or committed file in the `GitLens` custom view\nAvailable tokens\n ${directory} - directory name\n ${file} - file name\n ${filePath} - formatted file name and path\n ${path} - full file path\n ${working} - optional indicator if the file is uncommitted"
}, },
"gitlens.gitExplorer.view": { "gitlens.gitExplorer.view": {
"type": "string", "type": "string",
@@ -1938,11 +1958,11 @@
"@types/copy-paste": "1.1.30", "@types/copy-paste": "1.1.30",
"@types/iconv-lite": "0.0.1", "@types/iconv-lite": "0.0.1",
"@types/mocha": "2.2.43", "@types/mocha": "2.2.43",
"@types/node": "8.0.28", "@types/node": "8.0.31",
"@types/tmp": "0.0.33", "@types/tmp": "0.0.33",
"mocha": "3.5.2", "mocha": "3.5.3",
"tslint": "5.7.0", "tslint": "5.7.0",
"typescript": "2.5.2", "typescript": "2.5.3",
"vscode": "1.1.5" "vscode": "1.1.5"
} }
} }

View File

@@ -138,7 +138,7 @@ export class Annotations {
// Try to get the width of the string, if there is a cap // Try to get the width of the string, if there is a cap
let width = 4; // Start with a padding let width = 4; // Start with a padding
for (const token of Objects.values<Strings.ITokenOptions | undefined>(options.tokenOptions)) { for (const token of Objects.values(options.tokenOptions!)) {
if (token === undefined) continue; if (token === undefined) continue;
// If any token is uncapped, kick out and set no max // If any token is uncapped, kick out and set no max

View File

@@ -11,7 +11,7 @@ export class ResetSuppressedWarningsCommand extends Command {
} }
async execute() { async execute() {
for (const key of Objects.values<string>(SuppressedKeys)) { for (const key of Objects.values(SuppressedKeys)) {
await this.context.globalState.update(key, false); await this.context.globalState.update(key, false);
} }
} }

View File

@@ -53,6 +53,16 @@ export const CustomRemoteType = {
GitLab: 'GitLab' as CustomRemoteType GitLab: 'GitLab' as CustomRemoteType
}; };
export type GitExplorerFilesLayout =
'auto' |
'list' |
'tree';
export const GitExplorerFilesLayout = {
Auto: 'auto' as GitExplorerFilesLayout,
List: 'list' as GitExplorerFilesLayout,
Tree: 'tree' as GitExplorerFilesLayout
};
export type StatusBarCommand = export type StatusBarCommand =
'gitlens.toggleFileBlame' | 'gitlens.toggleFileBlame' |
'gitlens.showBlameHistory' | 'gitlens.showBlameHistory' |
@@ -132,6 +142,24 @@ export interface ICodeLensLanguageLocation {
customSymbols?: string[]; customSymbols?: string[];
} }
export interface IGitExplorerConfig {
enabled: boolean;
view: GitExplorerView;
files: {
layout: GitExplorerFilesLayout;
compact: boolean;
threshold: number;
};
includeWorkingTree: boolean;
showTrackingBranch: boolean;
commitFormat: string;
commitFileFormat: string;
stashFormat: string;
stashFileFormat: string;
statusFileFormat: string;
// dateFormat: string | null;
}
export interface IRemotesConfig { export interface IRemotesConfig {
type: CustomRemoteType; type: CustomRemoteType;
domain: string; domain: string;
@@ -316,18 +344,7 @@ export interface IConfig {
defaultDateFormat: string | null; defaultDateFormat: string | null;
gitExplorer: { gitExplorer: IGitExplorerConfig;
enabled: boolean;
view: GitExplorerView;
includeWorkingTree: boolean;
showTrackingBranch: boolean;
commitFormat: string;
commitFileFormat: string;
stashFormat: string;
stashFileFormat: string;
statusFileFormat: string;
// dateFormat: string | null;
};
remotes: IRemotesConfig[]; remotes: IRemotesConfig[];

View File

@@ -6,7 +6,10 @@ import { GitStatusFile, IGitStatusFile, IGitStatusFileWithCommit } from '../mode
import * as path from 'path'; import * as path from 'path';
export interface IStatusFormatOptions extends IFormatOptions { export interface IStatusFormatOptions extends IFormatOptions {
relativePath?: string;
tokenOptions?: { tokenOptions?: {
directory?: Strings.ITokenOptions;
file?: Strings.ITokenOptions; file?: Strings.ITokenOptions;
filePath?: Strings.ITokenOptions; filePath?: Strings.ITokenOptions;
path?: Strings.ITokenOptions; path?: Strings.ITokenOptions;
@@ -15,18 +18,23 @@ export interface IStatusFormatOptions extends IFormatOptions {
export class StatusFileFormatter extends Formatter<IGitStatusFile, IStatusFormatOptions> { export class StatusFileFormatter extends Formatter<IGitStatusFile, IStatusFormatOptions> {
get directory() {
const directory = GitStatusFile.getFormattedDirectory(this._item, false, this._options.relativePath);
return this._padOrTruncate(directory, this._options.tokenOptions!.file);
}
get file() { get file() {
const file = path.basename(this._item.fileName); const file = path.basename(this._item.fileName);
return this._padOrTruncate(file, this._options.tokenOptions!.file); return this._padOrTruncate(file, this._options.tokenOptions!.file);
} }
get filePath() { get filePath() {
const filePath = GitStatusFile.getFormattedPath(this._item); const filePath = GitStatusFile.getFormattedPath(this._item, undefined, this._options.relativePath);
return this._padOrTruncate(filePath, this._options.tokenOptions!.filePath); return this._padOrTruncate(filePath, this._options.tokenOptions!.filePath);
} }
get path() { get path() {
const directory = GitStatusFile.getFormattedDirectory(this._item, false); const directory = GitStatusFile.getRelativePath(this._item, this._options.relativePath);
return this._padOrTruncate(directory, this._options.tokenOptions!.file); return this._padOrTruncate(directory, this._options.tokenOptions!.file);
} }

View File

@@ -420,7 +420,7 @@ export class Git {
static status(repoPath: string, porcelainVersion: number = 1): Promise<string> { static status(repoPath: string, porcelainVersion: number = 1): Promise<string> {
const porcelain = porcelainVersion >= 2 ? `--porcelain=v${porcelainVersion}` : '--porcelain'; const porcelain = porcelainVersion >= 2 ? `--porcelain=v${porcelainVersion}` : '--porcelain';
return gitCommand({ cwd: repoPath }, 'status', porcelain, '--branch'); return gitCommand({ cwd: repoPath }, 'status', porcelain, '--branch', '-u');
} }
static status_file(repoPath: string, fileName: string, porcelainVersion: number = 1): Promise<string> { static status_file(repoPath: string, fileName: string, porcelainVersion: number = 1): Promise<string> {

View File

@@ -63,11 +63,14 @@ export class GitUri extends Uri {
return Uri.file(this.sha ? this.path : this.fsPath); return Uri.file(this.sha ? this.path : this.fsPath);
} }
getFormattedPath(separator: string = Strings.pad(GlyphChars.Dot, 2, 2)): string { getFormattedPath(separator: string = Strings.pad(GlyphChars.Dot, 2, 2), relativeTo?: string): string {
let directory = path.dirname(this.fsPath); let directory = path.dirname(this.fsPath);
if (this.repoPath) { if (this.repoPath) {
directory = path.relative(this.repoPath, directory); directory = path.relative(this.repoPath, directory);
} }
if (relativeTo !== undefined) {
directory = path.relative(relativeTo, directory);
}
directory = GitService.normalizePath(directory); directory = GitService.normalizePath(directory);
return (!directory || directory === '.') return (!directory || directory === '.')
@@ -75,8 +78,12 @@ export class GitUri extends Uri {
: `${path.basename(this.fsPath)}${separator}${directory}`; : `${path.basename(this.fsPath)}${separator}${directory}`;
} }
getRelativePath(): string { getRelativePath(relativeTo?: string): string {
return GitService.normalizePath(path.relative(this.repoPath || '', this.fsPath)); let relativePath = path.relative(this.repoPath || '', this.fsPath);
if (relativeTo !== undefined) {
relativePath = path.relative(relativeTo, relativePath);
}
return GitService.normalizePath(relativePath);
} }
static async fromUri(uri: Uri, git: GitService) { static async fromUri(uri: Uri, git: GitService) {
@@ -104,15 +111,19 @@ export class GitUri extends Uri {
return new GitUri(uri, repoPathOrCommit); return new GitUri(uri, repoPathOrCommit);
} }
static getDirectory(fileName: string): string { static getDirectory(fileName: string, relativeTo?: string): string {
const directory: string | undefined = GitService.normalizePath(path.dirname(fileName)); let directory: string | undefined = path.dirname(fileName);
if (relativeTo !== undefined) {
directory = path.relative(relativeTo, directory);
}
directory = GitService.normalizePath(directory);
return (!directory || directory === '.') ? '' : directory; return (!directory || directory === '.') ? '' : directory;
} }
static getFormattedPath(fileNameOrUri: string | Uri, separator: string = Strings.pad(GlyphChars.Dot, 2, 2)): string { static getFormattedPath(fileNameOrUri: string | Uri, separator: string = Strings.pad(GlyphChars.Dot, 2, 2), relativeTo?: string): string {
let fileName: string; let fileName: string;
if (fileNameOrUri instanceof Uri) { if (fileNameOrUri instanceof Uri) {
if (fileNameOrUri instanceof GitUri) return fileNameOrUri.getFormattedPath(separator); if (fileNameOrUri instanceof GitUri) return fileNameOrUri.getFormattedPath(separator, relativeTo);
fileName = fileNameOrUri.fsPath; fileName = fileNameOrUri.fsPath;
} }
@@ -120,11 +131,29 @@ export class GitUri extends Uri {
fileName = fileNameOrUri; fileName = fileNameOrUri;
} }
const directory = GitUri.getDirectory(fileName); const directory = GitUri.getDirectory(fileName, relativeTo);
return !directory return !directory
? path.basename(fileName) ? path.basename(fileName)
: `${path.basename(fileName)}${separator}${directory}`; : `${path.basename(fileName)}${separator}${directory}`;
} }
static getRelativePath(fileNameOrUri: string | Uri, relativeTo?: string, repoPath?: string): string {
let fileName: string;
if (fileNameOrUri instanceof Uri) {
if (fileNameOrUri instanceof GitUri) return fileNameOrUri.getRelativePath(relativeTo);
fileName = fileNameOrUri.fsPath;
}
else {
fileName = fileNameOrUri;
}
let relativePath = path.relative(repoPath || '', fileName);
if (relativeTo !== undefined) {
relativePath = path.relative(relativeTo, relativePath);
}
return GitService.normalizePath(relativePath);
}
} }
export interface IGitCommitInfo { export interface IGitCommitInfo {

View File

@@ -56,15 +56,19 @@ export class GitStatusFile implements IGitStatusFile {
return Uri.file(path.resolve(this.repoPath, this.fileName)); return Uri.file(path.resolve(this.repoPath, this.fileName));
} }
static getFormattedDirectory(status: IGitStatusFile, includeOriginal: boolean = false): string { static getFormattedDirectory(status: IGitStatusFile, includeOriginal: boolean = false, relativeTo?: string): string {
const directory = GitUri.getDirectory(status.fileName); const directory = GitUri.getDirectory(status.fileName, relativeTo);
return (includeOriginal && status.status === 'R' && status.originalFileName) return (includeOriginal && status.status === 'R' && status.originalFileName)
? `${directory} ${Strings.pad(GlyphChars.ArrowLeft, 1, 1)} ${status.originalFileName}` ? `${directory} ${Strings.pad(GlyphChars.ArrowLeft, 1, 1)} ${status.originalFileName}`
: directory; : directory;
} }
static getFormattedPath(status: IGitStatusFile, separator: string = Strings.pad(GlyphChars.Dot, 2, 2)): string { static getFormattedPath(status: IGitStatusFile, separator: string = Strings.pad(GlyphChars.Dot, 2, 2), relativeTo?: string): string {
return GitUri.getFormattedPath(status.fileName, separator); return GitUri.getFormattedPath(status.fileName, separator, relativeTo);
}
static getRelativePath(status: IGitStatusFile, relativeTo?: string): string {
return GitUri.getRelativePath(status.fileName, relativeTo);
} }
} }

View File

@@ -1,6 +1,16 @@
'use strict'; 'use strict';
import { Objects } from './object';
export namespace Arrays { export namespace Arrays {
export function countUniques<T>(array: T[], accessor: (item: T) => string): { [key: string]: number } {
const uniqueCounts = Object.create(null);
for (const item of array) {
const value = accessor(item);
uniqueCounts[value] = (uniqueCounts[value] || 0) + 1;
}
return uniqueCounts;
}
export function groupBy<T>(array: T[], accessor: (item: T) => string): { [key: string]: T[] } { export function groupBy<T>(array: T[], accessor: (item: T) => string): { [key: string]: T[] } {
return array.reduce((previous, current) => { return array.reduce((previous, current) => {
const value = accessor(current); const value = accessor(current);
@@ -10,6 +20,96 @@ export namespace Arrays {
}, Object.create(null)); }, Object.create(null));
} }
export interface IHierarchicalItem<T> {
name: string;
relativePath: string;
value?: T;
// parent?: IHierarchicalItem<T>;
children: { [key: string]: IHierarchicalItem<T> } | undefined;
descendants: T[] | undefined;
}
export function makeHierarchical<T>(values: T[], splitPath: (i: T) => string[], joinPath: (...paths: string[]) => string, compact: boolean = false): IHierarchicalItem<T> {
const seed = {
name: '',
relativePath: '',
children: Object.create(null),
descendants: []
};
const hierarchy = values.reduce((root: IHierarchicalItem<T>, value) => {
let folder = root;
let relativePath = '';
for (const folderName of splitPath(value)) {
relativePath = joinPath(relativePath, folderName);
if (folder.children === undefined) {
folder.children = Object.create(null);
}
let f = folder.children![folderName];
if (f === undefined) {
folder.children![folderName] = f = {
name: folderName,
relativePath: relativePath,
// parent: folder,
children: undefined,
descendants: undefined
};
}
if (folder.descendants === undefined) {
folder.descendants = [];
}
folder.descendants.push(value);
folder = f;
}
folder.value = value;
return root;
}, seed);
if (compact) return compactHierarchy(hierarchy, joinPath, true);
return hierarchy;
}
export function compactHierarchy<T>(root: IHierarchicalItem<T>, joinPath: (...paths: string[]) => string, isRoot: boolean = true): IHierarchicalItem<T> {
if (root.children === undefined) return root;
const children = [...Objects.values(root.children)];
// // Attempts less nesting but duplicate roots
// if (!isRoot && children.every(c => c.value === undefined)) {
// const parentSiblings = root.parent!.children!;
// if (parentSiblings[root.name] !== undefined) {
// delete parentSiblings[root.name];
// for (const child of children) {
// child.name = joinPath(root.name, child.name);
// parentSiblings[child.name] = child;
// }
// }
// }
for (const child of children) {
compactHierarchy(child, joinPath, false);
}
if (!isRoot && children.length === 1) {
const child = children[0];
if (child.value === undefined) {
root.name = joinPath(root.name, child.name);
root.relativePath = child.relativePath;
root.children = child.children;
}
}
return root;
}
export function uniqueBy<T>(array: T[], accessor: (item: T) => any, predicate?: (item: T) => boolean): T[] { export function uniqueBy<T>(array: T[], accessor: (item: T) => any, predicate?: (item: T) => boolean): T[] {
const uniqueValues = Object.create(null); const uniqueValues = Object.create(null);
return array.filter(_ => { return array.filter(_ => {

View File

@@ -6,7 +6,9 @@ export namespace Objects {
return _isEqual(first, second); return _isEqual(first, second);
} }
export function* entries(o: any): IterableIterator<[string, any]> { export function entries<T>(o: { [key: string]: T }): IterableIterator<[string, T]>;
export function entries<T>(o: { [key: number]: T }): IterableIterator<[string, T]>;
export function* entries<T>(o: any): IterableIterator<[string, T]> {
for (const key in o) { for (const key in o) {
yield [key, o[key]]; yield [key, o[key]];
} }
@@ -56,6 +58,8 @@ export namespace Objects {
} }
} }
export function values<T>(o: { [key: string]: T }): IterableIterator<T>;
export function values<T>(o: { [key: number]: T }): IterableIterator<T>;
export function* values<T>(o: any): IterableIterator<T> { export function* values<T>(o: any): IterableIterator<T> {
for (const key in o) { for (const key in o) {
yield o[key]; yield o[key];

View File

@@ -2,7 +2,7 @@
import { Command, ExtensionContext, TreeItem, TreeItemCollapsibleState, Uri } from 'vscode'; import { Command, ExtensionContext, TreeItem, TreeItemCollapsibleState, Uri } from 'vscode';
import { Commands, DiffWithPreviousCommandArgs } from '../commands'; import { Commands, DiffWithPreviousCommandArgs } from '../commands';
import { ExplorerNode, ResourceType } from './explorerNode'; import { ExplorerNode, ResourceType } from './explorerNode';
import { CommitFormatter, getGitStatusIcon, GitBranch, GitCommit, GitService, GitUri, ICommitFormatOptions, IGitStatusFile, StatusFileFormatter } from '../gitService'; import { CommitFormatter, getGitStatusIcon, GitBranch, GitCommit, GitService, GitUri, ICommitFormatOptions, IGitStatusFile, IStatusFormatOptions, StatusFileFormatter } from '../gitService';
import * as path from 'path'; import * as path from 'path';
export enum CommitFileNodeDisplayAs { export enum CommitFileNodeDisplayAs {
@@ -17,6 +17,8 @@ export enum CommitFileNodeDisplayAs {
export class CommitFileNode extends ExplorerNode { export class CommitFileNode extends ExplorerNode {
readonly priority: boolean = false;
readonly repoPath: string;
readonly resourceType: ResourceType = 'gitlens:commit-file'; readonly resourceType: ResourceType = 'gitlens:commit-file';
constructor( constructor(
@@ -28,6 +30,7 @@ export class CommitFileNode extends ExplorerNode {
public readonly branch?: GitBranch public readonly branch?: GitBranch
) { ) {
super(new GitUri(Uri.file(path.resolve(commit.repoPath, status.fileName)), { repoPath: commit.repoPath, fileName: status.fileName, sha: commit.sha })); super(new GitUri(Uri.file(path.resolve(commit.repoPath, status.fileName)), { repoPath: commit.repoPath, fileName: status.fileName, sha: commit.sha }));
this.repoPath = commit.repoPath;
} }
async getChildren(): Promise<ExplorerNode[]> { async getChildren(): Promise<ExplorerNode[]> {
@@ -36,7 +39,7 @@ export class CommitFileNode extends ExplorerNode {
async getTreeItem(): Promise<TreeItem> { async getTreeItem(): Promise<TreeItem> {
if (this.commit.type !== 'file') { if (this.commit.type !== 'file') {
const log = await this.git.getLogForFile(this.commit.repoPath, this.status.fileName, this.commit.sha, { maxCount: 2 }); const log = await this.git.getLogForFile(this.repoPath, this.status.fileName, this.commit.sha, { maxCount: 2 });
if (log !== undefined) { if (log !== undefined) {
this.commit = log.commits.get(this.commit.sha) || this.commit; this.commit = log.commits.get(this.commit.sha) || this.commit;
} }
@@ -62,6 +65,14 @@ export class CommitFileNode extends ExplorerNode {
return item; return item;
} }
private _folderName: string | undefined;
get folderName() {
if (this._folderName === undefined) {
this._folderName = path.dirname(this.uri.getRelativePath());
}
return this._folderName;
}
private _label: string | undefined; private _label: string | undefined;
get label() { get label() {
if (this._label === undefined) { if (this._label === undefined) {
@@ -70,11 +81,22 @@ export class CommitFileNode extends ExplorerNode {
truncateMessageAtNewLine: true, truncateMessageAtNewLine: true,
dataFormat: this.git.config.defaultDateFormat dataFormat: this.git.config.defaultDateFormat
} as ICommitFormatOptions) } as ICommitFormatOptions)
: StatusFileFormatter.fromTemplate(this.getCommitFileTemplate(), this.status); : StatusFileFormatter.fromTemplate(this.getCommitFileTemplate(),
this.status,
{ relativePath: this.relativePath } as IStatusFormatOptions);
} }
return this._label; return this._label;
} }
private _relativePath: string | undefined;
get relativePath(): string | undefined {
return this._relativePath;
}
set relativePath(value: string | undefined) {
this._relativePath = value;
this._label = undefined;
}
protected getCommitTemplate() { protected getCommitTemplate() {
return this.git.config.gitExplorer.commitFormat; return this.git.config.gitExplorer.commitFormat;
} }

View File

@@ -1,13 +1,17 @@
'use strict'; 'use strict';
import { Iterables } from '../system'; import { Arrays, Iterables } from '../system';
import { Command, ExtensionContext, TreeItem, TreeItemCollapsibleState } from 'vscode'; import { Command, ExtensionContext, TreeItem, TreeItemCollapsibleState } from 'vscode';
import { Commands, DiffWithPreviousCommandArgs } from '../commands'; import { Commands, DiffWithPreviousCommandArgs } from '../commands';
import { CommitFileNode, CommitFileNodeDisplayAs } from './commitFileNode'; import { CommitFileNode, CommitFileNodeDisplayAs } from './commitFileNode';
import { GitExplorerFilesLayout } from '../configuration';
import { FolderNode, IFileExplorerNode } from './folderNode';
import { ExplorerNode, ResourceType } from './explorerNode'; import { ExplorerNode, ResourceType } from './explorerNode';
import { CommitFormatter, GitBranch, GitLogCommit, GitService, GitUri, ICommitFormatOptions } from '../gitService'; import { CommitFormatter, GitBranch, GitLogCommit, GitService, GitUri, ICommitFormatOptions } from '../gitService';
import * as path from 'path';
export class CommitNode extends ExplorerNode { export class CommitNode extends ExplorerNode {
readonly repoPath: string;
readonly resourceType: ResourceType = 'gitlens:commit'; readonly resourceType: ResourceType = 'gitlens:commit';
constructor( constructor(
@@ -17,17 +21,32 @@ export class CommitNode extends ExplorerNode {
public readonly branch?: GitBranch public readonly branch?: GitBranch
) { ) {
super(new GitUri(commit.uri, commit)); super(new GitUri(commit.uri, commit));
this.repoPath = commit.repoPath;
} }
async getChildren(): Promise<ExplorerNode[]> { async getChildren(): Promise<ExplorerNode[]> {
const log = await this.git.getLogForRepo(this.commit.repoPath, this.commit.sha, 1); const repoPath = this.repoPath;
const log = await this.git.getLogForRepo(repoPath, this.commit.sha, 1);
if (log === undefined) return []; if (log === undefined) return [];
const commit = Iterables.first(log.commits.values()); const commit = Iterables.first(log.commits.values());
if (commit === undefined) return []; if (commit === undefined) return [];
const children = [...Iterables.map(commit.fileStatuses, s => new CommitFileNode(s, commit, this.context, this.git, CommitFileNodeDisplayAs.File, this.branch))]; let children: IFileExplorerNode[] = [
children.sort((a, b) => a.label!.localeCompare(b.label!)); ...Iterables.map(commit.fileStatuses, s => new CommitFileNode(s, commit, this.context, this.git, CommitFileNodeDisplayAs.File, this.branch))
];
if (this.git.config.gitExplorer.files.layout !== GitExplorerFilesLayout.List) {
const hierarchy = Arrays.makeHierarchical(children, n => n.uri.getRelativePath().split('/'),
(...paths: string[]) => GitService.normalizePath(path.join(...paths)), this.git.config.gitExplorer.files.compact);
const root = new FolderNode(repoPath, '', undefined, hierarchy, this.git.config.gitExplorer);
children = await root.getChildren() as IFileExplorerNode[];
}
else {
children.sort((a, b) => a.label!.localeCompare(b.label!));
}
return children; return children;
} }

View File

@@ -10,6 +10,7 @@ export declare type ResourceType =
'gitlens:commit' | 'gitlens:commit' |
'gitlens:commit-file' | 'gitlens:commit-file' |
'gitlens:file-history' | 'gitlens:file-history' |
'gitlens:folder' |
'gitlens:history' | 'gitlens:history' |
'gitlens:message' | 'gitlens:message' |
'gitlens:pager' | 'gitlens:pager' |

85
src/views/folderNode.ts Normal file
View File

@@ -0,0 +1,85 @@
'use strict';
import { Arrays, Objects } from '../system';
import { TreeItem, TreeItemCollapsibleState, Uri } from 'vscode';
import { GitExplorerFilesLayout, IGitExplorerConfig } from '../configuration';
import { ExplorerNode, ResourceType } from './explorerNode';
import { GitUri } from '../gitService';
export interface IFileExplorerNode extends ExplorerNode {
folderName: string;
label?: string;
priority: boolean;
relativePath?: string;
root?: Arrays.IHierarchicalItem<IFileExplorerNode>;
}
export class FolderNode extends ExplorerNode {
readonly priority: boolean = true;
readonly resourceType: ResourceType = 'gitlens:folder';
constructor(
public readonly repoPath: string,
public folderName: string,
public relativePath: string | undefined,
public readonly root: Arrays.IHierarchicalItem<IFileExplorerNode>,
private readonly config: IGitExplorerConfig
) {
super(new GitUri(Uri.file(repoPath), { repoPath: repoPath, fileName: repoPath }));
}
async getChildren(): Promise<(FolderNode | IFileExplorerNode)[]> {
if (this.root.descendants === undefined || this.root.children === undefined) return [];
let children: (FolderNode | IFileExplorerNode)[];
const nesting = FolderNode.getFileNesting(this.config, this.root.descendants, this.relativePath === undefined);
if (nesting !== GitExplorerFilesLayout.List) {
children = [];
for (const folder of Objects.values(this.root.children)) {
if (folder.value === undefined) {
children.push(new FolderNode(this.repoPath, folder.name, folder.relativePath, folder, this.config));
continue;
}
folder.value.relativePath = this.root.relativePath;
children.push(folder.value);
}
}
else {
this.root.descendants.forEach(n => n.relativePath = this.root.relativePath);
children = this.root.descendants;
}
children.sort((a, b) => {
return ((a instanceof FolderNode) ? -1 : 1) - ((b instanceof FolderNode) ? -1 : 1) ||
(a.priority ? -1 : 1) - (b.priority ? -1 : 1) ||
a.label!.localeCompare(b.label!);
});
return children;
}
async getTreeItem(): Promise<TreeItem> {
// TODO: Change this to expanded once https://github.com/Microsoft/vscode/issues/30918 is fixed
const item = new TreeItem(this.label, TreeItemCollapsibleState.Collapsed);
item.contextValue = this.resourceType;
return item;
}
get label(): string {
return this.folderName;
}
static getFileNesting<T extends IFileExplorerNode>(config: IGitExplorerConfig, children: T[], isRoot: boolean): GitExplorerFilesLayout {
const nesting = config.files.layout || GitExplorerFilesLayout.Auto;
if (nesting === GitExplorerFilesLayout.Auto) {
if (isRoot || config.files.compact) {
const nestingThreshold = config.files.threshold || 5;
if (children.length <= nestingThreshold) return GitExplorerFilesLayout.List;
}
return GitExplorerFilesLayout.Tree;
}
return nesting;
}
}

View File

@@ -3,7 +3,7 @@ import { Command, ExtensionContext, TreeItem, TreeItemCollapsibleState, Uri } fr
import { Commands, DiffWithPreviousCommandArgs } from '../commands'; import { Commands, DiffWithPreviousCommandArgs } from '../commands';
import { CommitFileNode, CommitFileNodeDisplayAs } from './commitFileNode'; import { CommitFileNode, CommitFileNodeDisplayAs } from './commitFileNode';
import { ExplorerNode, ResourceType } from './explorerNode'; import { ExplorerNode, ResourceType } from './explorerNode';
import { getGitStatusIcon, GitBranch, GitLogCommit, GitService, GitUri, IGitStatusFile, IGitStatusFileWithCommit, StatusFileFormatter } from '../gitService'; import { getGitStatusIcon, GitBranch, GitLogCommit, GitService, GitUri, IGitStatusFile, IGitStatusFileWithCommit, IStatusFormatOptions, StatusFileFormatter } from '../gitService';
import * as path from 'path'; import * as path from 'path';
export class StatusFileCommitsNode extends ExplorerNode { export class StatusFileCommitsNode extends ExplorerNode {
@@ -11,7 +11,7 @@ export class StatusFileCommitsNode extends ExplorerNode {
readonly resourceType: ResourceType = 'gitlens:status-file-commits'; readonly resourceType: ResourceType = 'gitlens:status-file-commits';
constructor( constructor(
repoPath: string, public readonly repoPath: string,
public readonly status: IGitStatusFile, public readonly status: IGitStatusFile,
public commits: GitLogCommit[], public commits: GitLogCommit[],
protected readonly context: ExtensionContext, protected readonly context: ExtensionContext,
@@ -47,10 +47,20 @@ export class StatusFileCommitsNode extends ExplorerNode {
return item; return item;
} }
private _folderName: string | undefined;
get folderName() {
if (this._folderName === undefined) {
this._folderName = path.dirname(this.uri.getRelativePath());
}
return this._folderName;
}
private _label: string | undefined; private _label: string | undefined;
get label() { get label() {
if (this._label === undefined) { if (this._label === undefined) {
this._label = StatusFileFormatter.fromTemplate(this.git.config.gitExplorer.statusFileFormat, { ...this.status, commit: this.commit } as IGitStatusFileWithCommit); this._label = StatusFileFormatter.fromTemplate(this.git.config.gitExplorer.statusFileFormat,
{ ...this.status, commit: this.commit } as IGitStatusFileWithCommit,
{ relativePath: this.relativePath } as IStatusFormatOptions);
} }
return this._label; return this._label;
} }
@@ -59,12 +69,25 @@ export class StatusFileCommitsNode extends ExplorerNode {
return this.commits[0]; return this.commits[0];
} }
get priority(): boolean {
return this.commit.isUncommitted;
}
private _relativePath: string | undefined;
get relativePath(): string | undefined {
return this._relativePath;
}
set relativePath(value: string | undefined) {
this._relativePath = value;
this._label = undefined;
}
getCommand(): Command | undefined { getCommand(): Command | undefined {
return { return {
title: 'Compare File with Previous Revision', title: 'Compare File with Previous Revision',
command: Commands.DiffWithPrevious, command: Commands.DiffWithPrevious,
arguments: [ arguments: [
GitUri.fromFileStatus(this.status, this.uri.repoPath!), GitUri.fromFileStatus(this.status, this.repoPath),
{ {
commit: this.commit, commit: this.commit,
line: 0, line: 0,

View File

@@ -1,12 +1,16 @@
'use strict'; 'use strict';
import { Arrays, Iterables, Objects } from '../system'; import { Arrays, Iterables, Objects } from '../system';
import { ExtensionContext, TreeItem, TreeItemCollapsibleState, Uri } from 'vscode'; import { ExtensionContext, TreeItem, TreeItemCollapsibleState, Uri } from 'vscode';
import { GitExplorerFilesLayout } from '../configuration';
import { ExplorerNode, ResourceType, ShowAllNode } from './explorerNode'; import { ExplorerNode, ResourceType, ShowAllNode } from './explorerNode';
import { FolderNode, IFileExplorerNode } from './folderNode';
import { GitBranch, GitLog, GitLogCommit, GitService, GitStatus, GitUri, IGitStatusFileWithCommit } from '../gitService'; import { GitBranch, GitLog, GitLogCommit, GitService, GitStatus, GitUri, IGitStatusFileWithCommit } from '../gitService';
import { StatusFileCommitsNode } from './statusFileCommitsNode'; import { StatusFileCommitsNode } from './statusFileCommitsNode';
import * as path from 'path';
export class StatusFilesNode extends ExplorerNode { export class StatusFilesNode extends ExplorerNode {
readonly repoPath: string;
readonly resourceType: ResourceType = 'gitlens:status-files'; readonly resourceType: ResourceType = 'gitlens:status-files';
maxCount: number | undefined = undefined; maxCount: number | undefined = undefined;
@@ -19,14 +23,17 @@ export class StatusFilesNode extends ExplorerNode {
public readonly branch?: GitBranch public readonly branch?: GitBranch
) { ) {
super(new GitUri(Uri.file(status.repoPath), { repoPath: status.repoPath, fileName: status.repoPath })); super(new GitUri(Uri.file(status.repoPath), { repoPath: status.repoPath, fileName: status.repoPath }));
this.repoPath = status.repoPath;
} }
async getChildren(): Promise<ExplorerNode[]> { async getChildren(): Promise<ExplorerNode[]> {
let statuses: IGitStatusFileWithCommit[] = []; let statuses: IGitStatusFileWithCommit[] = [];
const repoPath = this.repoPath;
let log: GitLog | undefined; let log: GitLog | undefined;
if (this.range !== undefined) { if (this.range !== undefined) {
log = await this.git.getLogForRepo(this.status.repoPath, this.range, this.maxCount); log = await this.git.getLogForRepo(repoPath, this.range, this.maxCount);
if (log !== undefined) { if (log !== undefined) {
statuses = Array.from(Iterables.flatMap(log.commits.values(), c => { statuses = Array.from(Iterables.flatMap(log.commits.values(), c => {
return c.fileStatuses.map(s => { return c.fileStatuses.map(s => {
@@ -38,22 +45,33 @@ export class StatusFilesNode extends ExplorerNode {
if (this.status.files.length !== 0 && this.includeWorkingTree) { if (this.status.files.length !== 0 && this.includeWorkingTree) {
statuses.splice(0, 0, ...this.status.files.map(s => { statuses.splice(0, 0, ...this.status.files.map(s => {
return { ...s, commit: new GitLogCommit('file', this.status.repoPath, GitService.uncommittedSha, s.fileName, 'You', new Date(), '', s.status, [s], s.originalFileName, 'HEAD', s.fileName) } as IGitStatusFileWithCommit; return {
...s,
commit: new GitLogCommit('file', repoPath, GitService.uncommittedSha, s.fileName, 'You', new Date(), '', s.status, [s], s.originalFileName, 'HEAD', s.fileName)
} as IGitStatusFileWithCommit;
})); }));
} }
statuses.sort((a, b) => b.commit.date.getTime() - a.commit.date.getTime()); statuses.sort((a, b) => b.commit.date.getTime() - a.commit.date.getTime());
const groups = Arrays.groupBy(statuses, s => s.fileName); const groups = Arrays.groupBy(statuses, s => s.fileName);
const children: (StatusFileCommitsNode | ShowAllNode)[] = [ let children: IFileExplorerNode[] = [
...Iterables.map(Objects.values<IGitStatusFileWithCommit[]>(groups), ...Iterables.map(Objects.values(groups), statuses => new StatusFileCommitsNode(repoPath, statuses[statuses.length - 1], statuses.map(s => s.commit), this.context, this.git, this.branch))
statuses => new StatusFileCommitsNode(this.uri.repoPath!, statuses[statuses.length - 1], statuses.map(s => s.commit), this.context, this.git, this.branch))
]; ];
children.sort((a: StatusFileCommitsNode, b: StatusFileCommitsNode) => (a.commit.isUncommitted ? -1 : 1) - (b.commit.isUncommitted ? -1 : 1) || a.label!.localeCompare(b.label!)); if (this.git.config.gitExplorer.files.layout !== GitExplorerFilesLayout.List) {
const hierarchy = Arrays.makeHierarchical(children, n => n.uri.getRelativePath().split('/'),
(...paths: string[]) => GitService.normalizePath(path.join(...paths)), this.git.config.gitExplorer.files.compact);
const root = new FolderNode(repoPath, '', undefined, hierarchy, this.git.config.gitExplorer);
children = await root.getChildren() as IFileExplorerNode[];
}
else {
children.sort((a, b) => (a.priority ? -1 : 1) - (b.priority ? -1 : 1) || a.label!.localeCompare(b.label!));
}
if (log !== undefined && log.truncated) { if (log !== undefined && log.truncated) {
children.push(new ShowAllNode('Show All Changes', this, this.context)); (children as (IFileExplorerNode | ShowAllNode)[]).push(new ShowAllNode('Show All Changes', this, this.context));
} }
return children; return children;
} }
@@ -62,7 +80,7 @@ export class StatusFilesNode extends ExplorerNode {
let files = (this.status.files !== undefined && this.includeWorkingTree) ? this.status.files.length : 0; let files = (this.status.files !== undefined && this.includeWorkingTree) ? this.status.files.length : 0;
if (this.status.upstream !== undefined) { if (this.status.upstream !== undefined) {
const stats = await this.git.getChangedFilesCount(this.status.repoPath, `${this.status.upstream}...`); const stats = await this.git.getChangedFilesCount(this.repoPath, `${this.status.upstream}...`);
if (stats !== undefined) { if (stats !== undefined) {
files += stats.files; files += stats.files;
} }
@@ -82,5 +100,4 @@ export class StatusFilesNode extends ExplorerNode {
private get includeWorkingTree(): boolean { private get includeWorkingTree(): boolean {
return this.git.config.gitExplorer.includeWorkingTree; return this.git.config.gitExplorer.includeWorkingTree;
} }
} }