Compare commits
56 Commits
v5.0.0-bet
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 3684629c9b | |||
|
|
65a3b31ca5 | ||
|
|
4694fbc1ae | ||
|
|
b56d101f76 | ||
|
|
ce9394297d | ||
|
|
4d18bf708d | ||
|
|
c44e4c6968 | ||
|
|
c5f57172e4 | ||
|
|
dd0a636e24 | ||
|
|
393ec351f0 | ||
|
|
99d6da9c90 | ||
|
|
0eb202b8ae | ||
|
|
65736a6ce7 | ||
|
|
ed42eba8b8 | ||
|
|
2245d82319 | ||
|
|
f7df845dfe | ||
|
|
712544fab8 | ||
|
|
a114e2de87 | ||
|
|
70071448d6 | ||
|
|
a10376385a | ||
|
|
41d25803d8 | ||
|
|
3802b43027 | ||
|
|
04ea3b7971 | ||
|
|
6d7f44e091 | ||
|
|
3a1caa2e0d | ||
|
|
3f7058bd48 | ||
|
|
71d17bcc2f | ||
|
|
a69afdb6ef | ||
|
|
26c6346b84 | ||
|
|
3a17605017 | ||
|
|
2c9a26e47b | ||
|
|
1c7785fd52 | ||
|
|
079f7b7f36 | ||
|
|
bedc1a05f5 | ||
|
|
858d9ec578 | ||
|
|
2809991096 | ||
|
|
f6019454b6 | ||
|
|
f0bdf3e2c3 | ||
|
|
0fdf856c27 | ||
|
|
aacf7cc2b5 | ||
|
|
11eacb27a1 | ||
|
|
543d39246f | ||
|
|
6837414f22 | ||
|
|
ea6fdbaaf2 | ||
|
|
ccc29e3dfc | ||
|
|
48814d4213 | ||
|
|
c3dd83cf3c | ||
|
|
77482f4930 | ||
|
|
503b2a3785 | ||
|
|
9464f7e79f | ||
|
|
77ae37c54c | ||
|
|
e20ec552b7 | ||
|
|
f911447c5e | ||
|
|
38c44c808d | ||
|
|
655afb358e | ||
|
|
21e0963600 |
107
CHANGELOG.md
@@ -4,13 +4,68 @@ 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/).
|
||||
|
||||
## [5.0.0-beta] - 2017-09-11
|
||||
## [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
|
||||
### Added
|
||||
- Adds new `Changed Files` node to the `Repository Status` node of the `GitLens` custom view's `Repository View` -- closes [#139](https://github.com/eamodio/vscode-gitlens/issues/139)
|
||||
- Provides a at-a-glance view of all "working" changes
|
||||
- Expands to a file-based view of all changed files in the working tree (enabled via `"gitlens.insiders": true`) and/or all files in all commits ahead of the upstream
|
||||
- Adds optional (on by default) working tree status information to the `Repository Status` node in the `GitLens` custom view
|
||||
- Adds `auto` value to `gitlens.gitExplorer.view` setting - closes [#150](https://github.com/eamodio/vscode-gitlens/issues/150)
|
||||
- Adds `gitlens.gitExplorer.enabled` setting to specify whether or not to show the `GitLens` custom view - closes [#144](https://github.com/eamodio/vscode-gitlens/issues/144)
|
||||
- Adds `gitlens.gitExplorer.includeWorkingTree` setting to specify whether or not to include working tree files inside the `Repository Status` node of the `GitLens` custom view
|
||||
- Adds `gitlens.gitExplorer.statusFileFormat` setting to the format of the status of a working or committed file in the `GitLens` custom view
|
||||
|
||||
### Changed
|
||||
- Changes the sorting (now alphabetical) of files shown in the `GitLens` custom view
|
||||
- Changes the default of the `gitlens.gitExplorer.view` setting to `auto`
|
||||
- Changes the default of the `gitlens.gitExplorer.commitFormat` setting to add parentheses around the commit id
|
||||
- Removes many menu items from `editor/title` & `editor/title/context` by default -- can be re-enabled via the `gitlens.advanced.menus` setting
|
||||
|
||||
### Fixed
|
||||
- Fixes [#146](https://github.com/eamodio/vscode-gitlens/issues/146) - Blame gutter annotation issue when commit contains emoji
|
||||
- Fixes an issue when running `Open File in Remote` with a multi-line selection wasn't properly opening the selection in GitLab -- thanks to [PR #145](https://github.com/eamodio/vscode-gitlens/pull/145) by Amanda Cameron ([@AmandaCameron](https://github.com/AmandaCameron))!
|
||||
- Fixes an issue where the `gitlens.advanced.menus` setting wasn't controlling all the menu items properly
|
||||
|
||||
## [5.1.0] - 2017-09-15
|
||||
### Added
|
||||
- Adds full (multi-line) commit message to the `details` hover annotations -- closes [#116](https://github.com/eamodio/vscode-gitlens/issues/116)
|
||||
- Adds an external link icon to the `details` hover annotations to run the `Open Commit in Remote` command (`gitlens.openCommitInRemote`)
|
||||
|
||||
### Changed
|
||||
- Optimizes performance of the providing blame annotations, especially for large files (saw a ~78% improvement on some files)
|
||||
- Optimizes date handling (parsing and formatting) for better performance and reduced memory consumption
|
||||
|
||||
### Removed
|
||||
- Removes `gitlens.annotations.file.recentChanges.hover.wholeLine` setting as it didn't really make sense
|
||||
|
||||
### Fixed
|
||||
- Fixes an issue where stashes with only untracked files would not show in the `Stashes` node of the GitLens custom view
|
||||
- Fixes an issue where stashes with untracked files would not show its untracked files in the GitLens custom view
|
||||
|
||||
## [5.0.0] - 2017-09-12
|
||||
### Added
|
||||
- Adds an all-new `GitLens` custom view to the Explorer activity
|
||||
|
||||
- `Repository View` - provides a full repository explorer
|
||||
|
||||

|
||||

|
||||
|
||||
- `Repository Status` node — provides the status of the repository
|
||||
- Provides the name of the current branch, its upstream tracking branch (if available), and its upstream status (if available)
|
||||
@@ -26,29 +81,30 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p
|
||||
- Indicates which branch is the current branch and optionally shows the remote tracking branch
|
||||
- Expand each branch to easily see its revision (commit) history
|
||||
- Expand each revision (commit) to quickly see the set of files changed, complete with status indicators for adds, changes, renames, and deletes
|
||||
- Provides a context menu on each changed file with `Open Changes`, `Open Changes with Working Tree`, `Open File`, `Open Revision`, `Open File in Remote`, `Open Revision in Remote`, `Apply Changes`, and `Show Commit File Details` commands
|
||||
- Provides a context menu on each revision (commit) with `Open Commit in Remote`, `Copy Commit ID to Clipboard`, `Copy Commit Message to Clipboard`, `Open Files`, `Open Revisions`, `Show Commit Details`, and `Refresh` commands
|
||||
- Provides a context menu on each changed file with `Open Changes`, `Open Changes with Working Tree`, `Open File`, `Open Revision`, `Open File in Remote`, `Open Revision in Remote`, `Apply Changes`, `Show File History`, and `Show Commit File Details` commands
|
||||
- Provides a context menu on each revision (commit) with `Open Commit in Remote`, `Open All Changes`, `Open All Changes with Working Tree`, `Open Files`, `Open Revisions`, `Copy Commit ID to Clipboard`, `Copy Commit Message to Clipboard`, `Show Commit Details`, and `Refresh` commands
|
||||
- Provides a context menu on each branch with `Open Branch in Remote`, and `Refresh` commands
|
||||
- Provides a context menu with `Open Branches in Remote`, and `Refresh` commands
|
||||
|
||||
- `Remotes` node — provides a list of remotes
|
||||
- Indicates the direction of the remote (fetch, push, both), remote service (if applicable), and repository path
|
||||
- Expand each remote to see its list of branches
|
||||
- Expand each branch to easily see its revision (commit) history
|
||||
- Expand each revision (commit) to quickly see the set of files changed, complete with status indicators for adds, changes, renames, and deletes
|
||||
- Provides a context menu on each changed file with `Open Changes`, `Open Changes with Working Tree`, `Open File`, `Open Revision`, `Open File in Remote`, `Open Revision in Remote`, `Apply Changes`, and `Show Commit File Details` commands
|
||||
- Provides a context menu on each revision (commit) with `Open Commit in Remote`, `Copy Commit ID to Clipboard`, `Copy Commit Message to Clipboard`, `Open Files`, `Open Revisions`, `Show Commit Details`, and `Refresh` commands
|
||||
- Provides a context menu on each revision (commit) with `Open Commit in Remote`, `Open All Changes`, `Open All Changes with Working Tree`, `Open Files`, `Open Revisions`, `Copy Commit ID to Clipboard`, `Copy Commit Message to Clipboard`,`Show Commit Details`, and `Refresh` commands
|
||||
- Provides a context menu on each remote with `Open Branches in Remote`, `Open Repository in Remote`, and `Refresh` commands
|
||||
- Provides a context menu with a `Refresh` command
|
||||
|
||||
- `Stashes` node — provides a list of stashed changes
|
||||
- Expand each stash to quickly see the set of files stashed, complete with status indicators for adds, changes, renames, and deletes
|
||||
- Provides a context menu with `Stash Changes`, and `Refresh` commands
|
||||
- Provides a context menu on each stash with `Apply Stashed Changes` (confirmation required), `Delete Stashed Changes` (confirmation required), `Copy Commit Message to Clipboard`, `Open Files`, `Open Revisions`, and `Refresh` commands
|
||||
- Provides a context menu on each stashed file with `Open Changes`, `Open Changes with Working Tree`, `Open File`, `Open Revision`, `Open File in Remote`, `Apply Changes`, and `Show File History` commands
|
||||
- Provides a context menu on each stash with `Apply Stashed Changes` (confirmation required), `Delete Stashed Changes` (confirmation required), `Open All Changes`, `Open All Changes with Working Tree`, `Open Files`, `Open Revisions`, `Copy Commit Message to Clipboard`, and `Refresh` commands
|
||||
- Provides a context menu on each stashed file with `Apply Changes`, `Open Changes`, `Open Changes with Working Tree`, `Open File`, `Open Revision`, `Open File in Remote`, and `Show File History` commands
|
||||
|
||||
- `History View` - provides the revision history of the active file
|
||||
|
||||

|
||||

|
||||
|
||||
- Automatically updates to track the active editor
|
||||
- Provides a context menu with `Open File`, `Open File in Remote`, and `Refresh` commands
|
||||
@@ -57,12 +113,19 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p
|
||||
- Quickly switch between views using the `Switch to Repository View` or `Switch to History View` commands
|
||||
- Provides toolbar commands to `Search Commits`, `Switch to Repository View` or `Switch to History View`, and `Refresh`
|
||||
|
||||
- Adds command-links to the `details` hover annotation
|
||||
- Clicking the commit id will run the `Show Commit Details` command (`gitlens.showQuickCommitDetails`)
|
||||
- Adds command-links to the `changes` hover annotation
|
||||
- Clicking on `Changes` will run the `Compare File Revisions` command (`gitlens.diffWith`)
|
||||
- Clicking the current and previous commit ids will run the `Show Commit Details` command (`gitlens.showQuickCommitDetails`)
|
||||
- Adds support for custom remote services - see [#120](https://github.com/eamodio/vscode-gitlens/issues/120)
|
||||
- Adds all-new interactivity to the hover annotations
|
||||
|
||||

|
||||
|
||||
- Adds the following command-links to the `details` hover annotation
|
||||
- Clicking the commit id will run the `Show Commit Details` command (`gitlens.showQuickCommitDetails`)
|
||||
- Adds the following command-links to the `changes` hover annotation
|
||||
- Clicking on `Changes` will run the `Compare File Revisions` command (`gitlens.diffWith`)
|
||||
- Clicking the current and previous commit ids will run the `Show Commit Details` command (`gitlens.showQuickCommitDetails`)
|
||||
|
||||
- Adds support for remote services with custom domains -- closes [#120](https://github.com/eamodio/vscode-gitlens/issues/120)
|
||||
- Adds support for the Bitbucket Server (previously called Stash) remote service -- closes [#120](https://github.com/eamodio/vscode-gitlens/issues/120)
|
||||
- Adds `gitlens.blame.ignoreWhitespace` setting to specify whether or not to ignore whitespace when comparing revisions during blame operations -- closes [#138](https://github.com/eamodio/vscode-gitlens/issues/138)
|
||||
- Adds `Compare File Revisions` command (`gitlens.diffWith`) - compares the specified file revisions
|
||||
- Adds `Open Branches in Remote` command (`gitlens.openBranchesInRemote`) - opens the branches in the supported remote service
|
||||
- Adds `Stash Changes` command (`gitlens.stashSave`) to the source control group context menu -- can now stash a group of files
|
||||
@@ -92,10 +155,11 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p
|
||||
- Removes the seeding of the commit search command from the clipboard
|
||||
|
||||
### Fixed
|
||||
- Fixes an issue where double hover annotations could be shown on blank lines
|
||||
- Fixes an issue where remote branches couldn't be opened properly in their remote service
|
||||
- Fixes [#130](https://github.com/eamodio/vscode-gitlens/issues/130) - First-run "Thank you for choosing GitLens! [...]" info message shown on every start up
|
||||
- Fixes [#120](https://github.com/eamodio/vscode-gitlens/issues/120) - Feature Request: "Open in Remote" support for custom repositories
|
||||
- Fixes an issue where sometimes diffs (via branch name) wouldn't open properly
|
||||
- Fixes an issue where remotes are queried more than once on startup
|
||||
|
||||
## [4.4.3] - 2017-08-30
|
||||
## Fixed
|
||||
@@ -156,9 +220,6 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p
|
||||
## [4.3.0] - 2017-07-03
|
||||
## Added
|
||||
- Adds `Git Stashes` custom view to the Explorer activity
|
||||
|
||||

|
||||
|
||||
- Shows all of the stashed changes in the repository
|
||||
- Provides toolbar buttons to `Stash Changes` and `Refresh`
|
||||
- Provides a context menu with `Apply Stashed Changes` and `Delete Stashed Changes` commands - both require a confirmation
|
||||
@@ -221,7 +282,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p
|
||||
|
||||
### Fixed
|
||||
- Fixes excessive memory usage when parsing diffs
|
||||
- Fixes extra newline in multiline commit messages
|
||||
- Fixes extra newline in multi-line commit messages
|
||||
- Fixes (again) [#33](https://github.com/eamodio/vscode-gitlens/issues/33) - Commit messages can causes markdown formatting in hovers
|
||||
|
||||
## [4.0.1] - 2017-06-09
|
||||
@@ -305,7 +366,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p
|
||||
### Added
|
||||
- Improves performance
|
||||
- Reduces the number of git calls on known "untrackables"
|
||||
- Caches many more git commands to reduce git command roundtrips and parsing
|
||||
- Caches many more git commands to reduce git command round-trips and parsing
|
||||
- Increases the debounce (delay) on cursor movement to reduce lag when navigating around a file
|
||||
- Adds diff information (the line's previous version) 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 [PR #72](https://github.com/eamodio/vscode-gitlens/pull/72) by Zack Schuster ([@zackschuster](https://github.com/zackschuster))!
|
||||
@@ -496,7 +557,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p
|
||||
### 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 [#30](https://github.com/eamodio/vscode-gitlens/issues/30) - Diff with Working Tree fails from repo/commit quick pick 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
|
||||
@@ -599,7 +660,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p
|
||||
|
||||
## [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
|
||||
- To accommodate 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
|
||||
@@ -637,7 +698,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p
|
||||
|
||||
### 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 some issue where some editors opened by the quick pick 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
|
||||
|
||||
46
CODE_OF_CONDUCT.md
Normal file
@@ -0,0 +1,46 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at eamodio@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
|
||||
|
||||
[homepage]: http://contributor-covenant.org
|
||||
[version]: http://contributor-covenant.org/version/1/4/
|
||||
860
README.md
@@ -1,421 +1,439 @@
|
||||
[](https://marketplace.visualstudio.com/items?itemName=eamodio.gitlens)
|
||||
[](https://marketplace.visualstudio.com/items?itemName=eamodio.gitlens)
|
||||
[](https://marketplace.visualstudio.com/items?itemName=eamodio.gitlens)
|
||||
[](https://join.slack.com/t/vscode-gitlens/shared_invite/MjIxOTgxNDE3NzM0LTE1MDE2Nzk1MTgtMjkwMmZjMzcxNQ)
|
||||
|
||||
# GitLens
|
||||
|
||||
GitLens **supercharges** the built-in Visual Studio Code Git capabilities. It helps you to **visualize code authorship** at a glance via Git blame annotations and code lens, **seamlessly navigate and explore** the history of a file or branch, **gain valuable insights** via powerful comparison commands, and so much more.
|
||||
|
||||
GitLens provides an unobtrusive blame annotation at the end of the current line, a status bar item showing the commit information (author and date, by default) of the current 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 current line blame annotation distracting — no problem, it is easy to [turn them off or change how they behave](#extension-settings).
|
||||
|
||||
### Preview — featuring blame annotations, code lens, status bar details, quick pick menus for navigation and exploration, compare with previous, and more
|
||||

|
||||
|
||||
## Features
|
||||
|
||||
### Git Blame Annotations
|
||||
|
||||
- Adds an unobtrusive, highly [customizable](#line-blame-annotation-settings) and [themeable](#theme-settings), **Git blame annotation** to the end of the current line ([optional](#line-blame-annotation-settings), on by default)
|
||||
|
||||

|
||||
- Contains the author, date, and message of the line's most recent commit, by [default](#line-blame-annotation-settings)
|
||||
- Adds a `details` hover annotation to the current line annotation, which provides more commit details ([optional](#line-blame-annotation-settings), on by default)
|
||||
- Clicking the commit id will run the `Show Commit Details` command (`gitlens.showQuickCommitDetails`)
|
||||
- Adds a `changes` (diff) hover annotation to the current line annotation, which provides **instant** access to the line's previous version ([optional](#line-blame-annotation-settings), on by default)
|
||||
- Clicking on `Changes` will run the `Compare File Revisions` command (`gitlens.diffWith`)
|
||||
- Clicking the current and previous commit ids will run the `Show Commit Details` command (`gitlens.showQuickCommitDetails`)
|
||||
|
||||

|
||||
|
||||
- Adds on-demand, beautiful, highly [customizable](#file-blame-annotation-settings) and [themeable](#theme-settings), **Git blame annotations** of the whole file
|
||||
|
||||

|
||||
- Choose between `gutter` (default) and `hover` [annotation styles](#file-blame-annotation-settings)
|
||||
- Contains the commit message and date, by [default](#file-blame-annotation-settings)
|
||||
- Adds a `details` hover annotation to the line's annotation, which provides more commit details ([optional](#file-blame-annotation-settings), on by default)
|
||||
- Clicking the commit id will run the `Show Commit Details` command (`gitlens.showQuickCommitDetails`)
|
||||
- Adds a `heatmap` (age) indicator to the gutter annotations (on right edge by [default](#file-blame-annotation-settings)), which provides an easy, at-a-glance way to tell the age of a line ([optional](#file-blame-annotation-settings), on by default)
|
||||
- Indicator ranges from bright yellow (newer) to dark brown (older)
|
||||
- Press `Escape` to quickly toggle the annotations off
|
||||
|
||||
- Adds [customizable](#status-bar-settings) **blame information** about the current line to the **status bar** ([optional](#status-bar-settings), on by default)
|
||||
|
||||

|
||||
- Contains the commit author and date, by [default](#status-bar-settings)
|
||||
- Clicking the status bar item will, by [default](#status-bar-settings), show a **commit details quick pick menu** with commands for comparing, navigating and exploring commits, and more
|
||||
- Provides [customizable](#status-bar-settings) click behavior — choose between one of the following
|
||||
- Toggle file blame annotations on and off
|
||||
- Toggle code lens on and off
|
||||
- Compare the line commit with the previous commit
|
||||
- Compare the line commit with the working tree
|
||||
- Show a quick pick menu with details and commands for the commit (default)
|
||||
- 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 File Blame Annotations` command (`gitlens.toggleFileBlame`) with a shortcut of `alt+b` to toggle the file blame annotations on and off
|
||||
- Also adds a `Show File Blame Annotations` command (`gitlens.showFileBlame`)
|
||||
|
||||
- Adds a `Toggle Line Blame Annotations` command (`gitlens.toggleLineBlame`) to toggle the current line blame annotations on and off
|
||||
- Also adds a `Show Line Blame Annotations` command (`gitlens.showLineBlame`)
|
||||
|
||||
### Git Recent Changes Annotations
|
||||
|
||||
- Adds on-demand, [customizable](#file-recent-changes-annotation-settings) and [themeable](#theme-settings), **recent changes annotations** of the whole file
|
||||
- Highlights all of lines changed in the most recent commit
|
||||
- Adds a `details` hover annotation to each line, which provides more commit details ([optional](#file-blame-annotation-settings), on by default)
|
||||
- Clicking the commit id will run the `Show Commit Details` command (`gitlens.showQuickCommitDetails`)
|
||||
- Adds a `changes` (diff) hover annotation to each line, which provides **instant** access to the line's previous version ([optional](#file-recent-changes-annotation-settings), on by default)
|
||||
- Clicking on `Changes` will run the `Compare File Revisions` command (`gitlens.diffWith`)
|
||||
- Clicking the current and previous commit ids will run the `Show Commit Details` command (`gitlens.showQuickCommitDetails`)
|
||||
- Press `Escape` to quickly toggle the annotations off
|
||||
|
||||
- Adds `Toggle Recent File Changes Annotations` command (`gitlens.toggleFileRecentChanges`) to toggle the recent changes annotations on and off
|
||||
|
||||
### Git Code Lens
|
||||
|
||||
- Adds **code lens** to the top of the file and on code blocks ([optional](#code-lens-settings), on by default)
|
||||
|
||||

|
||||
- **Recent Change** — author and date of the most recent commit for the file or code block
|
||||
- Clicking the code lens will, by [default](#code-lens-settings), show 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)
|
||||
- Clicking the code lens will, by [default](#code-lens-settings), toggle the file Git blame annotations on and off of 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](#code-lens-settings) click behavior for each code lens — choose between one of the following
|
||||
- Toggle file blame annotations on and off
|
||||
- Compare the commit 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
|
||||
|
||||
### Powerful Comparison Tools
|
||||
|
||||
- Effortlessly navigate between comparisons 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 Branch...` command (`gitlens.diffWithBranch`) to compare the active file with the same file on the selected branch
|
||||
|
||||
- Adds a `Compare File with Next Revision` 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 Revision` command (`gitlens.diffWithPrevious`) with a shortcut of `alt+,` to compare the active file/diff with the previous commit revision
|
||||
|
||||
- Adds a `Compare Line Revision with Previous` 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 Revision...` command (`gitlens.diffWithRevision`) to compare the active file with the selected revision of the same file
|
||||
|
||||
- Adds a `Compare File with Working Revision` 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 Revision with Working` 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 [customizable](#gitlens-custom-view-settings) `GitLens` custom view to the Explorer activity
|
||||
|
||||
- `Repository View` - provides a full repository explorer
|
||||
|
||||

|
||||
|
||||
- `Repository Status` node — provides the status of the repository
|
||||
- Provides the name of the current branch, its upstream tracking branch (if available), and its upstream status (if available)
|
||||
- Provides indicator dots on the repository icon which denote the following:
|
||||
- `None` - up-to-date with the upstream
|
||||
- `Green` - ahead of the upstream
|
||||
- `Red` - behind the upstream
|
||||
- `Yellow` - both ahead of and behind the upstream
|
||||
- Provides additional nodes, if the current branch is not synchronized with the upstream, to quickly see and explore the specific commits ahead and/or behind the upstream
|
||||
- Provides a context menu with `Open Repository in Remote`, and `Refresh` commands
|
||||
|
||||
- `Branches` node — provides a list of the local branches
|
||||
- Indicates which branch is the current branch and [optionally](#gitlens-custom-view-settings) shows the remote tracking branch
|
||||
- Expand each branch to easily see its revision (commit) history
|
||||
- Expand each revision (commit) to quickly see the set of files changed, complete with status indicators for adds, changes, renames, and deletes
|
||||
- Provides a context menu on each changed file with `Open Changes`, `Open Changes with Working Tree`, `Open File`, `Open Revision`, `Open File in Remote`, `Open Revision in Remote`, `Apply Changes`, and `Show Commit File Details` commands
|
||||
- Provides a context menu on each revision (commit) with `Open Commit in Remote`, `Copy Commit ID to Clipboard`, `Copy Commit Message to Clipboard`, `Open Files`, `Open Revisions`, `Show Commit Details`, and `Refresh` commands
|
||||
- Provides a context menu on each branch with `Open Branch in Remote`, and `Refresh` commands
|
||||
- Provides a context menu with `Open Branches in Remote`, and `Refresh` commands
|
||||
|
||||
- `Remotes` node — provides a list of remotes
|
||||
- Expand each remote to see its list of branches
|
||||
- Expand each branch to easily see its revision (commit) history
|
||||
- Expand each revision (commit) to quickly see the set of files changed, complete with status indicators for adds, changes, renames, and deletes
|
||||
- Provides a context menu on each changed file with `Open Changes`, `Open Changes with Working Tree`, `Open File`, `Open Revision`, `Open File in Remote`, `Open Revision in Remote`, `Apply Changes`, and `Show Commit File Details` commands
|
||||
- Provides a context menu on each revision (commit) with `Open Commit in Remote`, `Copy Commit ID to Clipboard`, `Copy Commit Message to Clipboard`, `Open Files`, `Open Revisions`, `Show Commit Details`, and `Refresh` commands
|
||||
- Provides a context menu on each remote with `Open Branches in Remote`, `Open Repository in Remote`, and `Refresh` commands
|
||||
- Provides a context menu with a `Refresh` command
|
||||
|
||||
- `Stashes` node — provides a list of stashed changes
|
||||
- Expand each stash to quickly see the set of files stashed, complete with status indicators for adds, changes, renames, and deletes
|
||||
- Provides a context menu with `Stash Changes`, and `Refresh` commands
|
||||
- Provides a context menu on each stash with `Apply Stashed Changes` (confirmation required), `Delete Stashed Changes` (confirmation required), `Copy Commit Message to Clipboard`, `Open Files`, `Open Revisions`, and `Refresh` commands
|
||||
- Provides a context menu on each stashed file with `Open Changes`, `Open Changes with Working Tree`, `Open File`, `Open Revision`, `Open File in Remote`, `Apply Changes`, and `Show File History` commands
|
||||
|
||||
- `History View` - provides the revision history of the active file
|
||||
|
||||

|
||||
|
||||
- Automatically updates to track the active editor
|
||||
- Provides a context menu with `Open File`, `Open File in Remote`, and `Refresh` commands
|
||||
- Provides a context menu on each revision (commit) with `Open Changes`, `Open Changes with Working Tree`, `Open File`, `Open Revision`, `Open File in Remote`, `Open Revision in Remote`, `Apply Changes`, and `Show Commit File Details` commands
|
||||
|
||||
- Quickly switch between views using the `Switch to Repository View` or `Switch to History View` commands
|
||||
- Provides toolbar commands to `Search Commits`, `Switch to Repository View` or `Switch to History View`, and `Refresh`
|
||||
|
||||
- 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 Branches in Remote` command (`gitlens.openBranchesInRemote`) — opens the branches in the supported remote service
|
||||
- `Open Branch in Remote` command (`gitlens.openBranchInRemote`) — opens the current branch commits in the supported remote service
|
||||
- `Open 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 comparison of the current revision with the previous one
|
||||
|
||||
- Adds a `Show Commit File 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 File 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. Choosing 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. Choosing 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 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
|
||||
|
||||

|
||||
|
||||
- Provides entries to `Stash Changes`
|
||||
- Navigate back to the previous quick pick menu via `alt+left arrow`, if available
|
||||
|
||||
- Choosing 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`
|
||||
- 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 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 or from the most recent commit to the current branch, if there is no active editor
|
||||
|
||||
- Adds a `Copy Commit Message to Clipboard` command (`gitlens.copyMessageToClipboard`) to copy the commit message of the active line to the clipboard or from the most recent commit to the current branch, if there is no active editor
|
||||
|
||||
- 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
|
||||
|
||||
- Adds a `Apply Stashed Changes` command (`gitlens.stashApply`) to chose a stash entry to apply to the working tree from a quick pick menu
|
||||
|
||||
- Adds a `Stash Changes` command (`gitlens.stashSave`) to save any working tree changes to the stash — can optionally provide a stash message
|
||||
- Also adds the command to the Source Control items context menu to stash an individual or group of files, works with multi-select too!
|
||||
|
||||
## Insiders
|
||||
|
||||
Add [`"gitlens.insiders": true`](#general-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
|
||||
|
||||
### General Settings
|
||||
|
||||
|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.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
|
||||
|
||||
### Blame Annotation Settings
|
||||
|
||||
#### File Blame Annotation Settings
|
||||
|
||||
|Name | Description
|
||||
|-----|------------
|
||||
|`gitlens.blame.file.annotationType`|Specifies the type of blame annotations that will be shown for the current file<br />`gutter` - adds an annotation to the beginning of each line<br />`hover` - shows annotations when hovering over each line
|
||||
|`gitlens.blame.file.lineHighlight.enabled`|Specifies whether or not to highlight lines associated with the current line
|
||||
|`gitlens.blame.file.lineHighlight.locations`|Specifies where the associated line highlights will be shown<br />`gutter` - adds a gutter glyph<br />`line` - adds a full-line highlight background color<br />`overviewRuler` - adds a decoration to the overviewRuler (scroll bar)
|
||||
|`gitlens.annotations.file.gutter.format`|Specifies the format of the gutter blame annotations<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.annotations.file.gutter.dateFormat`)<br />`${authorAgo}` - commit author, relative commit date<br />See https://github.com/eamodio/vscode-gitlens/wiki/Advanced-Formatting for advanced formatting
|
||||
|`gitlens.annotations.file.gutter.dateFormat`|Specifies how to format absolute dates (using the `${date}` token) in gutter blame annotations<br />See https://momentjs.com/docs/#/displaying/format/ for valid formats
|
||||
|`gitlens.annotations.file.gutter.compact`|Specifies whether or not to compact (deduplicate) matching adjacent gutter blame annotations
|
||||
|`gitlens.annotations.file.gutter.heatmap.enabled`|Specifies whether or not to provide a heatmap indicator in the gutter blame annotations
|
||||
|`gitlens.annotations.file.gutter.heatmap.location`|Specifies where the heatmap indicators will be shown in the gutter blame annotations<br />`left` - adds a heatmap indicator on the left edge of the gutter blame annotations<br />`right` - adds a heatmap indicator on the right edge of the gutter blame annotations
|
||||
|`gitlens.annotations.file.gutter.hover.details`|Specifies whether or not to provide a commit details hover annotation over the gutter blame annotations
|
||||
|`gitlens.annotations.file.gutter.hover.wholeLine`|Specifies whether or not to trigger hover annotations over the whole line
|
||||
|`gitlens.annotations.file.hover.heatmap.enabled`|Specifies whether or not to provide heatmap indicators on the left edge of each line
|
||||
|`gitlens.annotations.file.hover.wholeLine`|Specifies whether or not to trigger hover annotations over the whole line
|
||||
|
||||
#### Line Blame Annotation Settings
|
||||
|
||||
|Name | Description
|
||||
|-----|------------
|
||||
|`gitlens.blame.line.enabled`|Specifies whether or not to provide a blame annotation for the current line, by default<br />Use the `gitlens.toggleLineBlame` command to toggle the annotations on and off for the current session
|
||||
|`gitlens.blame.line.annotationType`|Specifies the type of blame annotations that will be shown for the current line<br />`trailing` - adds an annotation to the end of the current line<br />`hover` - shows annotations when hovering over the current line
|
||||
|`gitlens.annotations.line.trailing.format`|Specifies the format of the trailing blame annotations<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.annotations.line.trailing.dateFormat`)<br />`${authorAgo}` - commit author, relative commit date<br />See https://github.com/eamodio/vscode-gitlens/wiki/Advanced-Formatting for advanced formatting
|
||||
|`gitlens.annotations.line.trailing.dateFormat`|Specifies how to format absolute dates (using the `${date}` token) in trailing blame annotations<br />See https://momentjs.com/docs/#/displaying/format/ for valid formats
|
||||
|`gitlens.annotations.line.trailing.hover.details`|Specifies whether or not to provide a commit details hover annotation over the trailing blame annotations
|
||||
|`gitlens.annotations.line.trailing.hover.changes`|Specifies whether or not to provide a changes (diff) hover annotation over the trailing blame annotations
|
||||
|`gitlens.annotations.line.trailing.hover.wholeLine`|Specifies whether or not to trigger hover annotations over the whole line
|
||||
|`gitlens.annotations.line.hover.details`|Specifies whether or not to provide a commit details hover annotation for the current line
|
||||
|`gitlens.annotations.line.hover.changes`|Specifies whether or not to provide a changes (diff) hover annotation for the current line
|
||||
|
||||
### File Recent Changes Annotation Settings
|
||||
|
||||
|Name | Description
|
||||
|-----|------------
|
||||
|`gitlens.recentChanges.file.lineHighlight.locations`|Specifies where the highlights of the recently changed lines will be shown<br />`gutter` - adds a gutter glyph<br />`line` - adds a full-line highlight background color<br />`overviewRuler` - adds a decoration to the overviewRuler (scroll bar)
|
||||
|`gitlens.annotations.file.recentChanges.hover.details`|Specifies whether or not to provide a commit details hover annotation
|
||||
|`gitlens.annotations.file.recentChanges.hover.changes`|Specifies whether or not to provide a changes (diff) hover annotation
|
||||
|`gitlens.annotations.file.recentChanges.hover.wholeLine`|Specifies whether or not to trigger hover annotations over the whole line
|
||||
|
||||
### Code Lens Settings
|
||||
|
||||
|Name | Description
|
||||
|-----|------------
|
||||
|`gitlens.codeLens.enabled`|Specifies whether or not to provide any Git code lens, by default<br />Use the `gitlens.toggleCodeLens` command to toggle the Git code lens on and off for the current session
|
||||
|`gitlens.codeLens.recentChange.enabled`|Specifies whether or not to show a `recent change` code lens showing the author and date of the most recent commit for the file or code block
|
||||
|`gitlens.codeLens.recentChange.command`|Specifies the command to be executed when the `recent change` code lens is clicked<br />`gitlens.toggleFileBlame` - toggles file blame annotations<br />`gitlens.showBlameHistory` - opens the blame history explorer<br />`gitlens.showFileHistory` - opens the file history explorer<br />`gitlens.diffWithPrevious` - compares the current committed file with the previous commit<br />`gitlens.showQuickCommitDetails` - shows a commit details quick pick<br />`gitlens.showQuickCommitFileDetails` - shows a commit file details quick pick<br />`gitlens.showQuickFileHistory` - shows a file history quick pick<br />`gitlens.showQuickRepoHistory` - shows a branch history quick pick
|
||||
|`gitlens.codeLens.authors.enabled`|Specifies whether or not to show an `authors` code lens showing number of authors of the file or code block and the most prominent author (if there is more than one)
|
||||
|`gitlens.codeLens.authors.command`|Specifies the command to be executed when the `authors` code lens is clicked<br />`gitlens.toggleFileBlame` - toggles file blame annotations<br />`gitlens.showBlameHistory` - opens the blame history explorer<br />`gitlens.showFileHistory` - opens the file history explorer<br />`gitlens.diffWithPrevious` - compares the current committed file with the previous commit<br />`gitlens.showQuickCommitDetails` - shows a commit details quick pick<br />`gitlens.showQuickCommitFileDetails` - shows a commit file details quick pick<br />`gitlens.showQuickFileHistory` - shows a file history quick pick<br />`gitlens.showQuickRepoHistory` - shows a branch history quick pick
|
||||
|`gitlens.codeLens.locations`|Specifies where Git code lens will be shown in the document<br />`document` - adds code lens at the top of the document<br />`containers` - adds code lens at the start of container-like symbols (modules, classes, interfaces, etc)<br />`blocks` - adds code lens at the start of block-like symbols (functions, methods, properties, etc) lines<br />`custom` - adds code lens at the start of symbols contained in `gitlens.codeLens.locationCustomSymbols`
|
||||
|`gitlens.codeLens.customLocationSymbols`|Specifies the set of document symbols where Git code lens will be shown in the document
|
||||
|`gitlens.codeLens.perLanguageLocations`|Specifies where Git code lens will be shown in the document for the specified languages
|
||||
|
||||
### GitLens Custom View Settings
|
||||
|
||||
|Name | Description
|
||||
|-----|------------
|
||||
|`gitlens.gitExplorer.view`|Specifies the starting view (mode) of the `GitLens` custom view<br />`history` - shows the commit history of the active file<br />`repository` - shows a repository explorer"
|
||||
|`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.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.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 Custom Remotes Settings
|
||||
|
||||
|Name | Description
|
||||
|-----|------------
|
||||
|`gitlens.remotes`|Specifies the custom remote services (code-hosting)"
|
||||
|
||||
### Status Bar Settings
|
||||
|
||||
|Name | Description
|
||||
|-----|------------
|
||||
|`gitlens.statusBar.enabled`|Specifies whether or not to provide blame information on the status bar
|
||||
|`gitlens.statusBar.alignment`|Specifies the blame alignment in the status bar<br />`left` - align to the left, `right` - align to the right
|
||||
|`gitlens.statusBar.command`|Specifies the command to be executed when the blame status bar item is clicked<br />`gitlens.toggleFileBlame` - toggles file blame annotations<br />`gitlens.showBlameHistory` - opens the blame history explorer<br />`gitlens.showFileHistory` - opens the file history explorer<br />`gitlens.diffWithPrevious` - compares the current line commit with the previous<br />`gitlens.diffWithWorking` - compares the current line commit with the working tree<br />`gitlens.toggleCodeLens` - toggles Git code lens<br />`gitlens.showQuickCommitDetails` - shows a commit details quick pick<br />`gitlens.showQuickCommitFileDetails` - shows a commit file details quick pick<br />`gitlens.showQuickFileHistory` - shows a file history quick pick<br />`gitlens.showQuickRepoHistory` - shows a branch history quick pick
|
||||
|`gitlens.statusBar.format`|Specifies the format of the blame information on the status bar<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 />See https://github.com/eamodio/vscode-gitlens/wiki/Advanced-Formatting for advanced formatting
|
||||
|`gitlens.statusBar.dateFormat`|Specifies the date format of absolute dates shown in the blame information on the status bar<br />See https://momentjs.com/docs/#/displaying/format/ for valid formats
|
||||
|
||||
### Strings Settings
|
||||
|
||||
|Name | Description
|
||||
|-----|------------
|
||||
|`gitlens.strings.codeLens.unsavedChanges.recentChangeAndAuthors`|Specifies the string to be shown in place of both the `recent change` and `authors` code lens when there are unsaved changes
|
||||
|`gitlens.strings.codeLens.unsavedChanges.recentChangeOnly`|Specifies the string to be shown in place of the `recent change` code lens when there are unsaved changes
|
||||
|`gitlens.strings.codeLens.unsavedChanges.authorsOnly`|Specifies the string to be shown in place of the `authors` code lens when there are unsaved changes
|
||||
|
||||
### Theme Settings
|
||||
|
||||
|Name | Description
|
||||
|-----|------------
|
||||
|`gitlens.theme.annotations.file.gutter.separateLines`|Specifies whether or not gutter blame annotations will have line separators
|
||||
|`gitlens.theme.annotations.file.gutter.dark.backgroundColor`|Specifies the dark theme background color of the gutter blame annotations
|
||||
|`gitlens.theme.annotations.file.gutter.light.backgroundColor`|Specifies the light theme background color of the gutter blame annotations
|
||||
|`gitlens.theme.annotations.file.gutter.dark.foregroundColor`|Specifies the dark theme foreground color of the gutter blame annotations
|
||||
|`gitlens.theme.annotations.file.gutter.light.foregroundColor`|Specifies the light theme foreground color of the gutter blame annotations
|
||||
|`gitlens.theme.annotations.file.gutter.dark.uncommittedForegroundColor`|Specifies the dark theme foreground color of an uncommitted line in the gutter blame annotations
|
||||
|`gitlens.theme.annotations.file.gutter.light.uncommittedForegroundColor`|Specifies the light theme foreground color of an uncommitted line in the gutter blame annotations
|
||||
|`gitlens.theme.annotations.line.trailing.dark.backgroundColor`|Specifies the dark theme background color of the trailing blame annotation
|
||||
|`gitlens.theme.annotations.line.trailing.light.backgroundColor`|Specifies the light theme background color of the trailing blame annotation
|
||||
|`gitlens.theme.annotations.line.trailing.dark.foregroundColor`|Specifies the dark theme foreground color of the trailing blame annotation
|
||||
|`gitlens.theme.annotations.line.trailing.light.foregroundColor`|Specifies the light theme foreground color of the trailing blame annotation
|
||||
|`gitlens.theme.lineHighlight.dark.backgroundColor`|Specifies the dark theme background color of the associated line highlights in blame annotations. Must be a valid css color
|
||||
|`gitlens.theme.lineHighlight.light.backgroundColor`|Specifies the light theme background color of the associated line highlights in blame annotations. Must be a valid css color
|
||||
|`gitlens.theme.lineHighlight.dark.overviewRulerColor`|Specifies the dark theme overview ruler color of the associated line highlights in blame annotations
|
||||
|`gitlens.theme.lineHighlight.light.overviewRulerColor`|Specifies the light theme overview ruler color of the associated line highlights in blame annotations
|
||||
|
||||
### Advanced Settings
|
||||
|
||||
|Name | Description
|
||||
|-----|------------
|
||||
|`gitlens.advanced.telemetry.enabled`|Specifies whether or not to enable GitLens telemetry (even if enabled still abides by the overall `telemetry.enableTelemetry` setting
|
||||
|`gitlens.advanced.menus`|Specifies which commands will be added to which menus
|
||||
|`gitlens.advanced.caching.enabled`|Specifies whether git output will be cached
|
||||
|`gitlens.advanced.caching.maxLines`|Specifies the threshold for caching larger documents
|
||||
|`gitlens.advanced.git`|Specifies the git path to use
|
||||
|`gitlens.advanced.gitignore.enabled`|Specifies whether or not to parse the root .gitignore file for better performance (i.e. avoids blaming excluded files)
|
||||
|`gitlens.advanced.maxQuickHistory`|Specifies the maximum number of QuickPick history entries to show
|
||||
|`gitlens.advanced.quickPick.closeOnFocusOut`|Specifies whether or not to close the QuickPick menu when focus is lost
|
||||
|
||||
## 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`
|
||||
|
||||
## Contributors
|
||||
|
||||
A big thanks to the people that have contributed to this project:
|
||||
|
||||
- Peng Lyu ([@rebornix](https://github.com/rebornix)) — [contributions](https://github.com/eamodio/vscode-gitlens/commits?author=rebornix))
|
||||
- Aurelio Ogliari ([@nobitagit](https://github.com/nobitagit)) — [contributions](https://github.com/eamodio/vscode-gitlens/commits?author=nobitagit)
|
||||
- Johannes Rieken ([@jrieken](https://github.com/jrieken)) — [contributions](https://github.com/eamodio/vscode-gitlens/commits?author=jrieken))
|
||||
- Zack Schuster ([@zackschuster](https://github.com/zackschuster)) — [contributions](https://github.com/eamodio/vscode-gitlens/commits?author=zackschuster)
|
||||
[](https://marketplace.visualstudio.com/items?itemName=eamodio.gitlens)
|
||||
[](https://marketplace.visualstudio.com/items?itemName=eamodio.gitlens)
|
||||
[](https://marketplace.visualstudio.com/items?itemName=eamodio.gitlens)
|
||||
[](https://join.slack.com/t/vscode-dev-community/shared_invite/enQtMjIxOTgxNDE3NzM0LWU5M2ZiZDU1YjBlMzdlZjA2YjBjYzRhYTM5NTgzMTAxMjdiNWU0ZmQzYWI3MWU5N2Q1YjBiYmQ4MzY0NDE1MzY)
|
||||
|
||||
# GitLens
|
||||
|
||||
GitLens **supercharges** the built-in Visual Studio Code Git capabilities. It helps you to **visualize code authorship** at a glance via Git blame annotations and code lens, **seamlessly navigate and explore** the history of a file or branch, **gain valuable insights** via powerful comparison commands, and so much more.
|
||||
|
||||
GitLens provides an unobtrusive blame annotation at the end of the current line, a status bar item showing the commit information (author and date, by default) of the current 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 current line blame annotation distracting — no problem, it is easy to [turn them off or change how they behave](#extension-settings).
|
||||
|
||||
### Preview — featuring blame annotations, code lens, status bar details, quick pick menus for navigation and exploration, compare with previous, and more
|
||||

|
||||
|
||||
## Features
|
||||
|
||||
### Git Blame Annotations
|
||||
|
||||
- Adds an unobtrusive, highly [customizable](#line-blame-annotation-settings) and [themeable](#theme-settings), **Git blame annotation** to the end of the current line ([optional](#line-blame-annotation-settings), on by default)
|
||||
|
||||

|
||||
- Contains the author, date, and message of the line's most recent commit, by [default](#line-blame-annotation-settings)
|
||||
- Adds a `details` hover annotation to the current line annotation, which provides more commit details ([optional](#line-blame-annotation-settings), on by default)
|
||||
- Clicking the commit id will run the `Show Commit Details` command (`gitlens.showQuickCommitDetails`)
|
||||
- Adds a `changes` (diff) hover annotation to the current line annotation, which provides **instant** access to the line's previous version ([optional](#line-blame-annotation-settings), on by default)
|
||||
- Clicking on `Changes` will run the `Compare File Revisions` command (`gitlens.diffWith`)
|
||||
- Clicking the current and previous commit ids will run the `Show Commit Details` command (`gitlens.showQuickCommitDetails`)
|
||||
- Clicking on external link icon will run the the `Open Commit in Remote` command (`gitlens.openCommitInRemote`)
|
||||
|
||||

|
||||
|
||||
- Adds on-demand, beautiful, highly [customizable](#file-blame-annotation-settings) and [themeable](#theme-settings), **Git blame annotations** of the whole file
|
||||
|
||||

|
||||
- Choose between `gutter` (default) and `hover` [annotation styles](#file-blame-annotation-settings)
|
||||
- Contains the commit message and date, by [default](#file-blame-annotation-settings)
|
||||
- Adds a `details` hover annotation to the line's annotation, which provides more commit details ([optional](#file-blame-annotation-settings), on by default)
|
||||
- Clicking the commit id will run the `Show Commit Details` command (`gitlens.showQuickCommitDetails`)
|
||||
- Adds a `heatmap` (age) indicator to the gutter annotations (on right edge by [default](#file-blame-annotation-settings)), which provides an easy, at-a-glance way to tell the age of a line ([optional](#file-blame-annotation-settings), on by default)
|
||||
- Indicator ranges from bright yellow (newer) to dark brown (older)
|
||||
- Press `Escape` to quickly toggle the annotations off
|
||||
|
||||
- Adds [customizable](#status-bar-settings) **blame information** about the current line to the **status bar** ([optional](#status-bar-settings), on by default)
|
||||
|
||||

|
||||
- Contains the commit author and date, by [default](#status-bar-settings)
|
||||
- Clicking the status bar item will, by [default](#status-bar-settings), show a **commit details quick pick menu** with commands for comparing, navigating and exploring commits, and more
|
||||
- Provides [customizable](#status-bar-settings) click behavior — choose between one of the following
|
||||
- Toggle file blame annotations on and off
|
||||
- Toggle code lens on and off
|
||||
- Compare the line commit with the previous commit
|
||||
- Compare the line commit with the working tree
|
||||
- Show a quick pick menu with details and commands for the commit (default)
|
||||
- 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 File Blame Annotations` command (`gitlens.toggleFileBlame`) with a shortcut of `alt+b` to toggle the file blame annotations on and off
|
||||
- Also adds a `Show File Blame Annotations` command (`gitlens.showFileBlame`)
|
||||
|
||||
- Adds a `Toggle Line Blame Annotations` command (`gitlens.toggleLineBlame`) to toggle the current line blame annotations on and off
|
||||
- Also adds a `Show Line Blame Annotations` command (`gitlens.showLineBlame`)
|
||||
|
||||
### Git Recent Changes Annotations
|
||||
|
||||
- Adds on-demand, [customizable](#file-recent-changes-annotation-settings) and [themeable](#theme-settings), **recent changes annotations** of the whole file
|
||||
- Highlights all of lines changed in the most recent commit
|
||||
- Adds a `details` hover annotation to each line, which provides more commit details ([optional](#file-blame-annotation-settings), on by default)
|
||||
- Clicking the commit id will run the `Show Commit Details` command (`gitlens.showQuickCommitDetails`)
|
||||
- Adds a `changes` (diff) hover annotation to each line, which provides **instant** access to the line's previous version ([optional](#file-recent-changes-annotation-settings), on by default)
|
||||
- Clicking on `Changes` will run the `Compare File Revisions` command (`gitlens.diffWith`)
|
||||
- Clicking the current and previous commit ids will run the `Show Commit Details` command (`gitlens.showQuickCommitDetails`)
|
||||
- Press `Escape` to quickly toggle the annotations off
|
||||
|
||||
- Adds `Toggle Recent File Changes Annotations` command (`gitlens.toggleFileRecentChanges`) to toggle the recent changes annotations on and off
|
||||
|
||||
### Git Code Lens
|
||||
|
||||
- Adds **code lens** to the top of the file and on code blocks ([optional](#code-lens-settings), on by default)
|
||||
|
||||

|
||||
- **Recent Change** — author and date of the most recent commit for the file or code block
|
||||
- Clicking the code lens will, by [default](#code-lens-settings), show 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)
|
||||
- Clicking the code lens will, by [default](#code-lens-settings), toggle the file Git blame annotations on and off of 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](#code-lens-settings) click behavior for each code lens — choose between one of the following
|
||||
- Toggle file blame annotations on and off
|
||||
- Compare the commit 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
|
||||
|
||||
### Powerful Comparison Tools
|
||||
|
||||
- Effortlessly navigate between comparisons 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 Branch...` command (`gitlens.diffWithBranch`) to compare the active file with the same file on the selected branch
|
||||
|
||||
- Adds a `Compare File with Next Revision` 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 Revision` command (`gitlens.diffWithPrevious`) with a shortcut of `alt+,` to compare the active file/diff with the previous commit revision
|
||||
|
||||
- Adds a `Compare Line Revision with Previous` 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 Revision...` command (`gitlens.diffWithRevision`) to compare the active file with the selected revision of the same file
|
||||
|
||||
- Adds a `Compare File with Working Revision` 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 Revision with Working` 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 [customizable](#gitlens-custom-view-settings) `GitLens` custom view to the Explorer activity
|
||||
|
||||
- `Repository View` - provides a full repository explorer
|
||||
|
||||

|
||||
|
||||
- `Repository Status` node — provides the status of the repository
|
||||
- Provides the name of the current branch, [optionally](#gitlens-custom-view-settings) its working tree status, and its upstream tracking branch and status (if available)
|
||||
- Provides indicator dots on the repository icon which denote the following:
|
||||
- `None` - up-to-date with the upstream
|
||||
- `Green` - ahead of the upstream
|
||||
- `Red` - behind the upstream
|
||||
- `Yellow` - both ahead of and behind the upstream
|
||||
- Provides additional upstream status nodes, if the current branch is tracking a remote branch and
|
||||
- is behind the upstream — quickly see and explore the specific commits behind the upstream (i.e. commits that haven't been pulled)
|
||||
- is ahead of the upstream — quickly see and explore the specific commits ahead of the upstream (i.e. commits that haven't been pushed)
|
||||
- `Changed Files` node — provides a at-a-glance view of all "working" changes
|
||||
- Expands to a file-based view of all changed files in the working tree ([optionally](#gitlens-custom-view-settings)) and/or all files in all commits ahead of the upstream
|
||||
- Provides a context menu with `Open Repository in Remote`, and `Refresh` commands
|
||||
|
||||
- `Branches` node — provides a list of the local branches
|
||||
- Indicates which branch is the current branch and [optionally](#gitlens-custom-view-settings) shows the remote tracking branch
|
||||
- Expand each branch to easily see its revision (commit) history
|
||||
- Expand each revision (commit) to quickly see the set of files changed, complete with status indicators for adds, changes, renames, and deletes
|
||||
- Provides a context menu on each changed file with `Open Changes`, `Open Changes with Working Tree`, `Open File`, `Open Revision`, `Open File in Remote`, `Open Revision in Remote`, `Apply Changes`, and `Show Commit File Details` commands
|
||||
|
||||
- Provides a context menu on each revision (commit) with `Open Commit in Remote`, `Open All Changes`, `Open All Changes with Working Tree`, `Open Files`, `Open Revisions`, `Copy Commit ID to Clipboard`, `Copy Commit Message to Clipboard`, `Show Commit Details`, and `Refresh` commands
|
||||
- Provides a context menu on each branch with `Open Branch in Remote`, and `Refresh` commands
|
||||
- Provides a context menu with `Open Branches in Remote`, and `Refresh` commands
|
||||
|
||||
- `Remotes` node — provides a list of remotes
|
||||
- Indicates the direction of the remote (fetch, push, both), remote service (if applicable), and repository path
|
||||
- Expand each remote to see its list of branches
|
||||
- Expand each branch to easily see its revision (commit) history
|
||||
- Expand each revision (commit) to quickly see the set of files changed, complete with status indicators for adds, changes, renames, and deletes
|
||||
- Provides a context menu on each changed file with `Open Changes`, `Open Changes with Working Tree`, `Open File`, `Open Revision`, `Open File in Remote`, `Open Revision in Remote`, `Apply Changes`, `Show File History`, and `Show Commit File Details` commands
|
||||
- Provides a context menu on each revision (commit) with `Open Commit in Remote`, `Open All Changes`, `Open All Changes with Working Tree`, `Open Files`, `Open Revisions`, `Copy Commit ID to Clipboard`, `Copy Commit Message to Clipboard`,`Show Commit Details`, and `Refresh` commands
|
||||
- Provides a context menu on each remote with `Open Branches in Remote`, `Open Repository in Remote`, and `Refresh` commands
|
||||
- Provides a context menu with a `Refresh` command
|
||||
|
||||
- `Stashes` node — provides a list of stashed changes
|
||||
- Expand each stash to quickly see the set of files stashed, complete with status indicators for adds, changes, renames, and deletes
|
||||
- Provides a context menu with `Stash Changes`, and `Refresh` commands
|
||||
- Provides a context menu on each stash with `Apply Stashed Changes` (confirmation required), `Delete Stashed Changes` (confirmation required), `Open All Changes`, `Open All Changes with Working Tree`, `Open Files`, `Open Revisions`, `Copy Commit Message to Clipboard`, and `Refresh` commands
|
||||
- Provides a context menu on each stashed file with `Apply Changes`, `Open Changes`, `Open Changes with Working Tree`, `Open File`, `Open Revision`, `Open File in Remote`, and `Show File History` commands
|
||||
|
||||
- `History View` - provides the revision history of the active file
|
||||
|
||||

|
||||
|
||||
- Automatically updates to track the active editor
|
||||
- Provides a context menu with `Open File`, `Open File in Remote`, and `Refresh` commands
|
||||
- Provides a context menu on each revision (commit) with `Open Changes`, `Open Changes with Working Tree`, `Open File`, `Open Revision`, `Open File in Remote`, `Open Revision in Remote`, `Apply Changes`, and `Show Commit File Details` commands
|
||||
|
||||
- Quickly switch between views using the `Switch to Repository View` or `Switch to History View` commands
|
||||
- Provides toolbar commands to `Search Commits`, `Switch to Repository View` or `Switch to History View`, and `Refresh`
|
||||
|
||||
- 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
|
||||
- Also supports [remote services with custom domains](#custom-remotes-settings), such as **BitBucket, Bitbucket Server (previously called Stash), GitHub, GitHub Enterprise, GitLab**
|
||||
- `Open Branches in Remote` command (`gitlens.openBranchesInRemote`) — opens the branches in the supported remote service
|
||||
- `Open Branch in Remote` command (`gitlens.openBranchInRemote`) — opens the current branch commits in the supported remote service
|
||||
- `Open 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 comparison of the current revision with the previous one
|
||||
|
||||
- Adds a `Show Commit File 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 File 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. Choosing 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. Choosing 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 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
|
||||
|
||||

|
||||
|
||||
- Provides entries to `Stash Changes`
|
||||
- Navigate back to the previous quick pick menu via `alt+left arrow`, if available
|
||||
|
||||
- Choosing 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`
|
||||
- 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 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 or from the most recent commit to the current branch, if there is no active editor
|
||||
|
||||
- Adds a `Copy Commit Message to Clipboard` command (`gitlens.copyMessageToClipboard`) to copy the commit message of the active line to the clipboard or from the most recent commit to the current branch, if there is no active editor
|
||||
|
||||
- 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
|
||||
|
||||
- Adds a `Apply Stashed Changes` command (`gitlens.stashApply`) to chose a stash entry to apply to the working tree from a quick pick menu
|
||||
|
||||
- Adds a `Stash Changes` command (`gitlens.stashSave`) to save any working tree changes to the stash — can optionally provide a stash message
|
||||
- Also adds the command to the Source Control items context menu to stash an individual or group of files, works with multi-select too!
|
||||
|
||||
## Insiders
|
||||
|
||||
Add [`"gitlens.insiders": true`](#general-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
|
||||
|
||||
### General Settings
|
||||
|
||||
|Name | Description
|
||||
|-----|------------
|
||||
|`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.outputLevel`|Specifies how much (if any) output will be sent to the GitLens output channel
|
||||
|
||||
### Blame Settings
|
||||
|
||||
|Name | Description
|
||||
|-----|------------
|
||||
|`gitlens.blame.ignoreWhitespace`|Specifies whether or not to ignore whitespace when comparing revisions during blame operations
|
||||
|
||||
#### File Blame Annotation Settings
|
||||
|
||||
|Name | Description
|
||||
|-----|------------
|
||||
|`gitlens.blame.file.annotationType`|Specifies the type of blame annotations that will be shown for the current file<br />`gutter` - adds an annotation to the beginning of each line<br />`hover` - shows annotations when hovering over each line
|
||||
|`gitlens.blame.file.lineHighlight.enabled`|Specifies whether or not to highlight lines associated with the current line
|
||||
|`gitlens.blame.file.lineHighlight.locations`|Specifies where the associated line highlights will be shown<br />`gutter` - adds a gutter glyph<br />`line` - adds a full-line highlight background color<br />`overviewRuler` - adds a decoration to the overviewRuler (scroll bar)
|
||||
|`gitlens.annotations.file.gutter.format`|Specifies the format of the gutter blame annotations<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.annotations.file.gutter.dateFormat`)<br />`${authorAgo}` - commit author, relative commit date<br />See https://github.com/eamodio/vscode-gitlens/wiki/Advanced-Formatting for advanced formatting
|
||||
|`gitlens.annotations.file.gutter.dateFormat`|Specifies how to format absolute dates (using the `${date}` token) in gutter blame annotations<br />See https://momentjs.com/docs/#/displaying/format/ for valid formats
|
||||
|`gitlens.annotations.file.gutter.compact`|Specifies whether or not to compact (deduplicate) matching adjacent gutter blame annotations
|
||||
|`gitlens.annotations.file.gutter.heatmap.enabled`|Specifies whether or not to provide a heatmap indicator in the gutter blame annotations
|
||||
|`gitlens.annotations.file.gutter.heatmap.location`|Specifies where the heatmap indicators will be shown in the gutter blame annotations<br />`left` - adds a heatmap indicator on the left edge of the gutter blame annotations<br />`right` - adds a heatmap indicator on the right edge of the gutter blame annotations
|
||||
|`gitlens.annotations.file.gutter.hover.details`|Specifies whether or not to provide a commit details hover annotation over the gutter blame annotations
|
||||
|`gitlens.annotations.file.gutter.hover.wholeLine`|Specifies whether or not to trigger hover annotations over the whole line
|
||||
|`gitlens.annotations.file.hover.heatmap.enabled`|Specifies whether or not to provide heatmap indicators on the left edge of each line
|
||||
|`gitlens.annotations.file.hover.wholeLine`|Specifies whether or not to trigger hover annotations over the whole line
|
||||
|
||||
#### Line Blame Annotation Settings
|
||||
|
||||
|Name | Description
|
||||
|-----|------------
|
||||
|`gitlens.blame.line.enabled`|Specifies whether or not to provide a blame annotation for the current line, by default<br />Use the `gitlens.toggleLineBlame` command to toggle the annotations on and off for the current session
|
||||
|`gitlens.blame.line.annotationType`|Specifies the type of blame annotations that will be shown for the current line<br />`trailing` - adds an annotation to the end of the current line<br />`hover` - shows annotations when hovering over the current line
|
||||
|`gitlens.annotations.line.trailing.format`|Specifies the format of the trailing blame annotations<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.annotations.line.trailing.dateFormat`)<br />`${authorAgo}` - commit author, relative commit date<br />See https://github.com/eamodio/vscode-gitlens/wiki/Advanced-Formatting for advanced formatting
|
||||
|`gitlens.annotations.line.trailing.dateFormat`|Specifies how to format absolute dates (using the `${date}` token) in trailing blame annotations<br />See https://momentjs.com/docs/#/displaying/format/ for valid formats
|
||||
|`gitlens.annotations.line.trailing.hover.details`|Specifies whether or not to provide a commit details hover annotation over the trailing blame annotations
|
||||
|`gitlens.annotations.line.trailing.hover.changes`|Specifies whether or not to provide a changes (diff) hover annotation over the trailing blame annotations
|
||||
|`gitlens.annotations.line.trailing.hover.wholeLine`|Specifies whether or not to trigger hover annotations over the whole line
|
||||
|`gitlens.annotations.line.hover.details`|Specifies whether or not to provide a commit details hover annotation for the current line
|
||||
|`gitlens.annotations.line.hover.changes`|Specifies whether or not to provide a changes (diff) hover annotation for the current line
|
||||
|
||||
### File Recent Changes Annotation Settings
|
||||
|
||||
|Name | Description
|
||||
|-----|------------
|
||||
|`gitlens.recentChanges.file.lineHighlight.locations`|Specifies where the highlights of the recently changed lines will be shown<br />`gutter` - adds a gutter glyph<br />`line` - adds a full-line highlight background color<br />`overviewRuler` - adds a decoration to the overviewRuler (scroll bar)
|
||||
|`gitlens.annotations.file.recentChanges.hover.details`|Specifies whether or not to provide a commit details hover annotation
|
||||
|`gitlens.annotations.file.recentChanges.hover.changes`|Specifies whether or not to provide a changes (diff) hover annotation
|
||||
|
||||
### Code Lens Settings
|
||||
|
||||
|Name | Description
|
||||
|-----|------------
|
||||
|`gitlens.codeLens.enabled`|Specifies whether or not to provide any Git code lens, by default<br />Use the `gitlens.toggleCodeLens` command to toggle the Git code lens on and off for the current session
|
||||
|`gitlens.codeLens.recentChange.enabled`|Specifies whether or not to show a `recent change` code lens showing the author and date of the most recent commit for the file or code block
|
||||
|`gitlens.codeLens.recentChange.command`|Specifies the command to be executed when the `recent change` code lens is clicked<br />`gitlens.toggleFileBlame` - toggles file blame annotations<br />`gitlens.showBlameHistory` - opens the blame history explorer<br />`gitlens.showFileHistory` - opens the file history explorer<br />`gitlens.diffWithPrevious` - compares the current committed file with the previous commit<br />`gitlens.showQuickCommitDetails` - shows a commit details quick pick<br />`gitlens.showQuickCommitFileDetails` - shows a commit file details quick pick<br />`gitlens.showQuickFileHistory` - shows a file history quick pick<br />`gitlens.showQuickRepoHistory` - shows a branch history quick pick
|
||||
|`gitlens.codeLens.authors.enabled`|Specifies whether or not to show an `authors` code lens showing number of authors of the file or code block and the most prominent author (if there is more than one)
|
||||
|`gitlens.codeLens.authors.command`|Specifies the command to be executed when the `authors` code lens is clicked<br />`gitlens.toggleFileBlame` - toggles file blame annotations<br />`gitlens.showBlameHistory` - opens the blame history explorer<br />`gitlens.showFileHistory` - opens the file history explorer<br />`gitlens.diffWithPrevious` - compares the current committed file with the previous commit<br />`gitlens.showQuickCommitDetails` - shows a commit details quick pick<br />`gitlens.showQuickCommitFileDetails` - shows a commit file details quick pick<br />`gitlens.showQuickFileHistory` - shows a file history quick pick<br />`gitlens.showQuickRepoHistory` - shows a branch history quick pick
|
||||
|`gitlens.codeLens.locations`|Specifies where Git code lens will be shown in the document<br />`document` - adds code lens at the top of the document<br />`containers` - adds code lens at the start of container-like symbols (modules, classes, interfaces, etc)<br />`blocks` - adds code lens at the start of block-like symbols (functions, methods, properties, etc) lines<br />`custom` - adds code lens at the start of symbols contained in `gitlens.codeLens.locationCustomSymbols`
|
||||
|`gitlens.codeLens.customLocationSymbols`|Specifies the set of document symbols where Git code lens will be shown in the document
|
||||
|`gitlens.codeLens.perLanguageLocations`|Specifies where Git code lens will be shown in the document for the specified languages
|
||||
|
||||
### GitLens Custom View Settings
|
||||
|
||||
|Name | Description
|
||||
|-----|------------
|
||||
|`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.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.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.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.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 /> ${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
|
||||
|
||||
|Name | Description
|
||||
|-----|------------
|
||||
|`gitlens.remotes`|Specifies any custom domains for remote (code-hosting) services<br />Example: ```"gitlens.remotes": [{ "domain": "git.corporate-url.com", "type": "GitHub" }]```
|
||||
|
||||
### Status Bar Settings
|
||||
|
||||
|Name | Description
|
||||
|-----|------------
|
||||
|`gitlens.statusBar.enabled`|Specifies whether or not to provide blame information on the status bar
|
||||
|`gitlens.statusBar.alignment`|Specifies the blame alignment in the status bar<br />`left` - align to the left, `right` - align to the right
|
||||
|`gitlens.statusBar.command`|Specifies the command to be executed when the blame status bar item is clicked<br />`gitlens.toggleFileBlame` - toggles file blame annotations<br />`gitlens.showBlameHistory` - opens the blame history explorer<br />`gitlens.showFileHistory` - opens the file history explorer<br />`gitlens.diffWithPrevious` - compares the current line commit with the previous<br />`gitlens.diffWithWorking` - compares the current line commit with the working tree<br />`gitlens.toggleCodeLens` - toggles Git code lens<br />`gitlens.showQuickCommitDetails` - shows a commit details quick pick<br />`gitlens.showQuickCommitFileDetails` - shows a commit file details quick pick<br />`gitlens.showQuickFileHistory` - shows a file history quick pick<br />`gitlens.showQuickRepoHistory` - shows a branch history quick pick
|
||||
|`gitlens.statusBar.format`|Specifies the format of the blame information on the status bar<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 />See https://github.com/eamodio/vscode-gitlens/wiki/Advanced-Formatting for advanced formatting
|
||||
|`gitlens.statusBar.dateFormat`|Specifies the date format of absolute dates shown in the blame information on the status bar<br />See https://momentjs.com/docs/#/displaying/format/ for valid formats
|
||||
|
||||
### Strings Settings
|
||||
|
||||
|Name | Description
|
||||
|-----|------------
|
||||
|`gitlens.strings.codeLens.unsavedChanges.recentChangeAndAuthors`|Specifies the string to be shown in place of both the `recent change` and `authors` code lens when there are unsaved changes
|
||||
|`gitlens.strings.codeLens.unsavedChanges.recentChangeOnly`|Specifies the string to be shown in place of the `recent change` code lens when there are unsaved changes
|
||||
|`gitlens.strings.codeLens.unsavedChanges.authorsOnly`|Specifies the string to be shown in place of the `authors` code lens when there are unsaved changes
|
||||
|
||||
### Theme Settings
|
||||
|
||||
|Name | Description
|
||||
|-----|------------
|
||||
|`gitlens.theme.annotations.file.gutter.separateLines`|Specifies whether or not gutter blame annotations will have line separators
|
||||
|`gitlens.theme.annotations.file.gutter.dark.backgroundColor`|Specifies the dark theme background color of the gutter blame annotations
|
||||
|`gitlens.theme.annotations.file.gutter.light.backgroundColor`|Specifies the light theme background color of the gutter blame annotations
|
||||
|`gitlens.theme.annotations.file.gutter.dark.foregroundColor`|Specifies the dark theme foreground color of the gutter blame annotations
|
||||
|`gitlens.theme.annotations.file.gutter.light.foregroundColor`|Specifies the light theme foreground color of the gutter blame annotations
|
||||
|`gitlens.theme.annotations.file.gutter.dark.uncommittedForegroundColor`|Specifies the dark theme foreground color of an uncommitted line in the gutter blame annotations
|
||||
|`gitlens.theme.annotations.file.gutter.light.uncommittedForegroundColor`|Specifies the light theme foreground color of an uncommitted line in the gutter blame annotations
|
||||
|`gitlens.theme.annotations.line.trailing.dark.backgroundColor`|Specifies the dark theme background color of the trailing blame annotation
|
||||
|`gitlens.theme.annotations.line.trailing.light.backgroundColor`|Specifies the light theme background color of the trailing blame annotation
|
||||
|`gitlens.theme.annotations.line.trailing.dark.foregroundColor`|Specifies the dark theme foreground color of the trailing blame annotation
|
||||
|`gitlens.theme.annotations.line.trailing.light.foregroundColor`|Specifies the light theme foreground color of the trailing blame annotation
|
||||
|`gitlens.theme.lineHighlight.dark.backgroundColor`|Specifies the dark theme background color of the associated line highlights in blame annotations. Must be a valid css color
|
||||
|`gitlens.theme.lineHighlight.light.backgroundColor`|Specifies the light theme background color of the associated line highlights in blame annotations. Must be a valid css color
|
||||
|`gitlens.theme.lineHighlight.dark.overviewRulerColor`|Specifies the dark theme overview ruler color of the associated line highlights in blame annotations
|
||||
|`gitlens.theme.lineHighlight.light.overviewRulerColor`|Specifies the light theme overview ruler color of the associated line highlights in blame annotations
|
||||
|
||||
### Advanced Settings
|
||||
|
||||
|Name | Description
|
||||
|-----|------------
|
||||
|`gitlens.advanced.telemetry.enabled`|Specifies whether or not to enable GitLens telemetry (even if enabled still abides by the overall `telemetry.enableTelemetry` setting
|
||||
|`gitlens.advanced.menus`|Specifies which commands will be added to which menus
|
||||
|`gitlens.advanced.caching.enabled`|Specifies whether git output will be cached
|
||||
|`gitlens.advanced.caching.maxLines`|Specifies the threshold for caching larger documents
|
||||
|`gitlens.advanced.git`|Specifies the git path to use
|
||||
|`gitlens.advanced.gitignore.enabled`|Specifies whether or not to parse the root .gitignore file for better performance (i.e. avoids blaming excluded files)
|
||||
|`gitlens.advanced.maxQuickHistory`|Specifies the maximum number of QuickPick history entries to show
|
||||
|`gitlens.advanced.quickPick.closeOnFocusOut`|Specifies whether or not to close the QuickPick menu when focus is lost
|
||||
|
||||
## 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`
|
||||
|
||||
## Contributors
|
||||
|
||||
A big thanks to the people that have contributed to this project:
|
||||
|
||||
- Amanda Cameron ([@AmandaCameron](https://github.com/AmandaCameron)) — [contributions](https://github.com/eamodio/vscode-gitlens/commits?author=AmandaCameron))
|
||||
- Peng Lyu ([@rebornix](https://github.com/rebornix)) — [contributions](https://github.com/eamodio/vscode-gitlens/commits?author=rebornix))
|
||||
- Aurelio Ogliari ([@nobitagit](https://github.com/nobitagit)) — [contributions](https://github.com/eamodio/vscode-gitlens/commits?author=nobitagit)
|
||||
- Johannes Rieken ([@jrieken](https://github.com/jrieken)) — [contributions](https://github.com/eamodio/vscode-gitlens/commits?author=jrieken))
|
||||
- Zack Schuster ([@zackschuster](https://github.com/zackschuster)) — [contributions](https://github.com/eamodio/vscode-gitlens/commits?author=zackschuster)
|
||||
|
||||
4
images/dark/icon-diff.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<svg width="16" height="22" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill="#C5C5C5" d="m7.5,10l2,0l0,1l-2,0l0,2l-1,0l0,-2l-2,0l0,-1l2,0l0,-2l1,0l0,2l0,0zm-3,6l5,0l0,-1l-5,0l0,1l0,0zm4.5,-11l3.5,3.5l0,9.5c0,0.55 -0.45,1 -1,1l-9,0c-0.55,0 -1,-0.45 -1,-1l0,-12c0,-0.55 0.45,-1 1,-1l6.5,0l0,0zm2.5,4l-3,-3l-6,0l0,12l9,0l0,-9l0,0zm-1.5,-6l-5.5,0l0,1l5,0l4,4l0,8l1,0l0,-8.5l-4.5,-4.5l0,0z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 437 B |
@@ -1,4 +1,4 @@
|
||||
<?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="16px" height="22px">
|
||||
<svg width="16" height="22" version="1.1" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill="#C5C5C5" d="m14.687501,11.955358l-1.079554,-6.821251c-0.076429,-0.458572 -0.477679,-0.821607 -0.955357,-0.821607l-9.30518,0c-0.477679,0 -0.878929,0.363036 -0.955357,0.821607l-1.079554,6.821251l0,4.776787c0,0.525447 0.429911,0.955357 0.955357,0.955357l11.464288,0c0.525447,0 0.955357,-0.429911 0.955357,-0.955357l0,-4.776787l0,0zm-3.133572,0.525447l-0.420357,0.850268c-0.162411,0.324821 -0.496786,0.535 -0.869375,0.535l-4.547501,0c-0.363036,0 -0.687857,-0.210179 -0.850268,-0.525447l-0.420357,-0.869375c-0.162411,-0.315268 -0.496786,-0.525447 -0.850268,-0.525447l-1.327947,0l0.955357,-6.687501l9.553573,0l0.955357,6.687501l-1.318393,0c-0.372589,0 -0.697411,0.210179 -0.869375,0.525447l0.009554,0.009554z" />
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 893 B After Width: | Height: | Size: 846 B |
@@ -1,6 +1,6 @@
|
||||
<svg width="14px" height="14px" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect fill="#7F4E7E" x="0" y="0" width="100" height="100" rx="35" ry="35"/>
|
||||
<text x="50" y="75" font-size="75" text-anchor="middle" style="font-family: Menlo, Monaco, Consolas, "Droid Sans Mono", "Inconsolata", "Courier New", monospace, "Droid Sans Fallback";" fill="white">
|
||||
C
|
||||
!
|
||||
</text>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 431 B After Width: | Height: | Size: 431 B |
6
images/dark/icon-status-unknown.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<svg width="14px" height="14px" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect fill="#6C6C6C" x="0" y="0" width="100" height="100" rx="35" ry="35"/>
|
||||
<text x="50" y="75" font-size="75" text-anchor="middle" style="font-family: Menlo, Monaco, Consolas, "Droid Sans Mono", "Inconsolata", "Courier New", monospace, "Droid Sans Fallback";" fill="white">
|
||||
?
|
||||
</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 431 B |
4
images/dark/icon-sync.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<svg width="16" height="22" version="1.1" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill="#C5C5C5" d="m12.24,10.4c0.19,1.28 -0.2,2.62 -1.2,3.6c-1.47,1.45 -3.74,1.63 -5.41,0.54l1.17,-1.14l-4.3,-0.6l0.6,4.2l1.31,-1.26c2.36,1.74 5.7,1.57 7.84,-0.54c1.24,-1.23 1.81,-2.85 1.74,-4.46l-1.75,-0.34l0,0zm-7.28,-2.4c1.47,-1.45 3.74,-1.63 5.41,-0.54l-1.17,1.14l4.3,0.6l-0.6,-4.2l-1.31,1.26c-2.36,-1.74 -5.7,-1.57 -7.85,0.54c-1.24,1.23 -1.8,2.85 -1.73,4.46l1.75,0.35c-0.19,-1.28 0.2,-2.63 1.2,-3.61l0,0z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 546 B |
4
images/dark/icon-unfold.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<svg width="16" height="22" version="1.1" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill="#C5C5C5" d="m12.5,11l2.5,2.5c0,0.55 -0.45,1 -1,1l-4,0l0,-1l3.5,0l-2,-2l-7,0l-2,2l3.5,0l0,1l-4,0c-0.55,0 -1,-0.45 -1,-1l2.5,-2.5l-2.5,-2.5c0,-0.55 0.45,-1 1,-1l4,0l0,1l-3.5,0l2,2l7,0l2,-2l-3.5,0l0,-1l4,0c0.55,0 1,0.45 1,1l-2.5,2.5l0,0zm-5.5,-1.5l2,0l0,-3l2,0l-3,-3l-3,3l2,0l0,3l0,0zm2,3l-2,0l0,3l-2,0l3,3l3,-3l-2,0l0,-3l0,0z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 467 B |
4
images/light/icon-diff.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<svg width="16" height="22" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill="#424242" d="m7.5,10l2,0l0,1l-2,0l0,2l-1,0l0,-2l-2,0l0,-1l2,0l0,-2l1,0l0,2l0,0zm-3,6l5,0l0,-1l-5,0l0,1l0,0zm4.5,-11l3.5,3.5l0,9.5c0,0.55 -0.45,1 -1,1l-9,0c-0.55,0 -1,-0.45 -1,-1l0,-12c0,-0.55 0.45,-1 1,-1l6.5,0l0,0zm2.5,4l-3,-3l-6,0l0,12l9,0l0,-9l0,0zm-1.5,-6l-5.5,0l0,1l5,0l4,4l0,8l1,0l0,-8.5l-4.5,-4.5l0,0z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 437 B |
@@ -1,4 +1,4 @@
|
||||
<?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="16px" height="22px">
|
||||
<svg width="16" height="22" version="1.1" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill="#424242" d="m14.687501,11.955358l-1.079554,-6.821251c-0.076429,-0.458572 -0.477679,-0.821607 -0.955357,-0.821607l-9.30518,0c-0.477679,0 -0.878929,0.363036 -0.955357,0.821607l-1.079554,6.821251l0,4.776787c0,0.525447 0.429911,0.955357 0.955357,0.955357l11.464288,0c0.525447,0 0.955357,-0.429911 0.955357,-0.955357l0,-4.776787l0,0zm-3.133572,0.525447l-0.420357,0.850268c-0.162411,0.324821 -0.496786,0.535 -0.869375,0.535l-4.547501,0c-0.363036,0 -0.687857,-0.210179 -0.850268,-0.525447l-0.420357,-0.869375c-0.162411,-0.315268 -0.496786,-0.525447 -0.850268,-0.525447l-1.327947,0l0.955357,-6.687501l9.553573,0l0.955357,6.687501l-1.318393,0c-0.372589,0 -0.697411,0.210179 -0.869375,0.525447l0.009554,0.009554z" />
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 893 B After Width: | Height: | Size: 846 B |
@@ -1,6 +1,6 @@
|
||||
<svg width="14px" height="14px" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect fill="#9B4F96" x="0" y="0" width="100" height="100" rx="35" ry="35"/>
|
||||
<text x="50" y="75" font-size="75" text-anchor="middle" style="font-family: Menlo, Monaco, Consolas, "Droid Sans Mono", "Inconsolata", "Courier New", monospace, "Droid Sans Fallback";" fill="white">
|
||||
C
|
||||
!
|
||||
</text>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 431 B After Width: | Height: | Size: 431 B |
6
images/light/icon-status-unknown.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<svg width="14px" height="14px" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect fill="#6C6C6C" x="0" y="0" width="100" height="100" rx="35" ry="35"/>
|
||||
<text x="50" y="75" font-size="75" text-anchor="middle" style="font-family: Menlo, Monaco, Consolas, "Droid Sans Mono", "Inconsolata", "Courier New", monospace, "Droid Sans Fallback";" fill="white">
|
||||
?
|
||||
</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 431 B |
4
images/light/icon-sync.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<svg width="16" height="22" version="1.1" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill="#424242" d="m12.24,10.4c0.19,1.28 -0.2,2.62 -1.2,3.6c-1.47,1.45 -3.74,1.63 -5.41,0.54l1.17,-1.14l-4.3,-0.6l0.6,4.2l1.31,-1.26c2.36,1.74 5.7,1.57 7.84,-0.54c1.24,-1.23 1.81,-2.85 1.74,-4.46l-1.75,-0.34l0,0zm-7.28,-2.4c1.47,-1.45 3.74,-1.63 5.41,-0.54l-1.17,1.14l4.3,0.6l-0.6,-4.2l-1.31,1.26c-2.36,-1.74 -5.7,-1.57 -7.85,0.54c-1.24,1.23 -1.8,2.85 -1.73,4.46l1.75,0.35c-0.19,-1.28 0.2,-2.63 1.2,-3.61l0,0z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 546 B |
4
images/light/icon-unfold.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<svg width="16" height="22" version="1.1" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill="#424242" d="m12.5,11l2.5,2.5c0,0.55 -0.45,1 -1,1l-4,0l0,-1l3.5,0l-2,-2l-7,0l-2,2l3.5,0l0,1l-4,0c-0.55,0 -1,-0.45 -1,-1l2.5,-2.5l-2.5,-2.5c0,-0.55 0.45,-1 1,-1l4,0l0,1l-3.5,0l2,2l7,0l2,-2l-3.5,0l0,-1l4,0c0.55,0 1,0.45 1,1l-2.5,2.5l0,0zm-5.5,-1.5l2,0l0,-3l2,0l-3,-3l-3,3l2,0l0,3l0,0zm2,3l-2,0l0,3l-2,0l3,3l3,-3l-2,0l0,-3l0,0z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 467 B |
|
Before Width: | Height: | Size: 23 KiB |
292
package-lock.json
generated
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "gitlens",
|
||||
"version": "5.0.0-beta",
|
||||
"version": "5.3.0",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
@@ -16,7 +16,7 @@
|
||||
"integrity": "sha1-qjuL2ivlErGuCgV7lC6GnDcKVWk=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/node": "8.0.28"
|
||||
"@types/node": "8.0.31"
|
||||
}
|
||||
},
|
||||
"@types/mocha": {
|
||||
@@ -26,9 +26,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"@types/node": {
|
||||
"version": "8.0.28",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-8.0.28.tgz",
|
||||
"integrity": "sha512-HupkFXEv3O3KSzcr3Ylfajg0kaerBg1DyaZzRBBQfrU3NN1mTBRE7sCveqHwXLS5Yrjvww8qFzkzYQQakG9FuQ==",
|
||||
"version": "8.0.31",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-8.0.31.tgz",
|
||||
"integrity": "sha512-R+LdMJHJQwRd/Ca0Nr5KnwbSWHxTD3DWz4ivqoPeNH+YPcuirMWK+Ti9Mx32jOecmPhHOCd+6CefU5e1eVq2Ew==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/tmp": {
|
||||
@@ -38,19 +38,22 @@
|
||||
"dev": true
|
||||
},
|
||||
"ajv": {
|
||||
"version": "4.11.8",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz",
|
||||
"integrity": "sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY=",
|
||||
"version": "5.2.3",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-5.2.3.tgz",
|
||||
"integrity": "sha1-wG9Zh3jETGsWGrr+NGa4GtGBTtI=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"co": "4.6.0",
|
||||
"fast-deep-equal": "1.0.0",
|
||||
"json-schema-traverse": "0.3.1",
|
||||
"json-stable-stringify": "1.0.1"
|
||||
}
|
||||
},
|
||||
"ansi-regex": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
|
||||
"integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg="
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
|
||||
"integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=",
|
||||
"dev": true
|
||||
},
|
||||
"ansi-styles": {
|
||||
"version": "2.2.1",
|
||||
@@ -249,21 +252,6 @@
|
||||
"supports-color": "2.0.0"
|
||||
},
|
||||
"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": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
|
||||
@@ -385,15 +373,15 @@
|
||||
}
|
||||
},
|
||||
"dateformat": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/dateformat/-/dateformat-2.0.0.tgz",
|
||||
"integrity": "sha1-J0Pjq7XD/CRi5SfcpEXgTp9N7hc=",
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/dateformat/-/dateformat-2.2.0.tgz",
|
||||
"integrity": "sha1-QGXiATz5+5Ft39gu+1Bq1MZ2kGI=",
|
||||
"dev": true
|
||||
},
|
||||
"debug": {
|
||||
"version": "2.6.8",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.8.tgz",
|
||||
"integrity": "sha1-5zFTHKLt4n0YgiJCfaF4IdaP9Pw=",
|
||||
"version": "2.6.9",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
||||
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
|
||||
"requires": {
|
||||
"ms": "2.0.0"
|
||||
}
|
||||
@@ -597,6 +585,12 @@
|
||||
"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": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.0.1.tgz",
|
||||
@@ -684,7 +678,7 @@
|
||||
"graceful-fs": "4.1.11",
|
||||
"inherits": "2.0.3",
|
||||
"mkdirp": "0.5.1",
|
||||
"rimraf": "2.6.1"
|
||||
"rimraf": "2.6.2"
|
||||
}
|
||||
},
|
||||
"generate-function": {
|
||||
@@ -1003,7 +997,7 @@
|
||||
"oauth-sign": "0.8.2",
|
||||
"qs": "6.3.2",
|
||||
"stringstream": "0.0.5",
|
||||
"tough-cookie": "2.3.2",
|
||||
"tough-cookie": "2.3.3",
|
||||
"tunnel-agent": "0.4.3",
|
||||
"uuid": "3.1.0"
|
||||
}
|
||||
@@ -1086,7 +1080,7 @@
|
||||
"array-uniq": "1.0.3",
|
||||
"beeper": "1.1.1",
|
||||
"chalk": "1.1.3",
|
||||
"dateformat": "2.0.0",
|
||||
"dateformat": "2.2.0",
|
||||
"fancy-log": "1.3.0",
|
||||
"gulplog": "1.0.0",
|
||||
"has-gulplog": "0.1.0",
|
||||
@@ -1187,9 +1181,9 @@
|
||||
}
|
||||
},
|
||||
"har-schema": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/har-schema/-/har-schema-1.0.5.tgz",
|
||||
"integrity": "sha1-0mMTX0MwfALGAq/I/pWXDAFRNp4=",
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz",
|
||||
"integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=",
|
||||
"dev": true
|
||||
},
|
||||
"har-validator": {
|
||||
@@ -1211,14 +1205,6 @@
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"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": {
|
||||
@@ -1336,11 +1322,6 @@
|
||||
"integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=",
|
||||
"dev": true
|
||||
},
|
||||
"is-fullwidth-code-point": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
|
||||
"integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8="
|
||||
},
|
||||
"is-glob": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz",
|
||||
@@ -1459,6 +1440,12 @@
|
||||
"integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=",
|
||||
"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": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz",
|
||||
@@ -1794,9 +1781,9 @@
|
||||
}
|
||||
},
|
||||
"mocha": {
|
||||
"version": "3.5.2",
|
||||
"resolved": "https://registry.npmjs.org/mocha/-/mocha-3.5.2.tgz",
|
||||
"integrity": "sha512-iH5zl7afRZl1GvD0pnrRlazgc9Z/o34pXWmTFi8xNIMFKXgNL1SoBTDDb9sJfbV/sJV/j8X+0gvwY1QS1He7Nw==",
|
||||
"version": "3.5.3",
|
||||
"resolved": "https://registry.npmjs.org/mocha/-/mocha-3.5.3.tgz",
|
||||
"integrity": "sha512-/6na001MJWEtYxHOV1WLfsmR4YIynkUEhBwzsb+fk2qmQ3iqsi258l/Q2MWHJMImAcNpZ8DEdYAK72NHoIQ9Eg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"browser-stdout": "1.3.0",
|
||||
@@ -1811,6 +1798,17 @@
|
||||
"lodash.create": "3.1.1",
|
||||
"mkdirp": "0.5.1",
|
||||
"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": {
|
||||
@@ -1971,9 +1969,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"performance-now": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/performance-now/-/performance-now-0.2.0.tgz",
|
||||
"integrity": "sha1-M+8wxcd9TqIcWlOGnZG1bY8lVeU=",
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
|
||||
"integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=",
|
||||
"dev": true
|
||||
},
|
||||
"pinkie": {
|
||||
@@ -2120,57 +2118,147 @@
|
||||
"dev": true
|
||||
},
|
||||
"request": {
|
||||
"version": "2.81.0",
|
||||
"resolved": "https://registry.npmjs.org/request/-/request-2.81.0.tgz",
|
||||
"integrity": "sha1-xpKJRqDgbF+Nb4qTM0af/aRimKA=",
|
||||
"version": "2.82.0",
|
||||
"resolved": "https://registry.npmjs.org/request/-/request-2.82.0.tgz",
|
||||
"integrity": "sha512-/QWqfmyTfQ4OYs6EhB1h2wQsX9ZxbuNePCvCm0Mdz/mxw73mjdg0D4QdIl0TQBFs35CZmMXLjk0iCGK395CUDg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"aws-sign2": "0.6.0",
|
||||
"aws-sign2": "0.7.0",
|
||||
"aws4": "1.6.0",
|
||||
"caseless": "0.12.0",
|
||||
"combined-stream": "1.0.5",
|
||||
"extend": "3.0.1",
|
||||
"forever-agent": "0.6.1",
|
||||
"form-data": "2.1.4",
|
||||
"har-validator": "4.2.1",
|
||||
"hawk": "3.1.3",
|
||||
"http-signature": "1.1.1",
|
||||
"form-data": "2.3.1",
|
||||
"har-validator": "5.0.3",
|
||||
"hawk": "6.0.2",
|
||||
"http-signature": "1.2.0",
|
||||
"is-typedarray": "1.0.0",
|
||||
"isstream": "0.1.2",
|
||||
"json-stringify-safe": "5.0.1",
|
||||
"mime-types": "2.1.17",
|
||||
"oauth-sign": "0.8.2",
|
||||
"performance-now": "0.2.0",
|
||||
"qs": "6.4.0",
|
||||
"performance-now": "2.1.0",
|
||||
"qs": "6.5.1",
|
||||
"safe-buffer": "5.1.1",
|
||||
"stringstream": "0.0.5",
|
||||
"tough-cookie": "2.3.2",
|
||||
"tough-cookie": "2.3.3",
|
||||
"tunnel-agent": "0.6.0",
|
||||
"uuid": "3.1.0"
|
||||
},
|
||||
"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": {
|
||||
"version": "0.12.0",
|
||||
"resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
|
||||
"integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=",
|
||||
"dev": true
|
||||
},
|
||||
"har-validator": {
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/har-validator/-/har-validator-4.2.1.tgz",
|
||||
"integrity": "sha1-M0gdDxu/9gDdID11gSpqX7oALio=",
|
||||
"cryptiles": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-3.1.2.tgz",
|
||||
"integrity": "sha1-qJ+7Ig9c4l7FboxKqKT9e1sNKf4=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ajv": "4.11.8",
|
||||
"har-schema": "1.0.5"
|
||||
"boom": "5.2.0"
|
||||
},
|
||||
"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": {
|
||||
"version": "6.4.0",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.4.0.tgz",
|
||||
"integrity": "sha1-E+JtKK1rD/qpExLNO/cI7TUecjM=",
|
||||
"version": "6.5.1",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz",
|
||||
"integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==",
|
||||
"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": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
|
||||
@@ -2198,9 +2286,9 @@
|
||||
}
|
||||
},
|
||||
"rimraf": {
|
||||
"version": "2.6.1",
|
||||
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.1.tgz",
|
||||
"integrity": "sha1-wjOOxkPfeht/5cVPqG9XQopV8z0=",
|
||||
"version": "2.6.2",
|
||||
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz",
|
||||
"integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"glob": "7.1.1"
|
||||
@@ -2260,7 +2348,7 @@
|
||||
"resolved": "https://registry.npmjs.org/spawn-rx/-/spawn-rx-2.0.11.tgz",
|
||||
"integrity": "sha1-ZUUa1lZigB2up1VJgyp4LeAEjb8=",
|
||||
"requires": {
|
||||
"debug": "2.6.8",
|
||||
"debug": "2.6.9",
|
||||
"lodash.assign": "4.2.0",
|
||||
"rxjs": "5.4.3"
|
||||
}
|
||||
@@ -2334,15 +2422,6 @@
|
||||
"integrity": "sha1-l+mNj6TRBdYqJpHR3AfoINuN/E8=",
|
||||
"dev": true
|
||||
},
|
||||
"string-width": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
|
||||
"integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==",
|
||||
"requires": {
|
||||
"is-fullwidth-code-point": "2.0.0",
|
||||
"strip-ansi": "4.0.0"
|
||||
}
|
||||
},
|
||||
"string_decoder": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz",
|
||||
@@ -2359,11 +2438,12 @@
|
||||
"dev": true
|
||||
},
|
||||
"strip-ansi": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
|
||||
"integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
|
||||
"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": "3.0.0"
|
||||
"ansi-regex": "2.1.1"
|
||||
}
|
||||
},
|
||||
"strip-bom": {
|
||||
@@ -2466,9 +2546,9 @@
|
||||
}
|
||||
},
|
||||
"tough-cookie": {
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.2.tgz",
|
||||
"integrity": "sha1-8IH3bkyFcg5sN6X6ztc3FQ2EByo=",
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.3.tgz",
|
||||
"integrity": "sha1-C2GKVWW23qkL80JdBNVe3EdadWE=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"punycode": "1.4.1"
|
||||
@@ -2495,13 +2575,13 @@
|
||||
"resolve": "1.4.0",
|
||||
"semver": "5.4.1",
|
||||
"tslib": "1.7.1",
|
||||
"tsutils": "2.8.2"
|
||||
"tsutils": "2.9.0"
|
||||
}
|
||||
},
|
||||
"tsutils": {
|
||||
"version": "2.8.2",
|
||||
"resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.8.2.tgz",
|
||||
"integrity": "sha1-LBSGukMSYIRbCsb5Aq/Z1wio6mo=",
|
||||
"version": "2.9.0",
|
||||
"resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.9.0.tgz",
|
||||
"integrity": "sha1-fhU3tVa6tocvp+ZIXf9FsHbVUz0=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"tslib": "1.7.1"
|
||||
@@ -2521,9 +2601,9 @@
|
||||
"optional": true
|
||||
},
|
||||
"typescript": {
|
||||
"version": "2.5.2",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-2.5.2.tgz",
|
||||
"integrity": "sha1-A4qV99m7tCCxvzW6MdTFwd0//jQ=",
|
||||
"version": "2.5.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-2.5.3.tgz",
|
||||
"integrity": "sha512-ptLSQs2S4QuS6/OD1eAKG+S5G8QQtrU5RT32JULdZQtM1L3WTi34Wsu48Yndzi8xsObRAB9RPt/KhA9wlpEF6w==",
|
||||
"dev": true
|
||||
},
|
||||
"unique-stream": {
|
||||
@@ -2714,8 +2794,8 @@
|
||||
"gulp-symdest": "1.1.0",
|
||||
"gulp-untar": "0.0.6",
|
||||
"gulp-vinyl-zip": "1.4.0",
|
||||
"mocha": "3.5.2",
|
||||
"request": "2.81.0",
|
||||
"mocha": "3.5.3",
|
||||
"request": "2.82.0",
|
||||
"semver": "5.4.1",
|
||||
"source-map-support": "0.4.18",
|
||||
"url-parse": "1.1.9",
|
||||
|
||||
330
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "gitlens",
|
||||
"version": "5.0.0-beta",
|
||||
"version": "5.3.0",
|
||||
"author": {
|
||||
"name": "Eric Amodio",
|
||||
"email": "eamodio@gmail.com"
|
||||
@@ -15,8 +15,8 @@
|
||||
"badges": [
|
||||
{
|
||||
"url": "https://img.shields.io/badge/chat-on%20slack-brightgreen.svg",
|
||||
"href": "https://join.slack.com/t/vscode-gitlens/shared_invite/MjIxOTgxNDE3NzM0LTE1MDE2Nzk1MTgtMjkwMmZjMzcxNQ",
|
||||
"description": "Chat at https://vscode-gitlens.slack.com/"
|
||||
"href": "https://join.slack.com/t/vscode-dev-community/shared_invite/enQtMjIxOTgxNDE3NzM0LWU5M2ZiZDU1YjBlMzdlZjA2YjBjYzRhYTM5NTgzMTAxMjdiNWU0ZmQzYWI3MWU5N2Q1YjBiYmQ4MzY0NDE1MzY",
|
||||
"description": "Chat at https://vscode-dev-community.slack.com/"
|
||||
}
|
||||
],
|
||||
"categories": [
|
||||
@@ -132,11 +132,6 @@
|
||||
"default": true,
|
||||
"description": "Specifies whether or not to provide a changes (diff) hover annotation"
|
||||
},
|
||||
"gitlens.annotations.file.recentChanges.hover.wholeLine": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "Specifies whether or not to trigger hover annotations over the whole line"
|
||||
},
|
||||
"gitlens.annotations.line.hover.details": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
@@ -172,6 +167,11 @@
|
||||
"default": false,
|
||||
"description": "Specifies whether or not to trigger hover annotations over the whole line"
|
||||
},
|
||||
"gitlens.blame.ignoreWhitespace": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Specifies whether or not to ignore whitespace when comparing revisions during blame operations"
|
||||
},
|
||||
"gitlens.blame.file.annotationType": {
|
||||
"type": "string",
|
||||
"default": "gutter",
|
||||
@@ -303,7 +303,7 @@
|
||||
"minItems": 1,
|
||||
"maxItems": 4,
|
||||
"uniqueItems": true,
|
||||
"description": "Specifies where Git code lens will be shown in the document\n `document` - adds code lens at the top of the document\n `containers` - adds code lens at the start of container-like symbols (modules, classes, interfaces, etc)\n `blocks` - adds code lens at the start of block-like symbols (functions, methods, properties, etc) lines\n `custom` - adds code lens at the start of symbols contained in `gitlens.codeLens.locationCustomSymbols`"
|
||||
"description": "Specifies where Git code lens will be shown in the document\n `document` - adds code lens at the top of the document\n `containers` - adds code lens at the start of container-like symbols (modules, classes, interfaces, etc)\n `blocks` - adds code lens at the start of block-like symbols (functions, methods, properties, etc) lines\n `custom` - adds code lens at the start of symbols contained in `gitlens.codeLens.locationCustomSymbols`"
|
||||
},
|
||||
"gitlens.codeLens.customLocationSymbols": {
|
||||
"type": "array",
|
||||
@@ -388,7 +388,7 @@
|
||||
"minItems": 1,
|
||||
"maxItems": 4,
|
||||
"uniqueItems": true,
|
||||
"description": "Specifies where Git code lens will be shown in the document for the specified language\n `document` - adds code lens at the top of the document\n `containers` - adds code lens at the start of container-like symbols (modules, classes, interfaces, etc)\n `blocks` - adds code lens at the start of block-like symbols (functions, methods, properties, etc) lines\n `custom` - adds code lens at the start of symbols contained in `customSymbols`"
|
||||
"description": "Specifies where Git code lens will be shown in the document for the specified language\n `document` - adds code lens at the top of the document\n `containers` - adds code lens at the start of container-like symbols (modules, classes, interfaces, etc)\n `blocks` - adds code lens at the start of block-like symbols (functions, methods, properties, etc) lines\n `custom` - adds code lens at the start of symbols contained in `customSymbols`"
|
||||
},
|
||||
"customSymbols": {
|
||||
"type": "array",
|
||||
@@ -415,13 +415,43 @@
|
||||
},
|
||||
"gitlens.gitExplorer.commitFormat": {
|
||||
"type": "string",
|
||||
"default": "${message} \u00a0\u2022\u00a0 ${authorAgo} \u00a0\u2022\u00a0 ${id}",
|
||||
"default": "${message} \u00a0\u2022\u00a0 ${authorAgo} \u00a0 (${id})",
|
||||
"description": "Specifies the format of committed changes in the `GitLens` custom view\nAvailable tokens\n ${id} - commit id\n ${author} - commit author\n ${message} - commit message\n ${ago} - relative commit date (e.g. 1 day ago)\n ${date} - formatted commit date (format specified by `gitlens.statusBar.dateFormat`)\n ${authorAgo} - commit author, relative commit date\nSee https://github.com/eamodio/vscode-gitlens/wiki/Advanced-Formatting for advanced formatting"
|
||||
},
|
||||
"gitlens.gitExplorer.commitFileFormat": {
|
||||
"type": "string",
|
||||
"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": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"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": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "Specifies whether or not to include working tree files inside the `Repository Status` node of the `GitLens` custom view"
|
||||
},
|
||||
"gitlens.gitExplorer.showTrackingBranch": {
|
||||
"type": "boolean",
|
||||
@@ -436,16 +466,22 @@
|
||||
"gitlens.gitExplorer.stashFileFormat": {
|
||||
"type": "string",
|
||||
"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": {
|
||||
"type": "string",
|
||||
"default": "${working}${filePath}",
|
||||
"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": {
|
||||
"type": "string",
|
||||
"default": "repository",
|
||||
"default": "auto",
|
||||
"enum": [
|
||||
"auto",
|
||||
"history",
|
||||
"repository"
|
||||
],
|
||||
"description": "Specifies the starting view (mode) of the `GitLens` custom view\n `history` - shows the commit history of the active file\n `repository` - shows a repository explorer"
|
||||
"description": "Specifies the starting view (mode) of the `GitLens` custom view\n `auto` - shows the last selected view, defaults to `repository`\n `history` - shows the commit history of the active file\n `repository` - shows a repository explorer"
|
||||
},
|
||||
"gitlens.remotes": {
|
||||
"type": "array",
|
||||
@@ -461,10 +497,11 @@
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"Bitbucket",
|
||||
"BitbucketServer",
|
||||
"GitHub",
|
||||
"GitLab"
|
||||
],
|
||||
"description": "Specifies the type of the custom remote service\n `Bitbucket`, `GitHub`, or `GitLab`"
|
||||
"description": "Specifies the type of the custom remote service\n `Bitbucket`, `BitbucketServer`, `GitHub`, or `GitLab`"
|
||||
},
|
||||
"domain": {
|
||||
"type": "string",
|
||||
@@ -473,7 +510,7 @@
|
||||
}
|
||||
},
|
||||
"uniqueItems": true,
|
||||
"description": "Specifies the custom remote services (code-hosting)"
|
||||
"description": "Specifies any custom domains for remote (code-hosting) services"
|
||||
},
|
||||
"gitlens.statusBar.enabled": {
|
||||
"type": "boolean",
|
||||
@@ -645,16 +682,16 @@
|
||||
},
|
||||
"editorTitle": {
|
||||
"blame": true,
|
||||
"fileDiff": true,
|
||||
"history": true,
|
||||
"remote": true,
|
||||
"status": true
|
||||
"fileDiff": false,
|
||||
"history": false,
|
||||
"remote": false,
|
||||
"status": false
|
||||
},
|
||||
"editorTitleContext": {
|
||||
"blame": true,
|
||||
"fileDiff": true,
|
||||
"history": true,
|
||||
"remote": true
|
||||
"blame": false,
|
||||
"fileDiff": false,
|
||||
"history": false,
|
||||
"remote": false
|
||||
},
|
||||
"explorerContext": {
|
||||
"fileDiff": true,
|
||||
@@ -1031,6 +1068,11 @@
|
||||
"light": "images/light/icon-add.svg"
|
||||
}
|
||||
},
|
||||
{
|
||||
"command": "gitlens.externalDiff",
|
||||
"title": "Open Changes (with difftool)",
|
||||
"category": "GitLens"
|
||||
},
|
||||
{
|
||||
"command": "gitlens.resetSuppressedWarnings",
|
||||
"title": "Reset Suppressed Warnings",
|
||||
@@ -1093,6 +1135,16 @@
|
||||
"title": "Open Files",
|
||||
"category": "GitLens"
|
||||
},
|
||||
{
|
||||
"command": "gitlens.gitExplorer.openChangedFileChanges",
|
||||
"title": "Open All Changes",
|
||||
"category": "GitLens"
|
||||
},
|
||||
{
|
||||
"command": "gitlens.gitExplorer.openChangedFileChangesWithWorking",
|
||||
"title": "Open All Changes with Working Tree",
|
||||
"category": "GitLens"
|
||||
},
|
||||
{
|
||||
"command": "gitlens.gitExplorer.openChangedFileRevisions",
|
||||
"title": "Open Revisions",
|
||||
@@ -1298,6 +1350,14 @@
|
||||
"command": "gitlens.gitExplorer.openChangedFiles",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "gitlens.gitExplorer.openChangedFileChanges",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "gitlens.gitExplorer.openChangedFileChangesWithWorking",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "gitlens.gitExplorer.openChangedFileRevisions",
|
||||
"when": "false"
|
||||
@@ -1378,12 +1438,12 @@
|
||||
},
|
||||
{
|
||||
"command": "gitlens.openFileInRemote",
|
||||
"when": "gitlens:enabled && gitlens:hasRemotes && config.gitlens.advanced.menus.editorTitleContext.remote",
|
||||
"when": "gitlens:enabled && gitlens:hasRemotes && config.gitlens.advanced.menus.editorTitle.remote",
|
||||
"group": "1_gitlens"
|
||||
},
|
||||
{
|
||||
"command": "gitlens.openRepoInRemote",
|
||||
"when": "gitlens:enabled && gitlens:hasRemotes && config.gitlens.advanced.menus.editorTitleContext.remote",
|
||||
"when": "gitlens:enabled && gitlens:hasRemotes && config.gitlens.advanced.menus.editorTitle.remote",
|
||||
"group": "1_gitlens"
|
||||
},
|
||||
{
|
||||
@@ -1465,17 +1525,22 @@
|
||||
{
|
||||
"command": "gitlens.openChangedFiles",
|
||||
"when": "gitlens:enabled",
|
||||
"group": "1_gitlens@1"
|
||||
"group": "2_gitlens@1"
|
||||
},
|
||||
{
|
||||
"command": "gitlens.closeUnchangedFiles",
|
||||
"when": "gitlens:enabled",
|
||||
"group": "1_gitlens@2"
|
||||
"group": "2_gitlens@2"
|
||||
},
|
||||
{
|
||||
"command": "gitlens.externalDiff",
|
||||
"when": "gitlens:enabled",
|
||||
"group": "2_gitlens@3"
|
||||
},
|
||||
{
|
||||
"command": "gitlens.stashSave",
|
||||
"when": "gitlens:enabled",
|
||||
"group": "2_gitlens@1"
|
||||
"group": "3_gitlens@1"
|
||||
}
|
||||
],
|
||||
"scm/resourceState/context": [
|
||||
@@ -1484,6 +1549,11 @@
|
||||
"when": "gitlens:enabled && gitlens:hasRemotes",
|
||||
"group": "navigation"
|
||||
},
|
||||
{
|
||||
"command": "gitlens.externalDiff",
|
||||
"when": "gitlens:enabled",
|
||||
"group": "navigation"
|
||||
},
|
||||
{
|
||||
"command": "gitlens.diffWithRevision",
|
||||
"when": "gitlens:enabled",
|
||||
@@ -1503,204 +1573,269 @@
|
||||
"view/title": [
|
||||
{
|
||||
"command": "gitlens.showCommitSearch",
|
||||
"when": "gitlens:enabled && view == gitlens.gitExplorer",
|
||||
"when": "view == gitlens.gitExplorer",
|
||||
"group": "navigation@1"
|
||||
},
|
||||
{
|
||||
"command": "gitlens.gitExplorer.switchToHistoryView",
|
||||
"when": "gitlens:enabled && view == gitlens.gitExplorer && gitlens:gitExplorer:view == repository",
|
||||
"when": "view == gitlens.gitExplorer && gitlens:gitExplorer:view == repository",
|
||||
"group": "navigation@2"
|
||||
},
|
||||
{
|
||||
"command": "gitlens.gitExplorer.switchToRepositoryView",
|
||||
"when": "gitlens:enabled && view == gitlens.gitExplorer && gitlens:gitExplorer:view == history",
|
||||
"when": "view == gitlens.gitExplorer && gitlens:gitExplorer:view == history",
|
||||
"group": "navigation@3"
|
||||
},
|
||||
{
|
||||
"command": "gitlens.gitExplorer.refresh",
|
||||
"when": "gitlens:enabled && view == gitlens.gitExplorer",
|
||||
"when": "view == gitlens.gitExplorer",
|
||||
"group": "navigation@4"
|
||||
}
|
||||
],
|
||||
"view/item/context": [
|
||||
{
|
||||
"command": "gitlens.openBranchesInRemote",
|
||||
"when": "gitlens:enabled && gitlens:hasRemotes && view == gitlens.gitExplorer && viewItem == gitlens:branches",
|
||||
"when": "view == gitlens.gitExplorer && viewItem == gitlens:branches:remote",
|
||||
"group": "1_gitlens@1"
|
||||
},
|
||||
{
|
||||
"command": "gitlens.openBranchInRemote",
|
||||
"when": "gitlens:enabled && gitlens:hasRemotes && view == gitlens.gitExplorer && viewItem == gitlens:branch-history",
|
||||
"when": "view == gitlens.gitExplorer && viewItem == gitlens:branch-history:remote",
|
||||
"group": "1_gitlens@1"
|
||||
},
|
||||
{
|
||||
"command": "gitlens.openCommitInRemote",
|
||||
"when": "gitlens:enabled && gitlens:hasRemotes && view == gitlens.gitExplorer && viewItem == gitlens:commit",
|
||||
"when": "gitlens:hasRemotes && view == gitlens.gitExplorer && viewItem == gitlens:commit",
|
||||
"group": "1_gitlens@1"
|
||||
},
|
||||
{
|
||||
"command": "gitlens.copyShaToClipboard",
|
||||
"when": "gitlens:enabled && view == gitlens.gitExplorer && viewItem == gitlens:commit",
|
||||
"command": "gitlens.gitExplorer.openChangedFileChanges",
|
||||
"when": "view == gitlens.gitExplorer && viewItem == gitlens:commit",
|
||||
"group": "2_gitlens@1"
|
||||
},
|
||||
{
|
||||
"command": "gitlens.copyMessageToClipboard",
|
||||
"when": "gitlens:enabled && view == gitlens.gitExplorer && viewItem == gitlens:commit",
|
||||
"group": "2_gitlens@2"
|
||||
"command": "gitlens.gitExplorer.openChangedFileChangesWithWorking",
|
||||
"when": "view == gitlens.gitExplorer && viewItem == gitlens:commit",
|
||||
"group": "2_gitlens@1"
|
||||
},
|
||||
{
|
||||
"command": "gitlens.gitExplorer.openChangedFiles",
|
||||
"when": "gitlens:enabled && view == gitlens.gitExplorer && viewItem == gitlens:commit",
|
||||
"when": "view == gitlens.gitExplorer && viewItem == gitlens:commit",
|
||||
"group": "3_gitlens@1"
|
||||
},
|
||||
{
|
||||
"command": "gitlens.gitExplorer.openChangedFileRevisions",
|
||||
"when": "gitlens:enabled && view == gitlens.gitExplorer && viewItem == gitlens:commit",
|
||||
"when": "view == gitlens.gitExplorer && viewItem == gitlens:commit",
|
||||
"group": "3_gitlens@2"
|
||||
},
|
||||
{
|
||||
"command": "gitlens.showQuickCommitDetails",
|
||||
"when": "gitlens:enabled && view == gitlens.gitExplorer && viewItem == gitlens:commit",
|
||||
"command": "gitlens.copyShaToClipboard",
|
||||
"when": "view == gitlens.gitExplorer && viewItem == gitlens:commit",
|
||||
"group": "4_gitlens@1"
|
||||
},
|
||||
{
|
||||
"command": "gitlens.copyMessageToClipboard",
|
||||
"when": "view == gitlens.gitExplorer && viewItem == gitlens:commit",
|
||||
"group": "4_gitlens@2"
|
||||
},
|
||||
{
|
||||
"command": "gitlens.showQuickCommitDetails",
|
||||
"when": "view == gitlens.gitExplorer && viewItem == gitlens:commit",
|
||||
"group": "5_gitlens@1"
|
||||
},
|
||||
{
|
||||
"command": "gitlens.gitExplorer.openChanges",
|
||||
"when": "gitlens:enabled && view == gitlens.gitExplorer && viewItem == gitlens:commit-file",
|
||||
"when": "view == gitlens.gitExplorer && viewItem == gitlens:commit-file",
|
||||
"group": "1_gitlens@1"
|
||||
},
|
||||
{
|
||||
"command": "gitlens.gitExplorer.openChangesWithWorking",
|
||||
"when": "gitlens:enabled && view == gitlens.gitExplorer && viewItem == gitlens:commit-file",
|
||||
"when": "view == gitlens.gitExplorer && viewItem == gitlens:commit-file",
|
||||
"group": "1_gitlens@2"
|
||||
},
|
||||
{
|
||||
"command": "gitlens.gitExplorer.openFile",
|
||||
"when": "gitlens:enabled && view == gitlens.gitExplorer && viewItem == gitlens:commit-file",
|
||||
"when": "view == gitlens.gitExplorer && viewItem == gitlens:commit-file",
|
||||
"group": "2_gitlens@1"
|
||||
},
|
||||
{
|
||||
"command": "gitlens.gitExplorer.openFileRevision",
|
||||
"when": "gitlens:enabled && view == gitlens.gitExplorer && viewItem == gitlens:commit-file",
|
||||
"when": "view == gitlens.gitExplorer && viewItem == gitlens:commit-file",
|
||||
"group": "2_gitlens@2"
|
||||
},
|
||||
{
|
||||
"command": "gitlens.openFileInRemote",
|
||||
"when": "gitlens:enabled && gitlens:hasRemotes && view == gitlens.gitExplorer && viewItem == gitlens:commit-file",
|
||||
"when": "gitlens:hasRemotes && view == gitlens.gitExplorer && viewItem == gitlens:commit-file",
|
||||
"group": "3_gitlens@1"
|
||||
},
|
||||
{
|
||||
"command": "gitlens.gitExplorer.openFileRevisionInRemote",
|
||||
"when": "gitlens:enabled && gitlens:hasRemotes && view == gitlens.gitExplorer && viewItem == gitlens:commit-file",
|
||||
"when": "gitlens:hasRemotes && view == gitlens.gitExplorer && viewItem == gitlens:commit-file",
|
||||
"group": "3_gitlens@2"
|
||||
},
|
||||
{
|
||||
"command": "gitlens.gitExplorer.applyChanges",
|
||||
"when": "gitlens:enabled && view == gitlens.gitExplorer && viewItem == gitlens:commit-file",
|
||||
"when": "view == gitlens.gitExplorer && viewItem == gitlens:commit-file",
|
||||
"group": "4_gitlens@1"
|
||||
},
|
||||
{
|
||||
"command": "gitlens.showQuickFileHistory",
|
||||
"when": "gitlens:isTracked && view == gitlens.gitExplorer && viewItem == gitlens:commit-file && gitlens:gitExplorer:view == repository",
|
||||
"when": "view == gitlens.gitExplorer && viewItem == gitlens:commit-file && gitlens:gitExplorer:view == repository",
|
||||
"group": "5_gitlens@1"
|
||||
},
|
||||
{
|
||||
"command": "gitlens.showQuickCommitFileDetails",
|
||||
"when": "gitlens:enabled && view == gitlens.gitExplorer && viewItem == gitlens:commit-file",
|
||||
"when": "view == gitlens.gitExplorer && viewItem == gitlens:commit-file",
|
||||
"group": "5_gitlens@2"
|
||||
},
|
||||
{
|
||||
"command": "gitlens.gitExplorer.openFile",
|
||||
"when": "gitlens:enabled && view == gitlens.gitExplorer && viewItem == gitlens:file-history",
|
||||
"when": "view == gitlens.gitExplorer && viewItem == gitlens:file-history",
|
||||
"group": "1_gitlens@1"
|
||||
},
|
||||
{
|
||||
"command": "gitlens.openFileInRemote",
|
||||
"when": "gitlens:enabled && gitlens:hasRemotes && view == gitlens.gitExplorer && viewItem == gitlens:file-history",
|
||||
"when": "gitlens:hasRemotes && view == gitlens.gitExplorer && viewItem == gitlens:file-history",
|
||||
"group": "1_gitlens@2"
|
||||
},
|
||||
{
|
||||
"command": "gitlens.openBranchesInRemote",
|
||||
"when": "gitlens:enabled && gitlens:hasRemotes && view == gitlens.gitExplorer && viewItem == gitlens:remote",
|
||||
"when": "gitlens:hasRemotes && view == gitlens.gitExplorer && viewItem == gitlens:remote",
|
||||
"group": "1_gitlens@1"
|
||||
},
|
||||
{
|
||||
"command": "gitlens.openRepoInRemote",
|
||||
"when": "gitlens:enabled && gitlens:hasRemotes && view == gitlens.gitExplorer && viewItem == gitlens:remote",
|
||||
"when": "gitlens:hasRemotes && view == gitlens.gitExplorer && viewItem == gitlens:remote",
|
||||
"group": "1_gitlens@2"
|
||||
},
|
||||
{
|
||||
"command": "gitlens.stashSave",
|
||||
"when": "gitlens:enabled && view == gitlens.gitExplorer && viewItem == gitlens:stashes",
|
||||
"when": "view == gitlens.gitExplorer && viewItem == gitlens:stashes",
|
||||
"group": "1_gitlens@1"
|
||||
},
|
||||
{
|
||||
"command": "gitlens.stashApply",
|
||||
"when": "gitlens:enabled && view == gitlens.gitExplorer && viewItem == gitlens:stash",
|
||||
"when": "view == gitlens.gitExplorer && viewItem == gitlens:stash",
|
||||
"group": "1_gitlens@1"
|
||||
},
|
||||
{
|
||||
"command": "gitlens.stashDelete",
|
||||
"when": "gitlens:enabled && view == gitlens.gitExplorer && viewItem == gitlens:stash",
|
||||
"when": "view == gitlens.gitExplorer && viewItem == gitlens:stash",
|
||||
"group": "1_gitlens@2"
|
||||
},
|
||||
{
|
||||
"command": "gitlens.copyMessageToClipboard",
|
||||
"when": "gitlens:enabled && view == gitlens.gitExplorer && viewItem == gitlens:stash",
|
||||
"command": "gitlens.gitExplorer.openChangedFileChanges",
|
||||
"when": "view == gitlens.gitExplorer && viewItem == gitlens:stash",
|
||||
"group": "2_gitlens@1"
|
||||
},
|
||||
{
|
||||
"command": "gitlens.gitExplorer.openChangedFileChangesWithWorking",
|
||||
"when": "view == gitlens.gitExplorer && viewItem == gitlens:stash",
|
||||
"group": "2_gitlens@1"
|
||||
},
|
||||
{
|
||||
"command": "gitlens.gitExplorer.openChangedFiles",
|
||||
"when": "gitlens:enabled && view == gitlens.gitExplorer && viewItem == gitlens:stash",
|
||||
"when": "view == gitlens.gitExplorer && viewItem == gitlens:stash",
|
||||
"group": "3_gitlens@1"
|
||||
},
|
||||
{
|
||||
"command": "gitlens.gitExplorer.openChangedFileRevisions",
|
||||
"when": "gitlens:enabled && view == gitlens.gitExplorer && viewItem == gitlens:stash",
|
||||
"when": "view == gitlens.gitExplorer && viewItem == gitlens:stash",
|
||||
"group": "3_gitlens@2"
|
||||
},
|
||||
{
|
||||
"command": "gitlens.gitExplorer.openChanges",
|
||||
"when": "gitlens:enabled && view == gitlens.gitExplorer && viewItem == gitlens:stash-file",
|
||||
"group": "1_gitlens@1"
|
||||
},
|
||||
{
|
||||
"command": "gitlens.gitExplorer.openChangesWithWorking",
|
||||
"when": "gitlens:enabled && view == gitlens.gitExplorer && viewItem == gitlens:stash-file",
|
||||
"group": "1_gitlens@2"
|
||||
},
|
||||
{
|
||||
"command": "gitlens.gitExplorer.openFile",
|
||||
"when": "gitlens:enabled && view == gitlens.gitExplorer && viewItem == gitlens:stash-file",
|
||||
"group": "2_gitlens@1"
|
||||
},
|
||||
{
|
||||
"command": "gitlens.gitExplorer.openFileRevision",
|
||||
"when": "gitlens:enabled && view == gitlens.gitExplorer && viewItem == gitlens:stash-file",
|
||||
"group": "2_gitlens@2"
|
||||
},
|
||||
{
|
||||
"command": "gitlens.openFileInRemote",
|
||||
"when": "gitlens:enabled && gitlens:hasRemotes && view == gitlens.gitExplorer && viewItem == gitlens:stash-file",
|
||||
"group": "3_gitlens@1"
|
||||
"command": "gitlens.copyMessageToClipboard",
|
||||
"when": "view == gitlens.gitExplorer && viewItem == gitlens:stash",
|
||||
"group": "4_gitlens@1"
|
||||
},
|
||||
{
|
||||
"command": "gitlens.gitExplorer.applyChanges",
|
||||
"when": "gitlens:enabled && view == gitlens.gitExplorer && viewItem == gitlens:stash-file",
|
||||
"when": "view == gitlens.gitExplorer && viewItem == gitlens:stash-file",
|
||||
"group": "1_gitlens@1"
|
||||
},
|
||||
{
|
||||
"command": "gitlens.gitExplorer.openChanges",
|
||||
"when": "view == gitlens.gitExplorer && viewItem == gitlens:stash-file",
|
||||
"group": "2_gitlens@1"
|
||||
},
|
||||
{
|
||||
"command": "gitlens.gitExplorer.openChangesWithWorking",
|
||||
"when": "view == gitlens.gitExplorer && viewItem == gitlens:stash-file",
|
||||
"group": "2_gitlens@2"
|
||||
},
|
||||
{
|
||||
"command": "gitlens.gitExplorer.openFile",
|
||||
"when": "view == gitlens.gitExplorer && viewItem == gitlens:stash-file",
|
||||
"group": "3_gitlens@1"
|
||||
},
|
||||
{
|
||||
"command": "gitlens.gitExplorer.openFileRevision",
|
||||
"when": "view == gitlens.gitExplorer && viewItem == gitlens:stash-file",
|
||||
"group": "3_gitlens@2"
|
||||
},
|
||||
{
|
||||
"command": "gitlens.openFileInRemote",
|
||||
"when": "gitlens:hasRemotes && view == gitlens.gitExplorer && viewItem == gitlens:stash-file",
|
||||
"group": "4_gitlens@1"
|
||||
},
|
||||
{
|
||||
"command": "gitlens.showQuickFileHistory",
|
||||
"when": "gitlens:isTracked && view == gitlens.gitExplorer && viewItem == gitlens:stash-file",
|
||||
"when": "view == gitlens.gitExplorer && viewItem == gitlens:stash-file",
|
||||
"group": "5_gitlens@1"
|
||||
},
|
||||
{
|
||||
"command": "gitlens.openRepoInRemote",
|
||||
"when": "gitlens:enabled && gitlens:hasRemotes && view == gitlens.gitExplorer && viewItem == gitlens:status",
|
||||
"when": "gitlens:hasRemotes && view == gitlens.gitExplorer && viewItem == gitlens:status",
|
||||
"group": "1_gitlens@1"
|
||||
},
|
||||
{
|
||||
"command": "gitlens.gitExplorer.openChanges",
|
||||
"when": "view == gitlens.gitExplorer && viewItem == gitlens:status-file",
|
||||
"group": "1_gitlens@1"
|
||||
},
|
||||
{
|
||||
"command": "gitlens.gitExplorer.openChangesWithWorking",
|
||||
"when": "view == gitlens.gitExplorer && viewItem == gitlens:status-file",
|
||||
"group": "1_gitlens@2"
|
||||
},
|
||||
{
|
||||
"command": "gitlens.gitExplorer.openFile",
|
||||
"when": "view == gitlens.gitExplorer && viewItem == gitlens:status-file",
|
||||
"group": "2_gitlens@1"
|
||||
},
|
||||
{
|
||||
"command": "gitlens.gitExplorer.openFileRevision",
|
||||
"when": "view == gitlens.gitExplorer && viewItem == gitlens:status-file",
|
||||
"group": "2_gitlens@2"
|
||||
},
|
||||
{
|
||||
"command": "gitlens.openFileInRemote",
|
||||
"when": "gitlens:hasRemotes && view == gitlens.gitExplorer && viewItem == gitlens:status-file",
|
||||
"group": "3_gitlens@1"
|
||||
},
|
||||
{
|
||||
"command": "gitlens.showQuickFileHistory",
|
||||
"when": "view == gitlens.gitExplorer && viewItem == gitlens:status-file && gitlens:gitExplorer:view == repository",
|
||||
"group": "5_gitlens@1"
|
||||
},
|
||||
{
|
||||
"command": "gitlens.showQuickCommitFileDetails",
|
||||
"when": "view == gitlens.gitExplorer && viewItem == gitlens:status-file",
|
||||
"group": "5_gitlens@2"
|
||||
},
|
||||
{
|
||||
"command": "gitlens.gitExplorer.openFile",
|
||||
"when": "view == gitlens.gitExplorer && viewItem == gitlens:status-file-commits",
|
||||
"group": "1_gitlens@1"
|
||||
},
|
||||
{
|
||||
"command": "gitlens.openFileInRemote",
|
||||
"when": "gitlens:hasRemotes && view == gitlens.gitExplorer && viewItem == gitlens:status-file-commits",
|
||||
"group": "1_gitlens@2"
|
||||
},
|
||||
{
|
||||
"command": "gitlens.gitExplorer.refresh",
|
||||
"when": "gitlens:enabled && view == gitlens.gitExplorer && viewItem != gitlens:commit-file && viewItem != gitlens:stash-file",
|
||||
"when": "view == gitlens.gitExplorer && viewItem != gitlens:commit-file && viewItem != gitlens:stash-file && viewItem != gitlens:status-file",
|
||||
"group": "9_gitlens@1"
|
||||
}
|
||||
]
|
||||
@@ -1802,7 +1937,7 @@
|
||||
{
|
||||
"id": "gitlens.gitExplorer",
|
||||
"name": "GitLens",
|
||||
"when": "gitlens:enabled"
|
||||
"when": "gitlens:enabled && config.gitlens.gitExplorer.enabled"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1832,18 +1967,17 @@
|
||||
"lodash.once": "4.1.1",
|
||||
"moment": "2.18.1",
|
||||
"spawn-rx": "2.0.11",
|
||||
"string-width": "2.1.1",
|
||||
"tmp": "0.0.33"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/copy-paste": "1.1.30",
|
||||
"@types/iconv-lite": "0.0.1",
|
||||
"@types/mocha": "2.2.43",
|
||||
"@types/node": "8.0.28",
|
||||
"@types/node": "8.0.31",
|
||||
"@types/tmp": "0.0.33",
|
||||
"mocha": "3.5.2",
|
||||
"mocha": "3.5.3",
|
||||
"tslint": "5.7.0",
|
||||
"typescript": "2.5.2",
|
||||
"typescript": "2.5.3",
|
||||
"vscode": "1.1.5"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -222,7 +222,7 @@ export class AnnotationController extends Disposable {
|
||||
}
|
||||
|
||||
getProvider(editor: TextEditor | undefined): AnnotationProviderBase | undefined {
|
||||
if (!editor || !editor.document || !this.git.isEditorBlameable(editor)) return undefined;
|
||||
if (editor === undefined || editor.document === undefined || !this.git.isEditorBlameable(editor)) return undefined;
|
||||
|
||||
return this._annotationProviders.get(editor.viewColumn || -1);
|
||||
}
|
||||
@@ -233,7 +233,7 @@ export class AnnotationController extends Disposable {
|
||||
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)) {
|
||||
if (currentProvider !== undefined && TextEditorComparer.equals(currentProvider.editor, editor)) {
|
||||
await currentProvider.selection(shaOrLine);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import { Strings } from '../system';
|
||||
import { Dates, Objects, Strings } from '../system';
|
||||
import { DecorationInstanceRenderOptions, DecorationOptions, MarkdownString, ThemableDecorationRenderOptions } from 'vscode';
|
||||
import { DiffWithCommand, ShowQuickCommitDetailsCommand } from '../commands';
|
||||
import { DiffWithCommand, OpenCommitInRemoteCommand, ShowQuickCommitDetailsCommand } from '../commands';
|
||||
import { IThemeConfig, themeDefaults } from '../configuration';
|
||||
import { GlyphChars } from '../constants';
|
||||
import { CommitFormatter, GitCommit, GitDiffChunkLine, GitService, GitUri, ICommitFormatOptions } from '../gitService';
|
||||
import * as moment from 'moment';
|
||||
|
||||
interface IHeatmapConfig {
|
||||
enabled: boolean;
|
||||
@@ -28,13 +27,13 @@ const escapeMarkdownRegEx = /[`\>\#\*\_\-\+\.]/g;
|
||||
|
||||
export class Annotations {
|
||||
|
||||
static applyHeatmap(decoration: DecorationOptions, date: Date, now: moment.Moment) {
|
||||
static applyHeatmap(decoration: DecorationOptions, date: Date, now: number) {
|
||||
const color = this._getHeatmapColor(now, date);
|
||||
(decoration.renderOptions!.before! as any).borderColor = color;
|
||||
}
|
||||
|
||||
private static _getHeatmapColor(now: moment.Moment, date: Date) {
|
||||
const days = now.diff(moment(date), 'days');
|
||||
private static _getHeatmapColor(now: number, date: Date) {
|
||||
const days = Dates.dateDaysFromNow(date, now);
|
||||
|
||||
if (days <= 2) return '#ffeca7';
|
||||
if (days <= 7) return '#ffdd8c';
|
||||
@@ -48,7 +47,7 @@ export class Annotations {
|
||||
return '#793738';
|
||||
}
|
||||
|
||||
static getHoverMessage(commit: GitCommit, dateFormat: string | null): MarkdownString {
|
||||
static getHoverMessage(commit: GitCommit, dateFormat: string | null, hasRemotes: boolean): MarkdownString {
|
||||
if (dateFormat === null) {
|
||||
dateFormat = 'MMMM Do, YYYY h:MMa';
|
||||
}
|
||||
@@ -65,7 +64,11 @@ export class Annotations {
|
||||
message = `\n\n> ${message}`;
|
||||
}
|
||||
|
||||
const markdown = new MarkdownString(`[\`${commit.shortSha}\`](${ShowQuickCommitDetailsCommand.getMarkdownCommandArgs(commit.sha)}) __${commit.author}__, ${moment(commit.date).fromNow()} _(${moment(commit.date).format(dateFormat)})_${message}`);
|
||||
const openInRemoteCommand = hasRemotes
|
||||
? `${' '.repeat(3)} [\`${GlyphChars.ArrowUpRight}\`](${OpenCommitInRemoteCommand.getMarkdownCommandArgs(commit.sha)} "Open in Remote")`
|
||||
: '';
|
||||
|
||||
const markdown = new MarkdownString(`[\`${commit.shortSha}\`](${ShowQuickCommitDetailsCommand.getMarkdownCommandArgs(commit.sha)} "Show Commit Details") __${commit.author}__, ${commit.fromNow()} _(${commit.formatDate(dateFormat)})_ ${openInRemoteCommand} ${message}`);
|
||||
markdown.isTrusted = true;
|
||||
return markdown;
|
||||
}
|
||||
@@ -75,8 +78,8 @@ export class Annotations {
|
||||
|
||||
const codeDiff = this._getCodeDiff(chunkLine);
|
||||
const markdown = new MarkdownString(commit.isUncommitted
|
||||
? `[\`Changes\`](${DiffWithCommand.getMarkdownCommandArgs(commit)}) ${GlyphChars.Dash} _uncommitted_\n${codeDiff}`
|
||||
: `[\`Changes\`](${DiffWithCommand.getMarkdownCommandArgs(commit)}) ${GlyphChars.Dash} [\`${commit.previousShortSha}\`](${ShowQuickCommitDetailsCommand.getMarkdownCommandArgs(commit.previousSha!)}) ${GlyphChars.ArrowLeftRight} [\`${commit.shortSha}\`](${ShowQuickCommitDetailsCommand.getMarkdownCommandArgs(commit.sha)})\n${codeDiff}`);
|
||||
? `[\`Changes\`](${DiffWithCommand.getMarkdownCommandArgs(commit)} "Open Changes") ${GlyphChars.Dash} _uncommitted_\n${codeDiff}`
|
||||
: `[\`Changes\`](${DiffWithCommand.getMarkdownCommandArgs(commit)} "Open Changes") ${GlyphChars.Dash} [\`${commit.previousShortSha}\`](${ShowQuickCommitDetailsCommand.getMarkdownCommandArgs(commit.previousSha!)} "Show Commit Details") ${GlyphChars.ArrowLeftRight} [\`${commit.shortSha}\`](${ShowQuickCommitDetailsCommand.getMarkdownCommandArgs(commit.sha)} "Show Commit Details")\n${codeDiff}`);
|
||||
markdown.isTrusted = true;
|
||||
return markdown;
|
||||
}
|
||||
@@ -98,22 +101,22 @@ export class Annotations {
|
||||
} as DecorationOptions;
|
||||
}
|
||||
|
||||
static detailsHover(commit: GitCommit, dateFormat: string | null): DecorationOptions {
|
||||
const message = this.getHoverMessage(commit, dateFormat);
|
||||
static detailsHover(commit: GitCommit, dateFormat: string | null, hasRemotes: boolean): DecorationOptions {
|
||||
const message = this.getHoverMessage(commit, dateFormat, hasRemotes);
|
||||
return {
|
||||
hoverMessage: message
|
||||
} as DecorationOptions;
|
||||
}
|
||||
|
||||
static gutter(commit: GitCommit, format: string, dateFormatOrFormatOptions: string | null | ICommitFormatOptions, renderOptions: IRenderOptions): DecorationOptions {
|
||||
const content = Strings.pad(CommitFormatter.fromTemplate(format, commit, dateFormatOrFormatOptions), 1, 1);
|
||||
const message = CommitFormatter.fromTemplate(format, commit, dateFormatOrFormatOptions);
|
||||
|
||||
return {
|
||||
renderOptions: {
|
||||
before: {
|
||||
...renderOptions.before,
|
||||
...{
|
||||
contentText: content
|
||||
contentText: Strings.pad(message.replace(/ /g, GlyphChars.Space), 1, 1)
|
||||
}
|
||||
},
|
||||
dark: {
|
||||
@@ -130,9 +133,23 @@ export class Annotations {
|
||||
} as DecorationOptions;
|
||||
}
|
||||
|
||||
static gutterRenderOptions(cfgTheme: IThemeConfig, heatmap: IHeatmapConfig): IRenderOptions {
|
||||
static gutterRenderOptions(cfgTheme: IThemeConfig, heatmap: IHeatmapConfig, options: ICommitFormatOptions): IRenderOptions {
|
||||
const cfgFileTheme = cfgTheme.annotations.file.gutter;
|
||||
|
||||
// Try to get the width of the string, if there is a cap
|
||||
let width = 4; // Start with a padding
|
||||
for (const token of Objects.values(options.tokenOptions!)) {
|
||||
if (token === undefined) continue;
|
||||
|
||||
// If any token is uncapped, kick out and set no max
|
||||
if (token.truncateTo == null) {
|
||||
width = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
width += token.truncateTo;
|
||||
}
|
||||
|
||||
let borderStyle = undefined;
|
||||
let borderWidth = undefined;
|
||||
if (heatmap.enabled) {
|
||||
@@ -149,7 +166,8 @@ export class Annotations {
|
||||
borderStyle: borderStyle,
|
||||
borderWidth: borderWidth,
|
||||
height: '100%',
|
||||
margin: '0 26px -1px 0'
|
||||
margin: '0 26px -1px 0',
|
||||
width: (width > 4) ? `${width}ch` : undefined
|
||||
},
|
||||
dark: {
|
||||
backgroundColor: cfgFileTheme.dark.backgroundColor || undefined,
|
||||
@@ -164,11 +182,12 @@ export class Annotations {
|
||||
} as IRenderOptions;
|
||||
}
|
||||
|
||||
static hover(commit: GitCommit, renderOptions: IRenderOptions, heatmap: boolean, dateFormat: string | null): DecorationOptions {
|
||||
return {
|
||||
hoverMessage: this.getHoverMessage(commit, dateFormat),
|
||||
renderOptions: heatmap ? { before: { ...renderOptions.before } } : undefined
|
||||
static hover(commit: GitCommit, renderOptions: IRenderOptions, now: number): DecorationOptions {
|
||||
const decoration = {
|
||||
renderOptions: { before: { ...renderOptions.before } }
|
||||
} as DecorationOptions;
|
||||
this.applyHeatmap(decoration, commit.date, now);
|
||||
return decoration;
|
||||
}
|
||||
|
||||
static hoverRenderOptions(cfgTheme: IThemeConfig, heatmap: IHeatmapConfig): IRenderOptions {
|
||||
@@ -191,10 +210,11 @@ export class Annotations {
|
||||
truncateMessageAtNewLine: true,
|
||||
dateFormat: dateFormat
|
||||
} as ICommitFormatOptions);
|
||||
|
||||
return {
|
||||
renderOptions: {
|
||||
after: {
|
||||
contentText: Strings.pad(message, 1, 1)
|
||||
contentText: Strings.pad(message.replace(/ /g, GlyphChars.Space), 1, 1)
|
||||
},
|
||||
dark: {
|
||||
after: {
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
'use strict';
|
||||
import { Iterables } from '../system';
|
||||
import { ExtensionContext, Range, TextEditor, TextEditorDecorationType } from 'vscode';
|
||||
import { CancellationToken, Disposable, ExtensionContext, Hover, HoverProvider, languages, Position, Range, TextDocument, TextEditor, TextEditorDecorationType } from 'vscode';
|
||||
import { AnnotationProviderBase } from './annotationProvider';
|
||||
import { GitBlame, GitService, GitUri } from '../gitService';
|
||||
import { Annotations, endOfLineIndex } from './annotations';
|
||||
import { GitBlame, GitCommit, GitService, GitUri } from '../gitService';
|
||||
import { WhitespaceController } from './whitespaceController';
|
||||
|
||||
export abstract class BlameAnnotationProviderBase extends AnnotationProviderBase {
|
||||
export abstract class BlameAnnotationProviderBase extends AnnotationProviderBase implements HoverProvider {
|
||||
|
||||
protected _blame: Promise<GitBlame | undefined>;
|
||||
protected _hoverProviderDisposable: Disposable;
|
||||
|
||||
constructor(context: ExtensionContext, editor: TextEditor, decoration: TextEditorDecorationType | undefined, highlightDecoration: TextEditorDecorationType | undefined, whitespaceController: WhitespaceController | undefined, protected git: GitService, protected uri: GitUri) {
|
||||
super(context, editor, decoration, highlightDecoration, whitespaceController);
|
||||
@@ -15,6 +17,11 @@ export abstract class BlameAnnotationProviderBase extends AnnotationProviderBase
|
||||
this._blame = this.git.getBlameForFile(this.uri);
|
||||
}
|
||||
|
||||
async clear() {
|
||||
this._hoverProviderDisposable && this._hoverProviderDisposable.dispose();
|
||||
super.clear();
|
||||
}
|
||||
|
||||
async selection(shaOrLine?: string | number, blame?: GitBlame) {
|
||||
if (!this.highlightDecoration) return;
|
||||
|
||||
@@ -56,6 +63,7 @@ export abstract class BlameAnnotationProviderBase extends AnnotationProviderBase
|
||||
const blame = await this._blame;
|
||||
return blame !== undefined && blame.lines.length !== 0;
|
||||
}
|
||||
|
||||
protected async getBlame(requiresWhitespaceHack: boolean): Promise<GitBlame | undefined> {
|
||||
let whitespacePromise: Promise<void> | undefined;
|
||||
// HACK: Until https://github.com/Microsoft/vscode/issues/11485 is fixed -- override whitespace (turn off)
|
||||
@@ -64,18 +72,47 @@ export abstract class BlameAnnotationProviderBase extends AnnotationProviderBase
|
||||
}
|
||||
|
||||
let blame: GitBlame | undefined;
|
||||
if (whitespacePromise) {
|
||||
if (whitespacePromise !== undefined) {
|
||||
[blame] = await Promise.all([this._blame, whitespacePromise]);
|
||||
}
|
||||
else {
|
||||
blame = await this._blame;
|
||||
}
|
||||
|
||||
if (blame === undefined || !blame.lines.length) {
|
||||
if (blame === undefined || blame.lines.length === 0) {
|
||||
this.whitespaceController && await this.whitespaceController.restore();
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return blame;
|
||||
}
|
||||
|
||||
registerHoverProvider() {
|
||||
this._hoverProviderDisposable = languages.registerHoverProvider({ pattern: this.uri.fsPath }, this);
|
||||
}
|
||||
|
||||
async provideHover(document: TextDocument, position: Position, token: CancellationToken): Promise<Hover | undefined> {
|
||||
// Avoid double annotations if we are showing the whole-file hover blame annotations
|
||||
if (this._config.blame.line.enabled && this.editor.selection.start.line === position.line) return undefined;
|
||||
|
||||
const cfg = this._config.annotations.file.gutter;
|
||||
if (!cfg.hover.wholeLine && position.character !== 0) return undefined;
|
||||
|
||||
const blame = await this.getBlame(true);
|
||||
if (blame === undefined) return undefined;
|
||||
|
||||
const line = blame.lines[position.line - this.uri.offset];
|
||||
|
||||
const commit = blame.commits.get(line.sha);
|
||||
if (commit === undefined) return undefined;
|
||||
|
||||
// Get the full commit message -- since blame only returns the summary
|
||||
let logCommit: GitCommit | undefined = undefined;
|
||||
if (!commit.isUncommitted) {
|
||||
logCommit = await this.git.getLogCommit(commit.repoPath, commit.uri.fsPath, commit.sha);
|
||||
}
|
||||
|
||||
const message = Annotations.getHoverMessage(logCommit || commit, this._config.defaultDateFormat, this.git.hasRemotes(commit.repoPath));
|
||||
return new Hover(message, document.validateRange(new Range(position.line, 0, position.line, endOfLineIndex)));
|
||||
}
|
||||
}
|
||||
@@ -2,11 +2,11 @@
|
||||
import { Strings } from '../system';
|
||||
import { DecorationOptions, Range } from 'vscode';
|
||||
import { FileAnnotationType } from './annotationController';
|
||||
import { Annotations, endOfLineIndex } from './annotations';
|
||||
import { Annotations } from './annotations';
|
||||
import { BlameAnnotationProviderBase } from './blameAnnotationProvider';
|
||||
import { GlyphChars } from '../constants';
|
||||
import { GitBlameCommit, ICommitFormatOptions } from '../gitService';
|
||||
import * as moment from 'moment';
|
||||
import { Logger } from '../logger';
|
||||
|
||||
export class GutterBlameAnnotationProvider extends BlameAnnotationProviderBase {
|
||||
|
||||
@@ -16,7 +16,7 @@ export class GutterBlameAnnotationProvider extends BlameAnnotationProviderBase {
|
||||
const blame = await this.getBlame(true);
|
||||
if (blame === undefined) return false;
|
||||
|
||||
// console.time('Computing blame annotations...');
|
||||
const start = process.hrtime();
|
||||
|
||||
const cfg = this._config.annotations.file.gutter;
|
||||
|
||||
@@ -32,59 +32,53 @@ export class GutterBlameAnnotationProvider extends BlameAnnotationProviderBase {
|
||||
tokenOptions: tokenOptions
|
||||
};
|
||||
|
||||
const now = moment();
|
||||
const now = Date.now();
|
||||
const offset = this.uri.offset;
|
||||
const renderOptions = Annotations.gutterRenderOptions(this._config.theme, cfg.heatmap);
|
||||
const dateFormat = this._config.defaultDateFormat;
|
||||
const renderOptions = Annotations.gutterRenderOptions(this._config.theme, cfg.heatmap, options);
|
||||
const separateLines = this._config.theme.annotations.file.gutter.separateLines;
|
||||
|
||||
const decorations: DecorationOptions[] = [];
|
||||
const document = this.document;
|
||||
const decorationsMap: { [sha: string]: DecorationOptions | undefined } = Object.create(null);
|
||||
|
||||
let commit: GitBlameCommit | undefined;
|
||||
let compacted = false;
|
||||
let details: DecorationOptions | undefined;
|
||||
let gutter: DecorationOptions | undefined;
|
||||
let previousSha: string | undefined;
|
||||
|
||||
for (const l of blame.lines) {
|
||||
commit = blame.commits.get(l.sha);
|
||||
if (commit === undefined) continue;
|
||||
|
||||
const line = l.line + offset;
|
||||
|
||||
if (previousSha === l.sha) {
|
||||
// Use a shallow copy of the previous decoration options
|
||||
gutter = { ...gutter } as DecorationOptions;
|
||||
|
||||
if (cfg.compact && !compacted) {
|
||||
// Since we are wiping out the contextText make sure to copy the objects
|
||||
gutter.renderOptions = { ...gutter.renderOptions };
|
||||
gutter.renderOptions.before = {
|
||||
...gutter.renderOptions.before,
|
||||
...{ contentText: GlyphChars.Space.repeat(Strings.getWidth(gutter.renderOptions!.before!.contentText!)) }
|
||||
gutter.renderOptions = {
|
||||
...gutter.renderOptions,
|
||||
before: {
|
||||
...gutter.renderOptions!.before,
|
||||
contentText: GlyphChars.Space.repeat(Strings.width(gutter.renderOptions!.before!.contentText!))
|
||||
}
|
||||
};
|
||||
|
||||
if (separateLines) {
|
||||
gutter.renderOptions.dark = { ...gutter.renderOptions.dark };
|
||||
gutter.renderOptions.dark.before = { ...gutter.renderOptions.dark.before, ...{ textDecoration: 'none' } };
|
||||
gutter.renderOptions.light = { ...gutter.renderOptions.light };
|
||||
gutter.renderOptions.light.before = { ...gutter.renderOptions.light.before, ...{ textDecoration: 'none' } };
|
||||
gutter.renderOptions.dark = {
|
||||
...gutter.renderOptions.dark,
|
||||
before: { ...gutter.renderOptions.dark!.before, textDecoration: 'none' }
|
||||
};
|
||||
gutter.renderOptions.light = {
|
||||
...gutter.renderOptions.light,
|
||||
before: { ...gutter.renderOptions.light!.before, textDecoration: 'none' }
|
||||
};
|
||||
}
|
||||
|
||||
compacted = true;
|
||||
}
|
||||
|
||||
const endIndex = document.lineAt(line).firstNonWhitespaceCharacterIndex;
|
||||
gutter.range = new Range(line, 0, line, endIndex);
|
||||
decorations.push(gutter);
|
||||
gutter.range = new Range(line, 0, line, 0);
|
||||
|
||||
if (details !== undefined) {
|
||||
details = { ...details } as DecorationOptions;
|
||||
details.range = cfg.hover.wholeLine
|
||||
? document.validateRange(new Range(line, 0, line, endOfLineIndex))
|
||||
: gutter.range;
|
||||
decorations.push(details);
|
||||
}
|
||||
decorations.push(gutter);
|
||||
|
||||
continue;
|
||||
}
|
||||
@@ -92,30 +86,43 @@ export class GutterBlameAnnotationProvider extends BlameAnnotationProviderBase {
|
||||
compacted = false;
|
||||
previousSha = l.sha;
|
||||
|
||||
gutter = decorationsMap[l.sha];
|
||||
if (gutter !== undefined) {
|
||||
gutter = {
|
||||
...gutter,
|
||||
range: new Range(line, 0, line, 0)
|
||||
} as DecorationOptions;
|
||||
|
||||
decorations.push(gutter);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
commit = blame.commits.get(l.sha);
|
||||
if (commit === undefined) continue;
|
||||
|
||||
gutter = Annotations.gutter(commit, cfg.format, options, renderOptions);
|
||||
|
||||
if (cfg.heatmap.enabled) {
|
||||
Annotations.applyHeatmap(gutter, commit.date, now);
|
||||
}
|
||||
|
||||
const endIndex = document.lineAt(line).firstNonWhitespaceCharacterIndex;
|
||||
gutter.range = new Range(line, 0, line, endIndex);
|
||||
decorations.push(gutter);
|
||||
gutter.range = new Range(line, 0, line, 0);
|
||||
|
||||
if (cfg.hover.details) {
|
||||
details = Annotations.detailsHover(commit, dateFormat);
|
||||
details.range = cfg.hover.wholeLine
|
||||
? document.validateRange(new Range(line, 0, line, endOfLineIndex))
|
||||
: gutter.range;
|
||||
decorations.push(details);
|
||||
}
|
||||
decorations.push(gutter);
|
||||
decorationsMap[l.sha] = gutter;
|
||||
}
|
||||
|
||||
if (decorations.length) {
|
||||
this.editor.setDecorations(this.decoration!, decorations);
|
||||
}
|
||||
|
||||
// console.timeEnd('Computing blame annotations...');
|
||||
const duration = process.hrtime(start);
|
||||
Logger.log(`${(duration[0] * 1000) + Math.floor(duration[1] / 1000000)} ms to compute gutter blame annotations`);
|
||||
|
||||
if (cfg.hover.details) {
|
||||
this.registerHoverProvider();
|
||||
}
|
||||
|
||||
this.selection(shaOrLine, blame);
|
||||
return true;
|
||||
|
||||
@@ -1,63 +1,70 @@
|
||||
'use strict';
|
||||
import { DecorationOptions, Range } from 'vscode';
|
||||
import { FileAnnotationType } from './annotationController';
|
||||
import { Annotations, endOfLineIndex } from './annotations';
|
||||
import { Annotations } from './annotations';
|
||||
import { BlameAnnotationProviderBase } from './blameAnnotationProvider';
|
||||
import { GitBlameCommit } from '../gitService';
|
||||
import * as moment from 'moment';
|
||||
import { Logger } from '../logger';
|
||||
|
||||
export class HoverBlameAnnotationProvider extends BlameAnnotationProviderBase {
|
||||
|
||||
async provideAnnotation(shaOrLine?: string | number): Promise<boolean> {
|
||||
this.annotationType = FileAnnotationType.Hover;
|
||||
|
||||
const blame = await this.getBlame(this._config.annotations.file.hover.heatmap.enabled);
|
||||
if (blame === undefined) return false;
|
||||
|
||||
// console.time('Computing blame annotations...');
|
||||
|
||||
const cfg = this._config.annotations.file.hover;
|
||||
|
||||
const now = moment();
|
||||
const offset = this.uri.offset;
|
||||
const renderOptions = Annotations.hoverRenderOptions(this._config.theme, cfg.heatmap);
|
||||
const dateFormat = this._config.defaultDateFormat;
|
||||
const blame = await this.getBlame(cfg.heatmap.enabled);
|
||||
if (blame === undefined) return false;
|
||||
|
||||
const decorations: DecorationOptions[] = [];
|
||||
const document = this.document;
|
||||
if (cfg.heatmap.enabled) {
|
||||
const start = process.hrtime();
|
||||
|
||||
let commit: GitBlameCommit | undefined;
|
||||
let hover: DecorationOptions | undefined;
|
||||
const now = Date.now();
|
||||
const offset = this.uri.offset;
|
||||
const renderOptions = Annotations.hoverRenderOptions(this._config.theme, cfg.heatmap);
|
||||
|
||||
for (const l of blame.lines) {
|
||||
commit = blame.commits.get(l.sha);
|
||||
if (commit === undefined) continue;
|
||||
const decorations: DecorationOptions[] = [];
|
||||
const decorationsMap: { [sha: string]: DecorationOptions } = Object.create(null);
|
||||
|
||||
const line = l.line + offset;
|
||||
let commit: GitBlameCommit | undefined;
|
||||
let hover: DecorationOptions | undefined;
|
||||
|
||||
hover = Annotations.hover(commit, renderOptions, cfg.heatmap.enabled, dateFormat);
|
||||
for (const l of blame.lines) {
|
||||
const line = l.line + offset;
|
||||
|
||||
hover = decorationsMap[l.sha];
|
||||
|
||||
if (hover !== undefined) {
|
||||
hover = {
|
||||
...hover,
|
||||
range: new Range(line, 0, line, 0)
|
||||
} as DecorationOptions;
|
||||
|
||||
decorations.push(hover);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
commit = blame.commits.get(l.sha);
|
||||
if (commit === undefined) continue;
|
||||
|
||||
hover = Annotations.hover(commit, renderOptions, now);
|
||||
hover.range = new Range(line, 0, line, 0);
|
||||
|
||||
decorations.push(hover);
|
||||
decorationsMap[l.sha] = hover;
|
||||
|
||||
if (cfg.wholeLine) {
|
||||
hover.range = document.validateRange(new Range(line, 0, line, endOfLineIndex));
|
||||
}
|
||||
else {
|
||||
const endIndex = document.lineAt(line).firstNonWhitespaceCharacterIndex;
|
||||
hover.range = new Range(line, 0, line, endIndex);
|
||||
}
|
||||
|
||||
if (cfg.heatmap.enabled) {
|
||||
Annotations.applyHeatmap(hover, commit.date, now);
|
||||
if (decorations.length) {
|
||||
this.editor.setDecorations(this.decoration!, decorations);
|
||||
}
|
||||
|
||||
decorations.push(hover);
|
||||
const duration = process.hrtime(start);
|
||||
Logger.log(`${(duration[0] * 1000) + Math.floor(duration[1] / 1000000)} ms to compute hover blame annotations`);
|
||||
}
|
||||
|
||||
if (decorations.length) {
|
||||
this.editor.setDecorations(this.decoration!, decorations);
|
||||
}
|
||||
|
||||
// console.timeEnd('Computing blame annotations...');
|
||||
|
||||
this.registerHoverProvider();
|
||||
this.selection(shaOrLine, blame);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import { Annotations, endOfLineIndex } from './annotations';
|
||||
import { FileAnnotationType } from './annotationController';
|
||||
import { AnnotationProviderBase } from './annotationProvider';
|
||||
import { GitService, GitUri } from '../gitService';
|
||||
import { Logger } from '../logger';
|
||||
|
||||
export class RecentChangesAnnotationProvider extends AnnotationProviderBase {
|
||||
|
||||
@@ -20,6 +21,8 @@ export class RecentChangesAnnotationProvider extends AnnotationProviderBase {
|
||||
const diff = await this.git.getDiffForFile(this.uri, commit.previousSha);
|
||||
if (diff === undefined) return false;
|
||||
|
||||
const start = process.hrtime();
|
||||
|
||||
const cfg = this._config.annotations.file.recentChanges;
|
||||
const dateFormat = this._config.defaultDateFormat;
|
||||
|
||||
@@ -34,16 +37,11 @@ export class RecentChangesAnnotationProvider extends AnnotationProviderBase {
|
||||
|
||||
if (line.state === 'unchanged') continue;
|
||||
|
||||
let endingIndex = 0;
|
||||
if (cfg.hover.details || cfg.hover.changes) {
|
||||
endingIndex = cfg.hover.wholeLine ? endOfLineIndex : this.editor.document.lineAt(count).firstNonWhitespaceCharacterIndex;
|
||||
}
|
||||
|
||||
const range = this.editor.document.validateRange(new Range(new Position(count, 0), new Position(count, endingIndex)));
|
||||
const range = this.editor.document.validateRange(new Range(new Position(count, 0), new Position(count, endOfLineIndex)));
|
||||
|
||||
if (cfg.hover.details) {
|
||||
decorators.push({
|
||||
hoverMessage: Annotations.getHoverMessage(commit, dateFormat),
|
||||
hoverMessage: Annotations.getHoverMessage(commit, dateFormat, this.git.hasRemotes(commit.repoPath)),
|
||||
range: range
|
||||
} as DecorationOptions);
|
||||
}
|
||||
@@ -62,6 +60,9 @@ export class RecentChangesAnnotationProvider extends AnnotationProviderBase {
|
||||
|
||||
this.editor.setDecorations(this.highlightDecoration!, decorators);
|
||||
|
||||
const duration = process.hrtime(start);
|
||||
Logger.log(`${(duration[0] * 1000) + Math.floor(duration[1] / 1000000)} ms to compute recent changes annotations`);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ export * from './commands/diffWithNext';
|
||||
export * from './commands/diffWithPrevious';
|
||||
export * from './commands/diffWithRevision';
|
||||
export * from './commands/diffWithWorking';
|
||||
export * from './commands/externalDiff';
|
||||
export * from './commands/openChangedFiles';
|
||||
export * from './commands/openBranchesInRemote';
|
||||
export * from './commands/openBranchInRemote';
|
||||
|
||||
@@ -19,6 +19,7 @@ export type Commands =
|
||||
'gitlens.diffWithRevision' |
|
||||
'gitlens.diffWithWorking' |
|
||||
'gitlens.diffLineWithWorking' |
|
||||
'gitlens.externalDiff' |
|
||||
'gitlens.openChangedFiles' |
|
||||
'gitlens.openBranchesInRemote' |
|
||||
'gitlens.openBranchInRemote' |
|
||||
@@ -61,6 +62,7 @@ export const Commands = {
|
||||
DiffWithRevision: 'gitlens.diffWithRevision' as Commands,
|
||||
DiffWithWorking: 'gitlens.diffWithWorking' as Commands,
|
||||
DiffLineWithWorking: 'gitlens.diffLineWithWorking' as Commands,
|
||||
ExternalDiff: 'gitlens.externalDiff' as Commands,
|
||||
OpenChangedFiles: 'gitlens.openChangedFiles' as Commands,
|
||||
OpenBranchesInRemote: 'gitlens.openBranchesInRemote' as Commands,
|
||||
OpenBranchInRemote: 'gitlens.openBranchInRemote' as Commands,
|
||||
|
||||
@@ -101,26 +101,18 @@ export class DiffWithCommand extends ActiveEditorCommand {
|
||||
args.showOptions.selection = new Range(args.line, 0, args.line, 0);
|
||||
}
|
||||
|
||||
let lhsPrefix = '';
|
||||
if (lhs === undefined) {
|
||||
lhsPrefix = 'deleted in ';
|
||||
}
|
||||
else if (args.rhs.sha === GitService.fakeSha) {
|
||||
lhsPrefix = 'added in ';
|
||||
}
|
||||
|
||||
let rhsPrefix = '';
|
||||
if (rhs === undefined) {
|
||||
rhsPrefix = 'deleted in ';
|
||||
}
|
||||
else if (args.lhs.sha === GitService.fakeSha) {
|
||||
else if (lhs === undefined || args.lhs.sha === GitService.fakeSha) {
|
||||
rhsPrefix = 'added in ';
|
||||
}
|
||||
|
||||
if (args.lhs.title === undefined && args.lhs.sha !== GitService.fakeSha) {
|
||||
if (args.lhs.title === undefined && lhs !== undefined && args.lhs.sha !== GitService.fakeSha) {
|
||||
args.lhs.title = (args.lhs.sha === '' || GitService.isUncommitted(args.lhs.sha))
|
||||
? `${path.basename(args.lhs.uri.fsPath)}`
|
||||
: `${path.basename(args.lhs.uri.fsPath)} (${lhsPrefix}${GitService.shortenSha(args.lhs.sha)})`;
|
||||
: `${path.basename(args.lhs.uri.fsPath)} (${GitService.shortenSha(args.lhs.sha)})`;
|
||||
}
|
||||
if (args.rhs.title === undefined && args.rhs.sha !== GitService.fakeSha) {
|
||||
args.rhs.title = (args.rhs.sha === '' || GitService.isUncommitted(args.rhs.sha))
|
||||
|
||||
115
src/commands/externalDiff.ts
Normal file
@@ -0,0 +1,115 @@
|
||||
'use strict';
|
||||
import { commands, SourceControlResourceState, Uri, window } from 'vscode';
|
||||
import { Command, Commands } from './common';
|
||||
import { BuiltInCommands } from '../constants';
|
||||
import { CommandContext } from '../commands';
|
||||
import { GitService } from '../gitService';
|
||||
import { Logger } from '../logger';
|
||||
import { Messages } from '../messages';
|
||||
|
||||
enum Status {
|
||||
INDEX_MODIFIED,
|
||||
INDEX_ADDED,
|
||||
INDEX_DELETED,
|
||||
INDEX_RENAMED,
|
||||
INDEX_COPIED,
|
||||
|
||||
MODIFIED,
|
||||
DELETED,
|
||||
UNTRACKED,
|
||||
IGNORED,
|
||||
|
||||
ADDED_BY_US,
|
||||
ADDED_BY_THEM,
|
||||
DELETED_BY_US,
|
||||
DELETED_BY_THEM,
|
||||
BOTH_ADDED,
|
||||
BOTH_DELETED,
|
||||
BOTH_MODIFIED
|
||||
}
|
||||
|
||||
enum ResourceGroupType {
|
||||
Merge,
|
||||
Index,
|
||||
WorkingTree
|
||||
}
|
||||
|
||||
interface Resource extends SourceControlResourceState {
|
||||
readonly resourceGroupType: ResourceGroupType;
|
||||
readonly type: Status;
|
||||
}
|
||||
|
||||
class ExternalDiffFile {
|
||||
constructor(public uri: Uri, public staged: boolean) {
|
||||
}
|
||||
}
|
||||
|
||||
export interface ExternalDiffCommandArgs {
|
||||
files?: ExternalDiffFile[];
|
||||
}
|
||||
|
||||
export class ExternalDiffCommand extends Command {
|
||||
constructor(private git: GitService) {
|
||||
super(Commands.ExternalDiff);
|
||||
}
|
||||
|
||||
protected async preExecute(context: CommandContext, args: ExternalDiffCommandArgs = {}): Promise<any> {
|
||||
if (context.type === 'scm-states') {
|
||||
args = { ...args };
|
||||
args.files = context.scmResourceStates.map<ExternalDiffFile>((_: Resource) => new ExternalDiffFile(_.resourceUri, _.resourceGroupType === ResourceGroupType.Index));
|
||||
|
||||
return this.execute(args);
|
||||
} else if (context.type === 'scm-groups') {
|
||||
const isModified = (status: Status): boolean => status === Status.BOTH_MODIFIED || status === Status.INDEX_MODIFIED || status === Status.MODIFIED;
|
||||
|
||||
args = { ...args };
|
||||
args.files = context.scmResourceGroups[0].resourceStates.filter((_: Resource) => isModified(_.type)).map<ExternalDiffFile>((_: Resource) => new ExternalDiffFile(_.resourceUri, _.resourceGroupType === ResourceGroupType.Index));
|
||||
|
||||
return this.execute(args);
|
||||
}
|
||||
|
||||
return this.execute(args);
|
||||
}
|
||||
|
||||
async execute(args: ExternalDiffCommandArgs = {}) {
|
||||
try {
|
||||
const diffTool = await this.git.getConfig('diff.tool');
|
||||
if (!diffTool) {
|
||||
const result = await window.showWarningMessage(`Unable to open file 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'));
|
||||
}
|
||||
|
||||
const repoPath = await this.git.getRepoPathFromUri(undefined);
|
||||
if (!repoPath) return Messages.showNoRepositoryWarningMessage(`Unable to open changed files`);
|
||||
|
||||
if (!args.files) {
|
||||
const status = await this.git.getStatusForRepo(repoPath);
|
||||
if (status === undefined) return window.showWarningMessage(`Unable to open changed files`);
|
||||
|
||||
args.files = [];
|
||||
|
||||
for (const file of status.files) {
|
||||
if (file.indexStatus === 'M') {
|
||||
args.files.push(new ExternalDiffFile(file.Uri, true));
|
||||
}
|
||||
|
||||
if (file.workTreeStatus === 'M') {
|
||||
args.files.push(new ExternalDiffFile(file.Uri, false));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const file of args.files) {
|
||||
this.git.openDiffTool(repoPath, file.uri, file.staged);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
catch (ex) {
|
||||
Logger.error(ex, 'ExternalDiffCommand');
|
||||
return window.showErrorMessage(`Unable to open external diff. See output channel for more details`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
'use strict';
|
||||
import { Arrays } from '../system';
|
||||
import { commands, TextEditor, Uri, window } from 'vscode';
|
||||
import { ActiveEditorCommand, CommandContext, Commands, getCommandUri, isCommandViewContextWithBranch } from './common';
|
||||
import { GlyphChars } from '../constants';
|
||||
@@ -52,7 +51,8 @@ export class OpenBranchInRemoteCommand extends ActiveEditorCommand {
|
||||
if (args.branch === undefined) return undefined;
|
||||
}
|
||||
|
||||
const remotes = Arrays.uniqueBy(await this.git.getRemotes(repoPath), _ => _.url, _ => !!_.provider);
|
||||
const remotes = (await this.git.getRemotes(repoPath)).filter(r => r.provider !== undefined);
|
||||
|
||||
return commands.executeCommand(Commands.OpenInRemote, uri, {
|
||||
resource: {
|
||||
type: 'branch',
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
'use strict';
|
||||
import { Arrays } from '../system';
|
||||
import { commands, TextEditor, Uri, window } from 'vscode';
|
||||
import { ActiveEditorCommand, CommandContext, Commands, getCommandUri, isCommandViewContextWithRemote } from './common';
|
||||
import { GitService, GitUri } from '../gitService';
|
||||
@@ -34,7 +33,7 @@ export class OpenBranchesInRemoteCommand extends ActiveEditorCommand {
|
||||
if (!repoPath) return undefined;
|
||||
|
||||
try {
|
||||
const remotes = Arrays.uniqueBy(await this.git.getRemotes(repoPath), r => r.url, r => !!r.provider);
|
||||
const remotes = (await this.git.getRemotes(repoPath)).filter(r => r.provider !== undefined);
|
||||
|
||||
return commands.executeCommand(Commands.OpenInRemote, uri, {
|
||||
resource: {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
'use strict';
|
||||
import { Arrays } from '../system';
|
||||
import { commands, TextEditor, Uri, window } from 'vscode';
|
||||
import { ActiveEditorCommand, CommandContext, Commands, getCommandUri, isCommandViewContextWithCommit } from './common';
|
||||
import { GitBlameCommit, GitService, GitUri } from '../gitService';
|
||||
@@ -13,6 +12,15 @@ export interface OpenCommitInRemoteCommandArgs {
|
||||
|
||||
export class OpenCommitInRemoteCommand extends ActiveEditorCommand {
|
||||
|
||||
static getMarkdownCommandArgs(sha: string): string;
|
||||
static getMarkdownCommandArgs(args: OpenCommitInRemoteCommandArgs): string;
|
||||
static getMarkdownCommandArgs(argsOrSha: OpenCommitInRemoteCommandArgs | string): string {
|
||||
const args = typeof argsOrSha === 'string'
|
||||
? { sha: argsOrSha }
|
||||
: argsOrSha;
|
||||
return super.getMarkdownCommandArgsCore<OpenCommitInRemoteCommandArgs>(Commands.OpenCommitInRemote, args);
|
||||
}
|
||||
|
||||
constructor(private git: GitService) {
|
||||
super(Commands.OpenCommitInRemote);
|
||||
}
|
||||
@@ -53,7 +61,8 @@ export class OpenCommitInRemoteCommand extends ActiveEditorCommand {
|
||||
args.sha = commit.sha;
|
||||
}
|
||||
|
||||
const remotes = Arrays.uniqueBy(await this.git.getRemotes(gitUri.repoPath), _ => _.url, _ => !!_.provider);
|
||||
const remotes = (await this.git.getRemotes(gitUri.repoPath)).filter(r => r.provider !== undefined);
|
||||
|
||||
return commands.executeCommand(Commands.OpenInRemote, uri, {
|
||||
resource: {
|
||||
type: 'commit',
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
'use strict';
|
||||
import { Arrays } from '../system';
|
||||
import { commands, Range, TextEditor, Uri, window } from 'vscode';
|
||||
import { ActiveEditorCommand, CommandContext, Commands, getCommandUri, isCommandViewContextWithCommit } from './common';
|
||||
import { ActiveEditorCommand, CommandContext, Commands, getCommandUri, isCommandViewContextWithBranch, isCommandViewContextWithCommit } from './common';
|
||||
import { GitService, GitUri } from '../gitService';
|
||||
import { Logger } from '../logger';
|
||||
import { OpenInRemoteCommandArgs } from './openInRemote';
|
||||
|
||||
export interface OpenFileInRemoteCommandArgs {
|
||||
branch?: string;
|
||||
range?: boolean;
|
||||
}
|
||||
|
||||
@@ -20,6 +20,9 @@ export class OpenFileInRemoteCommand extends ActiveEditorCommand {
|
||||
if (isCommandViewContextWithCommit(context)) {
|
||||
args = { ...args };
|
||||
args.range = false;
|
||||
if (isCommandViewContextWithBranch(context)) {
|
||||
args.branch = context.node.branch !== undefined ? context.node.branch.name : undefined;
|
||||
}
|
||||
return this.execute(context.editor, context.node.commit.uri, args);
|
||||
}
|
||||
|
||||
@@ -33,18 +36,23 @@ export class OpenFileInRemoteCommand extends ActiveEditorCommand {
|
||||
const gitUri = await GitUri.fromUri(uri, this.git);
|
||||
if (!gitUri.repoPath) return undefined;
|
||||
|
||||
const branch = await this.git.getBranch(gitUri.repoPath);
|
||||
if (args.branch === undefined) {
|
||||
const branch = await this.git.getBranch(gitUri.repoPath);
|
||||
if (branch !== undefined) {
|
||||
args.branch = branch.name;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const remotes = Arrays.uniqueBy(await this.git.getRemotes(gitUri.repoPath), _ => _.url, _ => !!_.provider);
|
||||
const remotes = (await this.git.getRemotes(gitUri.repoPath)).filter(r => r.provider !== undefined);
|
||||
const range = (args.range && editor !== undefined)
|
||||
? new Range(editor.selection.start.with({ line: editor.selection.start.line + 1 }), editor.selection.end.with({ line: editor.selection.end.line + 1 }))
|
||||
: undefined;
|
||||
|
||||
return commands.executeCommand(Commands.OpenInRemote, uri, {
|
||||
resource: {
|
||||
type: 'file',
|
||||
branch: branch === undefined ? 'Current' : branch.name,
|
||||
type: gitUri.sha === undefined ? 'file' : 'revision',
|
||||
branch: args.branch === undefined ? 'Current' : args.branch,
|
||||
fileName: gitUri.getRelativePath(),
|
||||
range: range,
|
||||
sha: gitUri.sha
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
'use strict';
|
||||
import { Arrays } from '../system';
|
||||
import { commands, TextEditor, Uri, window } from 'vscode';
|
||||
import { ActiveEditorCommand, CommandContext, Commands, getCommandUri, isCommandViewContextWithRemote } from './common';
|
||||
import { GitService, GitUri } from '../gitService';
|
||||
@@ -34,7 +33,7 @@ export class OpenRepoInRemoteCommand extends ActiveEditorCommand {
|
||||
if (!repoPath) return undefined;
|
||||
|
||||
try {
|
||||
const remotes = Arrays.uniqueBy(await this.git.getRemotes(repoPath), r => r.url, r => !!r.provider);
|
||||
const remotes = (await this.git.getRemotes(repoPath)).filter(r => r.provider !== undefined);
|
||||
|
||||
return commands.executeCommand(Commands.OpenInRemote, uri, {
|
||||
resource: {
|
||||
|
||||
@@ -11,7 +11,7 @@ export class ResetSuppressedWarningsCommand extends Command {
|
||||
}
|
||||
|
||||
async execute() {
|
||||
for (const key of Objects.values<string>(SuppressedKeys)) {
|
||||
for (const key of Objects.values(SuppressedKeys)) {
|
||||
await this.context.globalState.update(key, false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,10 +48,21 @@ export type CustomRemoteType =
|
||||
'GitLab';
|
||||
export const CustomRemoteType = {
|
||||
Bitbucket: 'Bitbucket' as CustomRemoteType,
|
||||
BitbucketServer: 'BitbucketServer' as CustomRemoteType,
|
||||
GitHub: 'GitHub' 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 =
|
||||
'gitlens.toggleFileBlame' |
|
||||
'gitlens.showBlameHistory' |
|
||||
@@ -131,6 +142,24 @@ export interface ICodeLensLanguageLocation {
|
||||
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 {
|
||||
type: CustomRemoteType;
|
||||
domain: string;
|
||||
@@ -250,7 +279,6 @@ export interface IConfig {
|
||||
hover: {
|
||||
details: boolean;
|
||||
changes: boolean;
|
||||
wholeLine: boolean;
|
||||
};
|
||||
};
|
||||
};
|
||||
@@ -274,6 +302,8 @@ export interface IConfig {
|
||||
};
|
||||
|
||||
blame: {
|
||||
ignoreWhitespace: boolean;
|
||||
|
||||
file: {
|
||||
annotationType: FileAnnotationType;
|
||||
lineHighlight: {
|
||||
@@ -314,15 +344,7 @@ export interface IConfig {
|
||||
|
||||
defaultDateFormat: string | null;
|
||||
|
||||
gitExplorer: {
|
||||
view: GitExplorerView;
|
||||
showTrackingBranch: boolean;
|
||||
commitFormat: string;
|
||||
commitFileFormat: string;
|
||||
stashFormat: string;
|
||||
stashFileFormat: string;
|
||||
// dateFormat: string | null;
|
||||
};
|
||||
gitExplorer: IGitExplorerConfig;
|
||||
|
||||
remotes: IRemotesConfig[];
|
||||
|
||||
|
||||
@@ -77,12 +77,16 @@ export type GlyphChars = '\u21a9' |
|
||||
'\u2937' |
|
||||
'\u2190' |
|
||||
'\u2194' |
|
||||
'\u2192' |
|
||||
'\u21e8' |
|
||||
'\u2191' |
|
||||
'\u2197' |
|
||||
'\u2217' |
|
||||
'\u2713' |
|
||||
'\u2014' |
|
||||
'\u2022' |
|
||||
'\u2026' |
|
||||
'\u270E' |
|
||||
'\u00a0' |
|
||||
'\u200b';
|
||||
export const GlyphChars = {
|
||||
@@ -91,17 +95,26 @@ export const GlyphChars = {
|
||||
ArrowDropRight: '\u2937' as GlyphChars,
|
||||
ArrowLeft: '\u2190' as GlyphChars,
|
||||
ArrowLeftRight: '\u2194' as GlyphChars,
|
||||
ArrowRight: '\u2192' as GlyphChars,
|
||||
ArrowRightHollow: '\u21e8' as GlyphChars,
|
||||
ArrowUp: '\u2191' as GlyphChars,
|
||||
ArrowUpRight: '\u2197' as GlyphChars,
|
||||
Asterisk: '\u2217' as GlyphChars,
|
||||
Check: '\u2713' as GlyphChars,
|
||||
Dash: '\u2014' as GlyphChars,
|
||||
Dot: '\u2022' as GlyphChars,
|
||||
Ellipsis: '\u2026' as GlyphChars,
|
||||
Pensil: '\u270E' as GlyphChars,
|
||||
Space: '\u00a0' as GlyphChars,
|
||||
ZeroWidthSpace: '\u200b' as GlyphChars
|
||||
};
|
||||
|
||||
export type WorkspaceState = 'gitlensVersion';
|
||||
export type GlobalState = 'gitlensVersion';
|
||||
export const GlobalState = {
|
||||
GitLensVersion: 'gitlensVersion' as GlobalState
|
||||
};
|
||||
|
||||
export type WorkspaceState = 'gitlens:gitExplorer:view';
|
||||
export const WorkspaceState = {
|
||||
GitLensVersion: 'gitlensVersion' as WorkspaceState
|
||||
GitExplorerView: 'gitlens:gitExplorer:view' as WorkspaceState
|
||||
};
|
||||
@@ -295,12 +295,10 @@ export class CurrentLineController extends Disposable {
|
||||
const decorationOptions: DecorationOptions[] = [];
|
||||
|
||||
let showChanges = false;
|
||||
let showChangesStartIndex = 0;
|
||||
let showChangesInStartingWhitespace = false;
|
||||
|
||||
let showDetails = false;
|
||||
let showDetailsStartIndex = 0;
|
||||
let showDetailsInStartingWhitespace = false;
|
||||
|
||||
let showAtStart = false;
|
||||
let showStartIndex = 0;
|
||||
|
||||
switch (state.annotationType) {
|
||||
case LineAnnotationType.Trailing: {
|
||||
@@ -308,21 +306,7 @@ export class CurrentLineController extends Disposable {
|
||||
|
||||
showChanges = cfgAnnotations.hover.changes;
|
||||
showDetails = cfgAnnotations.hover.details;
|
||||
|
||||
if (cfgAnnotations.hover.wholeLine) {
|
||||
showChangesStartIndex = 0;
|
||||
showChangesInStartingWhitespace = false;
|
||||
|
||||
showDetailsStartIndex = 0;
|
||||
showDetailsInStartingWhitespace = false;
|
||||
}
|
||||
else {
|
||||
showChangesStartIndex = endOfLineIndex;
|
||||
showChangesInStartingWhitespace = true;
|
||||
|
||||
showDetailsStartIndex = endOfLineIndex;
|
||||
showDetailsInStartingWhitespace = true;
|
||||
}
|
||||
showStartIndex = cfgAnnotations.hover.wholeLine ? 0 : endOfLineIndex;
|
||||
|
||||
const decoration = Annotations.trailing(commit, cfgAnnotations.format, cfgAnnotations.dateFormat === null ? this._config.defaultDateFormat : cfgAnnotations.dateFormat, this._config.theme);
|
||||
decoration.range = editor.document.validateRange(new Range(line, endOfLineIndex, line, endOfLineIndex));
|
||||
@@ -334,12 +318,8 @@ export class CurrentLineController extends Disposable {
|
||||
const cfgAnnotations = this._config.annotations.line.hover;
|
||||
|
||||
showChanges = cfgAnnotations.changes;
|
||||
showChangesStartIndex = 0;
|
||||
showChangesInStartingWhitespace = false;
|
||||
|
||||
showDetails = cfgAnnotations.details;
|
||||
showDetailsStartIndex = 0;
|
||||
showDetailsInStartingWhitespace = false;
|
||||
showStartIndex = 0;
|
||||
|
||||
break;
|
||||
}
|
||||
@@ -348,25 +328,15 @@ export class CurrentLineController extends Disposable {
|
||||
if (showDetails || showChanges) {
|
||||
const annotationType = this.annotationController.getAnnotationType(editor);
|
||||
|
||||
const firstNonWhitespace = editor.document.lineAt(line).firstNonWhitespaceCharacterIndex;
|
||||
|
||||
switch (annotationType) {
|
||||
case FileAnnotationType.Gutter: {
|
||||
const cfgHover = this._config.annotations.file.gutter.hover;
|
||||
if (cfgHover.details) {
|
||||
showDetailsInStartingWhitespace = false;
|
||||
if (cfgHover.wholeLine) {
|
||||
// Avoid double annotations if we are showing the whole-file hover blame annotations
|
||||
showDetails = false;
|
||||
showStartIndex = 0;
|
||||
}
|
||||
else {
|
||||
if (showDetailsStartIndex === 0) {
|
||||
showDetailsStartIndex = firstNonWhitespace === 0 ? 1 : firstNonWhitespace;
|
||||
}
|
||||
if (showChangesStartIndex === 0) {
|
||||
showChangesInStartingWhitespace = true;
|
||||
showChangesStartIndex = firstNonWhitespace === 0 ? 1 : firstNonWhitespace;
|
||||
}
|
||||
else if (showStartIndex !== 0) {
|
||||
showAtStart = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -374,20 +344,11 @@ export class CurrentLineController extends Disposable {
|
||||
}
|
||||
case FileAnnotationType.Hover: {
|
||||
const cfgHover = this._config.annotations.file.hover;
|
||||
showDetailsInStartingWhitespace = false;
|
||||
if (cfgHover.wholeLine) {
|
||||
// Avoid double annotations if we are showing the whole-file hover blame annotations
|
||||
showDetails = false;
|
||||
showChangesStartIndex = 0;
|
||||
showStartIndex = 0;
|
||||
}
|
||||
else {
|
||||
if (showDetailsStartIndex === 0) {
|
||||
showDetailsStartIndex = firstNonWhitespace === 0 ? 1 : firstNonWhitespace;
|
||||
}
|
||||
if (showChangesStartIndex === 0) {
|
||||
showChangesInStartingWhitespace = true;
|
||||
showChangesStartIndex = firstNonWhitespace === 0 ? 1 : firstNonWhitespace;
|
||||
}
|
||||
else if (showStartIndex !== 0) {
|
||||
showAtStart = true;
|
||||
}
|
||||
|
||||
break;
|
||||
@@ -395,29 +356,21 @@ export class CurrentLineController extends Disposable {
|
||||
case FileAnnotationType.RecentChanges: {
|
||||
const cfgChanges = this._config.annotations.file.recentChanges.hover;
|
||||
if (cfgChanges.details) {
|
||||
if (cfgChanges.wholeLine) {
|
||||
// Avoid double annotations if we are showing the whole-file hover blame annotations
|
||||
showDetails = false;
|
||||
}
|
||||
else {
|
||||
showDetailsInStartingWhitespace = false;
|
||||
}
|
||||
// Avoid double annotations if we are showing the whole-file hover blame annotations
|
||||
showDetails = false;
|
||||
}
|
||||
|
||||
if (cfgChanges.changes) {
|
||||
if (cfgChanges.wholeLine) {
|
||||
// Avoid double annotations if we are showing the whole-file hover blame annotations
|
||||
showChanges = false;
|
||||
}
|
||||
else {
|
||||
showChangesInStartingWhitespace = false;
|
||||
}
|
||||
// Avoid double annotations if we are showing the whole-file hover blame annotations
|
||||
showChanges = false;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const range = editor.document.validateRange(new Range(line, showStartIndex, line, endOfLineIndex));
|
||||
|
||||
if (showDetails) {
|
||||
// Get the full commit message -- since blame only returns the summary
|
||||
let logCommit: GitCommit | undefined = undefined;
|
||||
@@ -425,29 +378,22 @@ export class CurrentLineController extends Disposable {
|
||||
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 === undefined) return;
|
||||
|
||||
const decoration = Annotations.detailsHover(logCommit || commit, this._config.defaultDateFormat);
|
||||
decoration.range = editor.document.validateRange(new Range(line, showDetailsStartIndex, line, endOfLineIndex));
|
||||
const decoration = Annotations.detailsHover(logCommit || commit, this._config.defaultDateFormat, this.git.hasRemotes((logCommit || commit).repoPath));
|
||||
decoration.range = range;
|
||||
decorationOptions.push(decoration);
|
||||
|
||||
if (showDetailsInStartingWhitespace && showDetailsStartIndex !== 0) {
|
||||
decorationOptions.push(Annotations.withRange(decoration, 0, firstNonWhitespace));
|
||||
if (showAtStart) {
|
||||
decorationOptions.push(Annotations.withRange(decoration, 0, 0));
|
||||
}
|
||||
}
|
||||
|
||||
if (showChanges) {
|
||||
const decoration = await Annotations.changesHover(commit, line, this._uri, this.git);
|
||||
|
||||
// I have no idea why I need this protection -- but it happens
|
||||
if (editor.document === undefined) return;
|
||||
|
||||
decoration.range = editor.document.validateRange(new Range(line, showChangesStartIndex, line, endOfLineIndex));
|
||||
decoration.range = range;
|
||||
decorationOptions.push(decoration);
|
||||
|
||||
if (showChangesInStartingWhitespace && showChangesStartIndex !== 0) {
|
||||
decorationOptions.push(Annotations.withRange(decoration, 0, firstNonWhitespace));
|
||||
if (showAtStart) {
|
||||
decorationOptions.push(Annotations.withRange(decoration, 0, 0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import { commands, ExtensionContext, extensions, languages, window, workspace } from 'vscode';
|
||||
import { AnnotationController } from './annotations/annotationController';
|
||||
import { CloseUnchangedFilesCommand, OpenChangedFilesCommand } from './commands';
|
||||
import { ExternalDiffCommand } from './commands';
|
||||
import { OpenBranchesInRemoteCommand, OpenBranchInRemoteCommand, OpenCommitInRemoteCommand, OpenFileInRemoteCommand, OpenInRemoteCommand, OpenRepoInRemoteCommand } from './commands';
|
||||
import { CopyMessageToClipboardCommand, CopyShaToClipboardCommand } from './commands';
|
||||
import { DiffDirectoryCommand, DiffLineWithPreviousCommand, DiffLineWithWorkingCommand, DiffWithBranchCommand, DiffWithCommand, DiffWithNextCommand, DiffWithPreviousCommand, DiffWithRevisionCommand, DiffWithWorkingCommand } from './commands';
|
||||
@@ -16,7 +17,7 @@ import { ShowQuickRepoStatusCommand, ShowQuickStashListCommand } from './command
|
||||
import { StashApplyCommand, StashDeleteCommand, StashSaveCommand } from './commands';
|
||||
import { ToggleCodeLensCommand } from './commands';
|
||||
import { CodeLensLocations, IConfig, LineHighlightLocations } from './configuration';
|
||||
import { ApplicationInsightsKey, CommandContext, ExtensionKey, QualifiedExtensionId, setCommandContext, WorkspaceState } from './constants';
|
||||
import { ApplicationInsightsKey, CommandContext, ExtensionKey, GlobalState, QualifiedExtensionId, setCommandContext } from './constants';
|
||||
import { CodeLensController } from './codeLensController';
|
||||
import { CurrentLineController, LineAnnotationType } from './currentLineController';
|
||||
import { RemoteProviderFactory } from './git/remotes/factory';
|
||||
@@ -71,7 +72,7 @@ export async function activate(context: ExtensionContext) {
|
||||
notifyOnUnsupportedGitVersion(context, gitVersion);
|
||||
notifyOnNewGitLensVersion(context, gitlensVersion);
|
||||
|
||||
await context.globalState.update(WorkspaceState.GitLensVersion, gitlensVersion);
|
||||
await context.globalState.update(GlobalState.GitLensVersion, gitlensVersion);
|
||||
|
||||
const git = new GitService(repoPath);
|
||||
context.subscriptions.push(git);
|
||||
@@ -99,6 +100,7 @@ export async function activate(context: ExtensionContext) {
|
||||
context.subscriptions.push(commands.registerTextEditorCommand('gitlens.computingFileAnnotations', () => { }));
|
||||
|
||||
context.subscriptions.push(new CloseUnchangedFilesCommand(git));
|
||||
context.subscriptions.push(new ExternalDiffCommand(git));
|
||||
context.subscriptions.push(new OpenChangedFilesCommand(git));
|
||||
context.subscriptions.push(new CopyMessageToClipboardCommand(git));
|
||||
context.subscriptions.push(new CopyShaToClipboardCommand(git));
|
||||
@@ -148,7 +150,7 @@ export async function activate(context: ExtensionContext) {
|
||||
export function deactivate() { }
|
||||
|
||||
async function migrateSettings(context: ExtensionContext) {
|
||||
const previousVersion = context.globalState.get<string>(WorkspaceState.GitLensVersion);
|
||||
const previousVersion = context.globalState.get<string>(GlobalState.GitLensVersion);
|
||||
if (previousVersion === undefined) return;
|
||||
|
||||
const [major] = previousVersion.split('.');
|
||||
@@ -274,7 +276,7 @@ async function migrateSettings(context: ExtensionContext) {
|
||||
async function notifyOnNewGitLensVersion(context: ExtensionContext, version: string) {
|
||||
if (context.globalState.get(SuppressedKeys.UpdateNotice, false)) return;
|
||||
|
||||
const previousVersion = context.globalState.get<string>(WorkspaceState.GitLensVersion);
|
||||
const previousVersion = context.globalState.get<string>(GlobalState.GitLensVersion);
|
||||
|
||||
if (previousVersion === undefined) {
|
||||
Logger.log(`GitLens first-time install`);
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
import { Strings } from '../../system';
|
||||
import { GitCommit } from '../models/commit';
|
||||
import { Formatter, IFormatOptions } from './formatter';
|
||||
import * as moment from 'moment';
|
||||
import { GlyphChars } from '../../constants';
|
||||
|
||||
export interface ICommitFormatOptions extends IFormatOptions {
|
||||
@@ -20,7 +19,7 @@ export interface ICommitFormatOptions extends IFormatOptions {
|
||||
export class CommitFormatter extends Formatter<GitCommit, ICommitFormatOptions> {
|
||||
|
||||
get ago() {
|
||||
const ago = moment(this._item.date).fromNow();
|
||||
const ago = this._item.fromNow();
|
||||
return this._padOrTruncate(ago, this._options.tokenOptions!.ago);
|
||||
}
|
||||
|
||||
@@ -30,17 +29,17 @@ export class CommitFormatter extends Formatter<GitCommit, ICommitFormatOptions>
|
||||
}
|
||||
|
||||
get authorAgo() {
|
||||
const authorAgo = `${this._item.author}, ${moment(this._item.date).fromNow()}`;
|
||||
const authorAgo = `${this._item.author}, ${this._item.fromNow()}`;
|
||||
return this._padOrTruncate(authorAgo, this._options.tokenOptions!.authorAgo);
|
||||
}
|
||||
|
||||
get date() {
|
||||
const date = moment(this._item.date).format(this._options.dateFormat!);
|
||||
const date = this._item.formatDate(this._options.dateFormat!);
|
||||
return this._padOrTruncate(date, this._options.tokenOptions!.date);
|
||||
}
|
||||
|
||||
get id() {
|
||||
return this._item.shortSha;
|
||||
return this._item.isUncommitted ? 'index' : this._item.shortSha;
|
||||
}
|
||||
|
||||
get message() {
|
||||
|
||||
@@ -51,7 +51,7 @@ export abstract class Formatter<TItem = any, TOptions extends IFormatOptions = I
|
||||
|
||||
let max = options.truncateTo;
|
||||
|
||||
const width = Strings.getWidth(s);
|
||||
const width = Strings.width(s);
|
||||
if (max === undefined) {
|
||||
if (this.collapsableWhitespace === 0) return s;
|
||||
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
'use strict';
|
||||
import { Strings } from '../../system';
|
||||
import { GlyphChars } from '../../constants';
|
||||
import { Formatter, IFormatOptions } from './formatter';
|
||||
import { GitStatusFile, IGitStatusFile } from '../models/status';
|
||||
import { GitStatusFile, IGitStatusFile, IGitStatusFileWithCommit } from '../models/status';
|
||||
import * as path from 'path';
|
||||
|
||||
export interface IStatusFormatOptions extends IFormatOptions {
|
||||
relativePath?: string;
|
||||
|
||||
tokenOptions?: {
|
||||
directory?: Strings.ITokenOptions;
|
||||
file?: Strings.ITokenOptions;
|
||||
filePath?: Strings.ITokenOptions;
|
||||
path?: Strings.ITokenOptions;
|
||||
@@ -14,21 +18,31 @@ export interface IStatusFormatOptions extends IFormatOptions {
|
||||
|
||||
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() {
|
||||
const file = path.basename(this._item.fileName);
|
||||
return this._padOrTruncate(file, this._options.tokenOptions!.file);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
get working() {
|
||||
const commit = (this._item as IGitStatusFileWithCommit).commit;
|
||||
return (commit !== undefined && commit.isUncommitted) ? `${GlyphChars.Pensil} ${GlyphChars.Space}` : '';
|
||||
}
|
||||
|
||||
static fromTemplate(template: string, status: IGitStatusFile, dateFormat: string | null): string;
|
||||
static fromTemplate(template: string, status: IGitStatusFile, options?: IStatusFormatOptions): string;
|
||||
static fromTemplate(template: string, status: IGitStatusFile, dateFormatOrOptions?: string | null | IStatusFormatOptions): string;
|
||||
|
||||
@@ -14,15 +14,16 @@ export * from './parsers/blameParser';
|
||||
export * from './parsers/branchParser';
|
||||
export * from './parsers/diffParser';
|
||||
export * from './parsers/logParser';
|
||||
export * from './parsers/remoteParser';
|
||||
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 ?`];
|
||||
const defaultBlameParams = [`blame`, `--root`, `--incremental`];
|
||||
const defaultLogParams = [`log`, `--name-status`, `--full-history`, `-M`, `--format=%H -%nauthor %an%nauthor-date %at%nparents %P%nsummary %B%nfilename ?`];
|
||||
const defaultStashParams = [`stash`, `list`, `--name-status`, `--full-history`, `-M`, `--format=%H -%nauthor-date %at%nreflog-selector %gd%nsummary %B%nfilename ?`];
|
||||
|
||||
let defaultEncoding = 'utf8';
|
||||
export function setDefaultEncoding(encoding: string) {
|
||||
@@ -176,15 +177,17 @@ export class Git {
|
||||
|
||||
// Git commands
|
||||
|
||||
static blame(repoPath: string | undefined, fileName: string, sha?: string, startLine?: number, endLine?: number) {
|
||||
static blame(repoPath: string | undefined, fileName: string, sha?: string, options: { ignoreWhitespace?: boolean, startLine?: number, endLine?: number } = {}) {
|
||||
const [file, root] = Git.splitPath(fileName, repoPath);
|
||||
|
||||
const params = [`blame`, `--root`, `--incremental`];
|
||||
const params = [...defaultBlameParams];
|
||||
|
||||
if (startLine != null && endLine != null) {
|
||||
params.push(`-L ${startLine},${endLine}`);
|
||||
if (options.ignoreWhitespace) {
|
||||
params.push('-w');
|
||||
}
|
||||
if (options.startLine != null && options.endLine != null) {
|
||||
params.push(`-L ${options.startLine},${options.endLine}`);
|
||||
}
|
||||
|
||||
if (sha) {
|
||||
params.push(sha);
|
||||
}
|
||||
@@ -256,6 +259,14 @@ export class Git {
|
||||
return gitCommand({ cwd: repoPath }, ...params);
|
||||
}
|
||||
|
||||
static diff_shortstat(repoPath: string, sha?: string) {
|
||||
const params = [`diff`, `--shortstat`, `--no-ext-diff`];
|
||||
if (sha) {
|
||||
params.push(sha);
|
||||
}
|
||||
return gitCommand({ cwd: repoPath }, ...params);
|
||||
}
|
||||
|
||||
static difftool_dirDiff(repoPath: string, sha1: string, sha2?: string) {
|
||||
const params = [`difftool`, `--dir-diff`, sha1];
|
||||
if (sha2) {
|
||||
@@ -265,6 +276,17 @@ export class Git {
|
||||
return gitCommand({ cwd: repoPath }, ...params);
|
||||
}
|
||||
|
||||
static difftool_fileDiff(repoPath: string, fileName: string, staged: boolean) {
|
||||
const params = [`difftool`, `--no-prompt`];
|
||||
if (staged) {
|
||||
params.push('--staged');
|
||||
}
|
||||
params.push('--');
|
||||
params.push(fileName);
|
||||
|
||||
return gitCommand({ cwd: repoPath }, ...params);
|
||||
}
|
||||
|
||||
static log(repoPath: string, sha?: string, maxCount?: number, reverse: boolean = false) {
|
||||
const params = [...defaultLogParams, `-m`];
|
||||
if (maxCount && !reverse) {
|
||||
@@ -329,6 +351,14 @@ export class Git {
|
||||
return gitCommand({ cwd: repoPath }, ...params, ...search);
|
||||
}
|
||||
|
||||
static log_shortstat(repoPath: string, sha?: string) {
|
||||
const params = [`log`, `--shortstat`, `--oneline`];
|
||||
if (sha) {
|
||||
params.push(sha);
|
||||
}
|
||||
return gitCommand({ cwd: repoPath }, ...params);
|
||||
}
|
||||
|
||||
static async ls_files(repoPath: string, fileName: string): Promise<string> {
|
||||
try {
|
||||
return await gitCommand({ cwd: repoPath, overrideErrorHandling: true }, 'ls-files', fileName);
|
||||
@@ -358,7 +388,8 @@ export class Git {
|
||||
return await gitCommand(opts, 'show', args);
|
||||
}
|
||||
catch (ex) {
|
||||
if (/Path \'.*?\' does not exist in/.test(ex && ex.toString())) {
|
||||
const msg = ex && ex.toString();
|
||||
if (/Path \'.*?\' does not exist in/.test(msg) || /Path \'.*?\' exists on disk, but not in /.test(msg)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@@ -400,7 +431,7 @@ export class Git {
|
||||
|
||||
static status(repoPath: string, porcelainVersion: number = 1): Promise<string> {
|
||||
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> {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { Disposable, Event, EventEmitter, TextDocument, TextDocumentChangeEvent, TextEditor, window, workspace } from 'vscode';
|
||||
import { TextDocumentComparer } from '../comparers';
|
||||
import { CommandContext, setCommandContext } from '../constants';
|
||||
import { GitService, GitUri } from '../gitService';
|
||||
import { GitService, GitUri, RepoChangedReasons } from '../gitService';
|
||||
import { Logger } from '../logger';
|
||||
|
||||
export interface BlameabilityChangeEvent {
|
||||
@@ -32,13 +32,13 @@ export class GitContextTracker extends Disposable {
|
||||
subscriptions.push(workspace.onDidChangeConfiguration(this._onConfigurationChanged, this));
|
||||
subscriptions.push(workspace.onDidSaveTextDocument(this._onTextDocumentSaved, this));
|
||||
subscriptions.push(this.git.onDidBlameFail(this._onBlameFailed, this));
|
||||
subscriptions.push(this.git.onDidChangeRepo(this._onRepoChanged, this));
|
||||
|
||||
this._disposable = Disposable.from(...subscriptions);
|
||||
|
||||
setCommandContext(CommandContext.IsRepository, !!this.git.repoPath);
|
||||
|
||||
this._onConfigurationChanged();
|
||||
this._onActiveTextEditorChanged(window.activeTextEditor);
|
||||
}
|
||||
|
||||
dispose() {
|
||||
@@ -55,6 +55,13 @@ export class GitContextTracker extends Disposable {
|
||||
}
|
||||
}
|
||||
|
||||
async _onRepoChanged(reasons: RepoChangedReasons[]) {
|
||||
if (!reasons.includes(RepoChangedReasons.Remotes)) return;
|
||||
|
||||
const gitUri = this._editor === undefined ? undefined : await GitUri.fromUri(this._editor.document.uri, this.git);
|
||||
this._updateContextHasRemotes(gitUri);
|
||||
}
|
||||
|
||||
private _onActiveTextEditorChanged(editor: TextEditor | undefined) {
|
||||
this._editor = editor;
|
||||
this._updateContext(this._gitEnabled ? editor : undefined);
|
||||
|
||||
@@ -42,7 +42,7 @@ export class GitUri extends Uri {
|
||||
}
|
||||
else {
|
||||
const commit = commitOrRepoPath;
|
||||
base._fsPath = path.resolve(commit.repoPath, commit.originalFileName || commit.fileName);
|
||||
base._fsPath = path.resolve(commit.repoPath, commit.originalFileName || commit.fileName || '');
|
||||
|
||||
if (commit.repoPath !== undefined) {
|
||||
this.repoPath = commit.repoPath;
|
||||
@@ -63,11 +63,14 @@ export class GitUri extends Uri {
|
||||
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);
|
||||
if (this.repoPath) {
|
||||
directory = path.relative(this.repoPath, directory);
|
||||
}
|
||||
if (relativeTo !== undefined) {
|
||||
directory = path.relative(relativeTo, directory);
|
||||
}
|
||||
directory = GitService.normalizePath(directory);
|
||||
|
||||
return (!directory || directory === '.')
|
||||
@@ -75,8 +78,12 @@ export class GitUri extends Uri {
|
||||
: `${path.basename(this.fsPath)}${separator}${directory}`;
|
||||
}
|
||||
|
||||
getRelativePath(): string {
|
||||
return GitService.normalizePath(path.relative(this.repoPath || '', this.fsPath));
|
||||
getRelativePath(relativeTo?: string): string {
|
||||
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) {
|
||||
@@ -104,15 +111,19 @@ export class GitUri extends Uri {
|
||||
return new GitUri(uri, repoPathOrCommit);
|
||||
}
|
||||
|
||||
static getDirectory(fileName: string): string {
|
||||
const directory: string | undefined = GitService.normalizePath(path.dirname(fileName));
|
||||
static getDirectory(fileName: string, relativeTo?: string): string {
|
||||
let directory: string | undefined = path.dirname(fileName);
|
||||
if (relativeTo !== undefined) {
|
||||
directory = path.relative(relativeTo, directory);
|
||||
}
|
||||
directory = GitService.normalizePath(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;
|
||||
if (fileNameOrUri instanceof Uri) {
|
||||
if (fileNameOrUri instanceof GitUri) return fileNameOrUri.getFormattedPath(separator);
|
||||
if (fileNameOrUri instanceof GitUri) return fileNameOrUri.getFormattedPath(separator, relativeTo);
|
||||
|
||||
fileName = fileNameOrUri.fsPath;
|
||||
}
|
||||
@@ -120,11 +131,29 @@ export class GitUri extends Uri {
|
||||
fileName = fileNameOrUri;
|
||||
}
|
||||
|
||||
const directory = GitUri.getDirectory(fileName);
|
||||
const directory = GitUri.getDirectory(fileName, relativeTo);
|
||||
return !directory
|
||||
? path.basename(fileName)
|
||||
: `${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 {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
'use strict';
|
||||
import { Strings } from '../../system';
|
||||
import { Dates, Strings } from '../../system';
|
||||
import { Uri } from 'vscode';
|
||||
import { GlyphChars } from '../../constants';
|
||||
import { Git } from '../git';
|
||||
@@ -70,7 +70,23 @@ export class GitCommit {
|
||||
}
|
||||
|
||||
get uri(): Uri {
|
||||
return Uri.file(path.resolve(this.repoPath, this.originalFileName || this.fileName));
|
||||
return Uri.file(path.resolve(this.repoPath, this.originalFileName || this.fileName || ''));
|
||||
}
|
||||
|
||||
private _dateFormatter?: Dates.IDateFormatter;
|
||||
|
||||
formatDate(format: string) {
|
||||
if (this._dateFormatter === undefined) {
|
||||
this._dateFormatter = Dates.toFormatter(this.date);
|
||||
}
|
||||
return this._dateFormatter.format(format);
|
||||
}
|
||||
|
||||
fromNow() {
|
||||
if (this._dateFormatter === undefined) {
|
||||
this._dateFormatter = Dates.toFormatter(this.date);
|
||||
}
|
||||
return this._dateFormatter.fromNow();
|
||||
}
|
||||
|
||||
getFormattedPath(separator: string = Strings.pad(GlyphChars.Dot, 2, 2)): string {
|
||||
|
||||
@@ -33,4 +33,10 @@ export interface GitDiff {
|
||||
chunks: GitDiffChunk[];
|
||||
|
||||
diff?: string;
|
||||
}
|
||||
|
||||
export interface GitDiffShortStat {
|
||||
files: number;
|
||||
insertions: number;
|
||||
deletions: number;
|
||||
}
|
||||
@@ -40,7 +40,12 @@ export class GitLogCommit extends GitCommit {
|
||||
this.status = fileStatus.status;
|
||||
}
|
||||
else {
|
||||
this.fileStatuses = [{ status: status, fileName: fileName, originalFileName: originalFileName } as IGitStatusFile];
|
||||
if (fileName === undefined) {
|
||||
this.fileStatuses = [];
|
||||
}
|
||||
else {
|
||||
this.fileStatuses = [{ status: status, fileName: fileName, originalFileName: originalFileName } as IGitStatusFile];
|
||||
}
|
||||
this.status = status;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,23 +5,9 @@ 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);
|
||||
constructor(public readonly repoPath: string, public readonly name: string, public readonly url: string, public readonly domain: string, public readonly path: string, public readonly types: GitRemoteType[]) {
|
||||
this.provider = RemoteProviderFactory.getRemoteProvider(this.domain, this.path);
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ import { Strings } from '../../system';
|
||||
import { Uri } from 'vscode';
|
||||
import { GlyphChars } from '../../constants';
|
||||
import { GitUri } from '../gitUri';
|
||||
import { GitLogCommit } from './logCommit';
|
||||
import * as path from 'path';
|
||||
|
||||
export interface GitStatus {
|
||||
@@ -19,19 +20,25 @@ export interface GitStatus {
|
||||
files: GitStatusFile[];
|
||||
}
|
||||
|
||||
export declare type GitStatusFileStatus = '!' | '?' | 'A' | 'C' | 'D' | 'M' | 'R' | 'U';
|
||||
export declare type GitStatusFileStatus = '!' | '?' | 'A' | 'C' | 'D' | 'M' | 'R' | 'T' | 'U' | 'X' | 'B';
|
||||
|
||||
export interface IGitStatusFile {
|
||||
status: GitStatusFileStatus;
|
||||
fileName: string;
|
||||
originalFileName?: string;
|
||||
workTreeStatus: GitStatusFileStatus;
|
||||
indexStatus: GitStatusFileStatus;
|
||||
}
|
||||
|
||||
export interface IGitStatusFileWithCommit extends IGitStatusFile {
|
||||
commit: GitLogCommit;
|
||||
}
|
||||
|
||||
export class GitStatusFile implements IGitStatusFile {
|
||||
|
||||
originalFileName?: string;
|
||||
|
||||
constructor(public repoPath: string, public status: GitStatusFileStatus, public fileName: string, public staged: boolean, originalFileName?: string) {
|
||||
constructor(public repoPath: string, public status: GitStatusFileStatus, public workTreeStatus: GitStatusFileStatus, public indexStatus: GitStatusFileStatus, public fileName: string, public staged: boolean, originalFileName?: string) {
|
||||
this.originalFileName = originalFileName;
|
||||
}
|
||||
|
||||
@@ -51,15 +58,19 @@ export class GitStatusFile implements IGitStatusFile {
|
||||
return Uri.file(path.resolve(this.repoPath, this.fileName));
|
||||
}
|
||||
|
||||
static getFormattedDirectory(status: IGitStatusFile, includeOriginal: boolean = false): string {
|
||||
const directory = GitUri.getDirectory(status.fileName);
|
||||
static getFormattedDirectory(status: IGitStatusFile, includeOriginal: boolean = false, relativeTo?: string): string {
|
||||
const directory = GitUri.getDirectory(status.fileName, relativeTo);
|
||||
return (includeOriginal && status.status === 'R' && status.originalFileName)
|
||||
? `${directory} ${Strings.pad(GlyphChars.ArrowLeft, 1, 1)} ${status.originalFileName}`
|
||||
: directory;
|
||||
}
|
||||
|
||||
static getFormattedPath(status: IGitStatusFile, separator: string = Strings.pad(GlyphChars.Dot, 2, 2)): string {
|
||||
return GitUri.getFormattedPath(status.fileName, separator);
|
||||
static getFormattedPath(status: IGitStatusFile, separator: string = Strings.pad(GlyphChars.Dot, 2, 2), relativeTo?: string): string {
|
||||
return GitUri.getFormattedPath(status.fileName, separator, relativeTo);
|
||||
}
|
||||
|
||||
static getRelativePath(status: IGitStatusFile, relativeTo?: string): string {
|
||||
return GitUri.getRelativePath(status.fileName, relativeTo);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,7 +82,10 @@ const statusOcticonsMap = {
|
||||
D: '$(diff-removed)',
|
||||
M: '$(diff-modified)',
|
||||
R: '$(diff-renamed)',
|
||||
U: '$(question)'
|
||||
T: '$(diff-modified)',
|
||||
U: '$(alert)',
|
||||
X: '$(question)',
|
||||
B: '$(question)'
|
||||
};
|
||||
|
||||
export function getGitStatusOcticon(status: GitStatusFileStatus, missing: string = GlyphChars.Space.repeat(4)): string {
|
||||
@@ -86,7 +100,10 @@ const statusIconsMap = {
|
||||
D: 'icon-status-deleted.svg',
|
||||
M: 'icon-status-modified.svg',
|
||||
R: 'icon-status-renamed.svg',
|
||||
U: 'icon-status-conflict.svg'
|
||||
T: 'icon-status-modified.svg',
|
||||
U: 'icon-status-conflict.svg',
|
||||
X: 'icon-status-unknown.svg',
|
||||
B: 'icon-status-unknown.svg'
|
||||
};
|
||||
|
||||
export function getGitStatusIcon(status: GitStatusFileStatus): string {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
'use strict';
|
||||
import { Strings } from '../../system';
|
||||
import { Git, GitAuthor, GitBlame, GitBlameCommit, GitCommitLine } from './../git';
|
||||
import * as moment from 'moment';
|
||||
import * as path from 'path';
|
||||
|
||||
interface BlameEntry {
|
||||
@@ -134,7 +133,7 @@ export class GitBlameParser {
|
||||
}
|
||||
}
|
||||
|
||||
commit = new GitBlameCommit(repoPath!, entry.sha, fileName!, entry.author, moment(`${entry.authorDate} ${entry.authorTimeZone}`, 'X +-HHmm').toDate(), entry.summary!, []);
|
||||
commit = new GitBlameCommit(repoPath!, entry.sha, fileName!, entry.author, new Date(entry.authorDate as any * 1000), entry.summary!, []);
|
||||
|
||||
if (fileName !== entry.fileName) {
|
||||
commit.originalFileName = entry.fileName;
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
'use strict';
|
||||
import { Iterables, Strings } from '../../system';
|
||||
import { GitDiff, GitDiffChunk, GitDiffChunkLine, GitDiffLine } from './../git';
|
||||
import { GitDiff, GitDiffChunk, GitDiffChunkLine, GitDiffLine, GitDiffShortStat } from './../git';
|
||||
|
||||
const unifiedDiffRegex = /^@@ -([\d]+),([\d]+) [+]([\d]+),([\d]+) @@([\s\S]*?)(?=^@@)/gm;
|
||||
const shortStatDiffRegex = /^\s*(\d+)\sfiles? changed(?:,\s+(\d+)\s+insertions?\(\+\))?(?:,\s+(\d+)\s+deletions?\(-\))?/;
|
||||
|
||||
export class GitDiffParser {
|
||||
|
||||
@@ -116,4 +117,20 @@ export class GitDiffParser {
|
||||
|
||||
return chunkLines;
|
||||
}
|
||||
|
||||
static parseShortStat(data: string): GitDiffShortStat | undefined {
|
||||
if (!data) return undefined;
|
||||
|
||||
const match = shortStatDiffRegex.exec(data);
|
||||
if (match == null) return undefined;
|
||||
|
||||
const files = match[1];
|
||||
const insertions = match[2];
|
||||
const deletions = match[3];
|
||||
return {
|
||||
files: files == null ? 0 : parseInt(files, 10),
|
||||
insertions: insertions == null ? 0 : parseInt(insertions, 10),
|
||||
deletions: deletions == null ? 0 : parseInt(deletions, 10)
|
||||
} as GitDiffShortStat;
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,6 @@ import { Strings } from '../../system';
|
||||
import { Range } from 'vscode';
|
||||
import { Git, GitAuthor, GitCommitType, GitLog, GitLogCommit, GitStatusFileStatus, IGitStatusFile } from './../git';
|
||||
// import { Logger } from '../../logger';
|
||||
import * as moment from 'moment';
|
||||
import * as path from 'path';
|
||||
|
||||
interface LogEntry {
|
||||
@@ -87,7 +86,7 @@ export class GitLogParser {
|
||||
break;
|
||||
|
||||
case 'author-date':
|
||||
entry.authorDate = `${lineParts[1]}T${lineParts[2]}${lineParts[3]}`;
|
||||
entry.authorDate = lineParts[1];
|
||||
break;
|
||||
|
||||
case 'parents':
|
||||
@@ -231,7 +230,7 @@ export class GitLogParser {
|
||||
}
|
||||
}
|
||||
|
||||
commit = new GitLogCommit(type, repoPath!, entry.sha, relativeFileName, entry.author, moment(entry.authorDate).toDate(), entry.summary!, entry.status, entry.fileStatuses, undefined, entry.originalFileName);
|
||||
commit = new GitLogCommit(type, repoPath!, entry.sha, relativeFileName, entry.author, new Date(entry.authorDate! as any * 1000), entry.summary!, entry.status, entry.fileStatuses, undefined, entry.originalFileName);
|
||||
commit.parentShas = entry.parentShas!;
|
||||
|
||||
if (relativeFileName !== entry.fileName) {
|
||||
|
||||
50
src/git/parsers/remoteParser.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
'use strict';
|
||||
import { GitRemote } from './../git';
|
||||
import { GitRemoteType } from '../models/remote';
|
||||
|
||||
const remoteRegex = /^(.*)\t(.*)\s\((.*)\)$/gm;
|
||||
const urlRegex = /^(?:git:\/\/(.*?)\/|https:\/\/(.*?)\/|http:\/\/(.*?)\/|git@(.*):|ssh:\/\/(?:.*@)?(.*?)(?::.*?)?\/)(.*)$/;
|
||||
|
||||
export class GitRemoteParser {
|
||||
|
||||
static parse(data: string, repoPath: string): GitRemote[] {
|
||||
if (!data) return [];
|
||||
|
||||
const remotes: GitRemote[] = [];
|
||||
const groups = Object.create(null);
|
||||
|
||||
let match: RegExpExecArray | null = null;
|
||||
do {
|
||||
match = remoteRegex.exec(data);
|
||||
if (match == null) break;
|
||||
|
||||
const url = match[2];
|
||||
|
||||
const [domain, path] = this.parseGitUrl(url);
|
||||
|
||||
let remote: GitRemote | undefined = groups[url];
|
||||
if (remote === undefined) {
|
||||
remote = new GitRemote(repoPath, match[1], url, domain, path, [match[3] as GitRemoteType]);
|
||||
remotes.push(remote);
|
||||
groups[url] = remote;
|
||||
}
|
||||
else {
|
||||
remote.types.push(match[3] as GitRemoteType);
|
||||
}
|
||||
} while (match != null);
|
||||
|
||||
if (!remotes.length) return [];
|
||||
|
||||
return remotes;
|
||||
}
|
||||
|
||||
static parseGitUrl(url: string): [string, string] {
|
||||
const match = urlRegex.exec(url);
|
||||
if (match == null) return ['', ''];
|
||||
|
||||
return [
|
||||
match[1] || match[2] || match[3] || match[4] || match[5],
|
||||
match[6].replace(/\.git\/?$/, '')
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
'use strict';
|
||||
import { Git, GitStash, GitStashCommit, GitStatusFileStatus, IGitStatusFile } from './../git';
|
||||
// import { Logger } from '../../logger';
|
||||
import * as moment from 'moment';
|
||||
|
||||
interface StashEntry {
|
||||
sha: string;
|
||||
@@ -14,11 +13,33 @@ interface StashEntry {
|
||||
|
||||
export class GitStashParser {
|
||||
|
||||
static parse(data: string, repoPath: string): GitStash | 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, new Date(entry.date! as any * 1000), entry.summary, undefined, entry.fileStatuses) as GitStashCommit;
|
||||
commits.set(entry.sha, commit);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
repoPath: repoPath,
|
||||
commits: commits
|
||||
} as GitStash;
|
||||
}
|
||||
|
||||
private static _parseEntries(data: string): StashEntry[] | undefined {
|
||||
if (!data) return undefined;
|
||||
|
||||
const lines = data.split('\n');
|
||||
if (!lines.length) return undefined;
|
||||
if (lines.length === 0) return undefined;
|
||||
|
||||
const entries: StashEntry[] = [];
|
||||
|
||||
@@ -42,7 +63,7 @@ export class GitStashParser {
|
||||
|
||||
switch (lineParts[0]) {
|
||||
case 'author-date':
|
||||
entry.date = `${lineParts[1]}T${lineParts[2]}${lineParts[3]}`;
|
||||
entry.date = lineParts[1];
|
||||
break;
|
||||
|
||||
case 'summary':
|
||||
@@ -66,7 +87,12 @@ export class GitStashParser {
|
||||
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;
|
||||
if (nextLine && Git.shaRegex.test(nextLine)) {
|
||||
entries.push(entry);
|
||||
entry = undefined;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
position++;
|
||||
|
||||
@@ -109,28 +135,6 @@ export class GitStashParser {
|
||||
return entries;
|
||||
}
|
||||
|
||||
static parse(data: string, repoPath: string): GitStash | 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 GitStash;
|
||||
}
|
||||
|
||||
private static _parseFileName(entry: { fileName?: string, originalFileName?: string }) {
|
||||
if (entry.fileName === undefined) return;
|
||||
|
||||
|
||||
@@ -6,6 +6,8 @@ interface FileStatusEntry {
|
||||
status: GitStatusFileStatus;
|
||||
fileName: string;
|
||||
originalFileName: string;
|
||||
workTreeStatus: GitStatusFileStatus;
|
||||
indexStatus: GitStatusFileStatus;
|
||||
}
|
||||
|
||||
const aheadStatusV1Regex = /(?:ahead ([0-9]+))/;
|
||||
@@ -17,7 +19,7 @@ export class GitStatusParser {
|
||||
if (!data) return undefined;
|
||||
|
||||
const lines = data.split('\n').filter(_ => !!_);
|
||||
if (!lines.length) return undefined;
|
||||
if (lines.length === 0) return undefined;
|
||||
|
||||
const status = {
|
||||
branch: '',
|
||||
@@ -69,7 +71,7 @@ export class GitStatusParser {
|
||||
else {
|
||||
entry = this._parseFileEntry(rawStatus, fileName);
|
||||
}
|
||||
status.files.push(new GitStatusFile(repoPath, entry.status, entry.fileName, entry.staged, entry.originalFileName));
|
||||
status.files.push(new GitStatusFile(repoPath, entry.status, entry.workTreeStatus, entry.indexStatus, entry.fileName, entry.staged, entry.originalFileName));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -117,7 +119,7 @@ export class GitStatusParser {
|
||||
}
|
||||
|
||||
if (entry !== undefined) {
|
||||
status.files.push(new GitStatusFile(repoPath, entry.status, entry.fileName, entry.staged, entry.originalFileName));
|
||||
status.files.push(new GitStatusFile(repoPath, entry.status, entry.workTreeStatus, entry.indexStatus, entry.fileName, entry.staged, entry.originalFileName));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -131,7 +133,9 @@ export class GitStatusParser {
|
||||
status: (indexStatus || workTreeStatus || '?') as GitStatusFileStatus,
|
||||
fileName: fileName,
|
||||
originalFileName: originalFileName,
|
||||
staged: !!indexStatus
|
||||
staged: !!indexStatus,
|
||||
indexStatus: indexStatus,
|
||||
workTreeStatus: workTreeStatus
|
||||
} as FileStatusEntry;
|
||||
}
|
||||
}
|
||||
47
src/git/remotes/bitbucket-server.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
'use strict';
|
||||
import { Range } from 'vscode';
|
||||
import { RemoteProvider } from './provider';
|
||||
|
||||
export class BitbucketServerService extends RemoteProvider {
|
||||
|
||||
constructor(public domain: string, public path: string, public custom: boolean = false) {
|
||||
super(domain, path);
|
||||
}
|
||||
|
||||
get name() {
|
||||
return this.formatName('Bitbucket Server');
|
||||
}
|
||||
|
||||
protected get baseUrl() {
|
||||
const [project, repo] = super.splitPath();
|
||||
return `https://${this.domain}/projects/${project}/repos/${repo}`;
|
||||
}
|
||||
|
||||
protected getUrlForBranches(): string {
|
||||
return `${this.baseUrl}/branches`;
|
||||
}
|
||||
|
||||
protected getUrlForBranch(branch: string): string {
|
||||
return `${this.baseUrl}/commits?until=${branch}`;
|
||||
}
|
||||
|
||||
protected getUrlForCommit(sha: string): string {
|
||||
return `${this.baseUrl}/commits/${sha}`;
|
||||
}
|
||||
|
||||
protected getUrlForFile(fileName: string, branch?: string, sha?: string, range?: Range): string {
|
||||
let line = '';
|
||||
if (range) {
|
||||
if (range.start.line === range.end.line) {
|
||||
line = `#${range.start.line}`;
|
||||
}
|
||||
else {
|
||||
line = `#${range.start.line}-${range.end.line}`;
|
||||
}
|
||||
}
|
||||
|
||||
if (sha) return `${this.baseUrl}/browse/${fileName}?at=${sha}${line}`;
|
||||
if (branch) return `${this.baseUrl}/browse/${fileName}?at=${branch}${line}`;
|
||||
return `${this.baseUrl}/browse/${fileName}${line}`;
|
||||
}
|
||||
}
|
||||
@@ -4,12 +4,12 @@ import { RemoteProvider } from './provider';
|
||||
|
||||
export class BitbucketService extends RemoteProvider {
|
||||
|
||||
constructor(public domain: string, public path: string) {
|
||||
constructor(public domain: string, public path: string, public custom: boolean = false) {
|
||||
super(domain, path);
|
||||
}
|
||||
|
||||
get name() {
|
||||
return 'Bitbucket';
|
||||
return this.formatName('Bitbucket');
|
||||
}
|
||||
|
||||
protected getUrlForBranches(): string {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
'use strict';
|
||||
import { ExtensionContext, workspace } from 'vscode';
|
||||
import { Objects } from '../../system';
|
||||
import { Event, EventEmitter, ExtensionContext, workspace } from 'vscode';
|
||||
import { BitbucketService } from './bitbucket';
|
||||
import { BitbucketServerService } from './bitbucket-server';
|
||||
import { CustomRemoteType, IConfig, IRemotesConfig } from '../../configuration';
|
||||
import { ExtensionKey } from '../../constants';
|
||||
import { GitHubService } from './github';
|
||||
@@ -8,21 +10,9 @@ import { GitLabService } from './gitlab';
|
||||
import { Logger } from '../../logger';
|
||||
import { RemoteProvider } from './provider';
|
||||
import { VisualStudioService } from './visualStudio';
|
||||
import { Objects } from '../../system';
|
||||
|
||||
export { RemoteProvider };
|
||||
|
||||
const UrlRegex = /^(?:git:\/\/(.*?)\/|https:\/\/(.*?)\/|http:\/\/(.*?)\/|git@(.*):|ssh:\/\/(?:.*@)?(.*?)(?::.*?)?\/)(.*)$/;
|
||||
|
||||
function getProviderKey(type: CustomRemoteType) {
|
||||
switch (type) {
|
||||
case CustomRemoteType.Bitbucket: return 'bitbucket.org';
|
||||
case CustomRemoteType.GitHub: return 'github.com';
|
||||
case CustomRemoteType.GitLab: return 'gitlab.com';
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const defaultProviderMap = 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)],
|
||||
@@ -30,48 +20,29 @@ const defaultProviderMap = new Map<string, (domain: string, path: string) => Rem
|
||||
['visualstudio.com', (domain: string, path: string) => new VisualStudioService(domain, path)]
|
||||
]);
|
||||
|
||||
let providerMap: Map<string, (domain: string, path: string) => RemoteProvider>;
|
||||
let remotesCfg: IRemotesConfig[];
|
||||
|
||||
function onConfigurationChanged() {
|
||||
const cfg = workspace.getConfiguration().get<IConfig>(ExtensionKey);
|
||||
if (cfg === undefined) return;
|
||||
|
||||
if (!Objects.areEquivalent(cfg.remotes, remotesCfg)) {
|
||||
providerMap = new Map(defaultProviderMap);
|
||||
|
||||
remotesCfg = cfg.remotes;
|
||||
if (remotesCfg != null && remotesCfg.length > 0) {
|
||||
for (const svc of remotesCfg) {
|
||||
const key = getProviderKey(svc.type);
|
||||
if (key === undefined) continue;
|
||||
|
||||
providerMap.set(svc.domain.toLowerCase(), providerMap.get(key)!);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class RemoteProviderFactory {
|
||||
|
||||
static configure(context: ExtensionContext) {
|
||||
context.subscriptions.push(workspace.onDidChangeConfiguration(onConfigurationChanged));
|
||||
onConfigurationChanged();
|
||||
private static _providerMap: Map<string, (domain: string, path: string) => RemoteProvider>;
|
||||
private static _remotesCfg: IRemotesConfig[];
|
||||
|
||||
private static _onDidChange = new EventEmitter<void>();
|
||||
public static get onDidChange(): Event<void> {
|
||||
return this._onDidChange.event;
|
||||
}
|
||||
|
||||
static getRemoteProvider(url: string): RemoteProvider | undefined {
|
||||
static configure(context: ExtensionContext) {
|
||||
context.subscriptions.push(workspace.onDidChangeConfiguration(() => this.onConfigurationChanged()));
|
||||
this.onConfigurationChanged(true);
|
||||
}
|
||||
|
||||
static getRemoteProvider(domain: string, path: string): RemoteProvider | undefined {
|
||||
try {
|
||||
const match = UrlRegex.exec(url);
|
||||
if (match == null) return undefined;
|
||||
let key = domain.toLowerCase();
|
||||
if (key.endsWith('visualstudio.com')) {
|
||||
key = 'visualstudio.com';
|
||||
}
|
||||
|
||||
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());
|
||||
const creator = this._providerMap.get(key);
|
||||
if (creator === undefined) return undefined;
|
||||
|
||||
return creator(domain, path);
|
||||
@@ -81,4 +52,37 @@ export class RemoteProviderFactory {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
private static onConfigurationChanged(silent: boolean = false) {
|
||||
const cfg = workspace.getConfiguration().get<IConfig>(ExtensionKey);
|
||||
if (cfg === undefined) return;
|
||||
|
||||
if (!Objects.areEquivalent(cfg.remotes, this._remotesCfg)) {
|
||||
this._providerMap = new Map(defaultProviderMap);
|
||||
|
||||
this._remotesCfg = cfg.remotes;
|
||||
if (this._remotesCfg != null && this._remotesCfg.length > 0) {
|
||||
for (const svc of this._remotesCfg) {
|
||||
const provider = this.getCustomProvider(svc.type);
|
||||
if (provider === undefined) continue;
|
||||
|
||||
this._providerMap.set(svc.domain.toLowerCase(), provider);
|
||||
}
|
||||
|
||||
if (!silent) {
|
||||
this._onDidChange.fire();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static getCustomProvider(type: CustomRemoteType) {
|
||||
switch (type) {
|
||||
case CustomRemoteType.Bitbucket: return (domain: string, path: string) => new BitbucketService(domain, path, true);
|
||||
case CustomRemoteType.BitbucketServer: return (domain: string, path: string) => new BitbucketServerService(domain, path, true);
|
||||
case CustomRemoteType.GitHub: return (domain: string, path: string) => new GitHubService(domain, path, true);
|
||||
case CustomRemoteType.GitLab: return (domain: string, path: string) => new GitLabService(domain, path, true);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
@@ -4,12 +4,12 @@ import { RemoteProvider } from './provider';
|
||||
|
||||
export class GitHubService extends RemoteProvider {
|
||||
|
||||
constructor(public domain: string, public path: string) {
|
||||
constructor(public domain: string, public path: string, public custom: boolean = false) {
|
||||
super(domain, path);
|
||||
}
|
||||
|
||||
get name() {
|
||||
return 'GitHub';
|
||||
return this.formatName('GitHub');
|
||||
}
|
||||
|
||||
protected getUrlForBranches(): string {
|
||||
|
||||
@@ -1,13 +1,42 @@
|
||||
'use strict';
|
||||
import { GitHubService } from './github';
|
||||
import { Range } from 'vscode';
|
||||
import { RemoteProvider } from './provider';
|
||||
|
||||
export class GitLabService extends GitHubService {
|
||||
export class GitLabService extends RemoteProvider {
|
||||
|
||||
constructor(public domain: string, public path: string) {
|
||||
constructor(public domain: string, public path: string, public custom: boolean = false) {
|
||||
super(domain, path);
|
||||
}
|
||||
|
||||
get name() {
|
||||
return 'GitLab';
|
||||
return this.formatName('GitLab');
|
||||
}
|
||||
|
||||
protected getUrlForBranches(): string {
|
||||
return `${this.baseUrl}/branches`;
|
||||
}
|
||||
|
||||
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 = '';
|
||||
if (range) {
|
||||
if (range.start.line === range.end.line) {
|
||||
line = `#L${range.start.line}`;
|
||||
}
|
||||
else {
|
||||
line = `#L${range.start.line}-${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}`;
|
||||
}
|
||||
}
|
||||
@@ -26,7 +26,7 @@ export function getNameFromRemoteResource(resource: RemoteResource) {
|
||||
|
||||
export abstract class RemoteProvider {
|
||||
|
||||
constructor(public domain: string, public path: string) { }
|
||||
constructor(public domain: string, public path: string, public custom: boolean = false) { }
|
||||
|
||||
abstract get name(): string;
|
||||
|
||||
@@ -34,6 +34,15 @@ export abstract class RemoteProvider {
|
||||
return `https://${this.domain}/${this.path}`;
|
||||
}
|
||||
|
||||
protected formatName(name: string) {
|
||||
return `${name}${this.custom ? ` (${this.domain})` : ''}`;
|
||||
}
|
||||
|
||||
protected splitPath(): [string, string] {
|
||||
const index = this.path.indexOf('/');
|
||||
return [ this.path.substring(0, index), this.path.substring(index + 1) ];
|
||||
}
|
||||
|
||||
protected abstract getUrlForBranches(): string;
|
||||
protected abstract getUrlForBranch(branch: string): string;
|
||||
protected abstract getUrlForCommit(sha: string): string;
|
||||
|
||||
@@ -6,7 +6,6 @@ import { BuiltInCommands, DocumentSchemes, ExtensionKey } from './constants';
|
||||
import { CodeLensCommand, CodeLensLocations, ICodeLensLanguageLocation, IConfig } from './configuration';
|
||||
import { GitBlame, GitBlameCommit, GitBlameLines, GitService, GitUri } from './gitService';
|
||||
import { Logger } from './logger';
|
||||
import * as moment from 'moment';
|
||||
|
||||
export class GitRecentChangeCodeLens extends CodeLens {
|
||||
|
||||
@@ -254,7 +253,7 @@ export class GitCodeLensProvider implements CodeLensProvider {
|
||||
if (blame === undefined) return lens;
|
||||
|
||||
const recentCommit = Iterables.first(blame.commits.values());
|
||||
title = `${recentCommit.author}, ${moment(recentCommit.date).fromNow()}`;
|
||||
title = `${recentCommit.author}, ${recentCommit.fromNow()}`;
|
||||
if (this._config.codeLens.debug) {
|
||||
title += ` [${SymbolKind[lens.symbolKind]}(${lens.range.start.character}-${lens.range.end.character}), Lines (${lens.blameRange.start.line + 1}-${lens.blameRange.end.line + 1}), Commit (${recentCommit.shortSha})]`;
|
||||
}
|
||||
|
||||
@@ -3,12 +3,12 @@ import { Functions, Iterables, Objects } from './system';
|
||||
import { Disposable, Event, EventEmitter, FileSystemWatcher, Location, Position, Range, TextDocument, TextDocumentChangeEvent, TextEditor, Uri, workspace } from 'vscode';
|
||||
import { IConfig } from './configuration';
|
||||
import { DocumentSchemes, ExtensionKey, GlyphChars } from './constants';
|
||||
import { Git, GitAuthor, GitBlame, GitBlameCommit, GitBlameLine, GitBlameLines, GitBlameParser, GitBranch, GitBranchParser, GitCommit, GitDiff, GitDiffChunkLine, GitDiffParser, GitLog, GitLogCommit, GitLogParser, GitRemote, GitStash, GitStashParser, GitStatus, GitStatusFile, GitStatusParser, IGit, setDefaultEncoding } from './git/git';
|
||||
import { RemoteProviderFactory } from './git/remotes/factory';
|
||||
import { Git, GitAuthor, GitBlame, GitBlameCommit, GitBlameLine, GitBlameLines, GitBlameParser, GitBranch, GitBranchParser, GitCommit, GitDiff, GitDiffChunkLine, GitDiffParser, GitDiffShortStat, GitLog, GitLogCommit, GitLogParser, GitRemote, GitRemoteParser, GitStash, GitStashParser, GitStatus, GitStatusFile, GitStatusParser, IGit, setDefaultEncoding } from './git/git';
|
||||
import { GitUri, IGitCommitInfo, IGitUriData } from './git/gitUri';
|
||||
import { Logger } from './logger';
|
||||
import * as fs from 'fs';
|
||||
import * as ignore from 'ignore';
|
||||
import * as moment from 'moment';
|
||||
import * as path from 'path';
|
||||
|
||||
export { GitUri, IGitCommitInfo };
|
||||
@@ -64,8 +64,9 @@ export const GitRepoSearchBy = {
|
||||
Sha: 'sha' as GitRepoSearchBy
|
||||
};
|
||||
|
||||
export type RepoChangedReasons = 'stash' | 'unknown';
|
||||
export type RepoChangedReasons = 'remotes' | 'stash' | 'unknown';
|
||||
export const RepoChangedReasons = {
|
||||
Remotes: 'remotes' as RepoChangedReasons,
|
||||
Stash: 'stash' as RepoChangedReasons,
|
||||
Unknown: 'unknown' as RepoChangedReasons
|
||||
};
|
||||
@@ -73,6 +74,7 @@ export const RepoChangedReasons = {
|
||||
export class GitService extends Disposable {
|
||||
|
||||
static fakeSha = 'ffffffffffffffffffffffffffffffffffffffff';
|
||||
static uncommittedSha = '0000000000000000000000000000000000000000';
|
||||
|
||||
private _onDidBlameFail = new EventEmitter<string>();
|
||||
get onDidBlameFail(): Event<string> {
|
||||
@@ -84,6 +86,11 @@ export class GitService extends Disposable {
|
||||
return this._onDidChangeGitCache.event;
|
||||
}
|
||||
|
||||
private _onDidChangeFileSystem = new EventEmitter<Uri>();
|
||||
get onDidChangeFileSystem(): Event<Uri> {
|
||||
return this._onDidChangeFileSystem.event;
|
||||
}
|
||||
|
||||
private _onDidChangeRepo = new EventEmitter<RepoChangedReasons[]>();
|
||||
get onDidChangeRepo(): Event<RepoChangedReasons[]> {
|
||||
return this._onDidChangeRepo.event;
|
||||
@@ -113,19 +120,22 @@ export class GitService extends Disposable {
|
||||
const subscriptions: Disposable[] = [];
|
||||
|
||||
subscriptions.push(workspace.onDidChangeConfiguration(this._onConfigurationChanged, this));
|
||||
subscriptions.push(RemoteProviderFactory.onDidChange(this._onRemoteProviderChanged, this));
|
||||
|
||||
this._disposable = Disposable.from(...subscriptions);
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this.stopWatchingFileSystem();
|
||||
|
||||
this._repoWatcher && this._repoWatcher.dispose();
|
||||
this._repoWatcher = undefined;
|
||||
|
||||
this._disposable && this._disposable.dispose();
|
||||
|
||||
this._cacheDisposable && this._cacheDisposable.dispose();
|
||||
this._cacheDisposable = undefined;
|
||||
|
||||
this._repoWatcher && this._repoWatcher.dispose();
|
||||
this._repoWatcher = undefined;
|
||||
|
||||
this._gitCache.clear();
|
||||
this._remotesCache.clear();
|
||||
this._uriCache.clear();
|
||||
@@ -153,6 +163,8 @@ export class GitService extends Disposable {
|
||||
disposables.push(workspace.onDidChangeTextDocument(this._onTextDocumentChanged, this));
|
||||
disposables.push(workspace.onDidSaveTextDocument(d => this._removeCachedEntry(d, RemoveCacheReason.DocumentSaved)));
|
||||
disposables.push(this._repoWatcher.onDidChange(this._onRepoChanged, this));
|
||||
disposables.push(this._repoWatcher.onDidCreate(this._onRepoChanged, this));
|
||||
disposables.push(this._repoWatcher.onDidDelete(this._onRepoChanged, this));
|
||||
|
||||
this._cacheDisposable = Disposable.from(...disposables);
|
||||
}
|
||||
@@ -164,7 +176,6 @@ export class GitService extends Disposable {
|
||||
this._repoWatcher = undefined;
|
||||
|
||||
this._gitCache.clear();
|
||||
this._remotesCache.clear();
|
||||
}
|
||||
|
||||
this._gitignore = new Promise<ignore.Ignore | undefined>((resolve, reject) => {
|
||||
@@ -190,7 +201,19 @@ export class GitService extends Disposable {
|
||||
});
|
||||
}
|
||||
|
||||
const ignoreWhitespace = this.config && this.config.blame.ignoreWhitespace;
|
||||
|
||||
this.config = cfg;
|
||||
|
||||
if (this.config.blame.ignoreWhitespace !== ignoreWhitespace) {
|
||||
this._gitCache.clear();
|
||||
this._fireGitCacheChange();
|
||||
}
|
||||
}
|
||||
|
||||
private _onRemoteProviderChanged() {
|
||||
this._remotesCache.clear();
|
||||
this._fireRepoChange(RepoChangedReasons.Remotes);
|
||||
}
|
||||
|
||||
private _onTextDocumentChanged(e: TextDocumentChangeEvent) {
|
||||
@@ -420,7 +443,7 @@ export class GitService extends Disposable {
|
||||
}
|
||||
|
||||
try {
|
||||
const data = await Git.blame(root, file, uri.sha);
|
||||
const data = await Git.blame(root, file, uri.sha, { ignoreWhitespace: this.config.blame.ignoreWhitespace });
|
||||
const blame = GitBlameParser.parse(data, root, file);
|
||||
return blame;
|
||||
}
|
||||
@@ -469,7 +492,7 @@ export class GitService extends Disposable {
|
||||
const fileName = uri.fsPath;
|
||||
|
||||
try {
|
||||
const data = await Git.blame(uri.repoPath, fileName, uri.sha, line + 1, line + 1);
|
||||
const data = await Git.blame(uri.repoPath, fileName, uri.sha, { ignoreWhitespace: this.config.blame.ignoreWhitespace, startLine: line + 1, endLine: line + 1 });
|
||||
const blame = GitBlameParser.parse(data, uri.repoPath, fileName);
|
||||
if (blame === undefined) return undefined;
|
||||
|
||||
@@ -553,7 +576,7 @@ export class GitService extends Disposable {
|
||||
Iterables.forEach(blame.commits.values(), (c, i) => {
|
||||
if (c.isUncommitted) return;
|
||||
|
||||
const decoration = `${GlyphChars.ArrowDropRight} ${c.author}, ${moment(c.date).format(dateFormat)}`;
|
||||
const decoration = `${GlyphChars.ArrowDropRight} ${c.author}, ${c.formatDate(dateFormat)}`;
|
||||
const uri = GitService.toReferenceGitContentUri(c, i + 1, commitCount, c.originalFileName, decoration, dateFormat);
|
||||
locations.push(new Location(uri, new Position(0, 0)));
|
||||
if (c.sha === selectedSha) {
|
||||
@@ -585,6 +608,11 @@ export class GitService extends Disposable {
|
||||
return Git.normalizePath(typeof fileNameOrUri === 'string' ? fileNameOrUri : fileNameOrUri.fsPath).toLowerCase();
|
||||
}
|
||||
|
||||
async getChangedFilesCount(repoPath: string, sha?: string): Promise<GitDiffShortStat | undefined> {
|
||||
const data = await Git.diff_shortstat(repoPath, sha);
|
||||
return GitDiffParser.parseShortStat(data);
|
||||
}
|
||||
|
||||
async getConfig(key: string, repoPath?: string): Promise<string> {
|
||||
Logger.log(`getConfig('${key}', '${repoPath}')`);
|
||||
|
||||
@@ -873,7 +901,7 @@ export class GitService extends Disposable {
|
||||
Iterables.forEach(log.commits.values(), (c, i) => {
|
||||
if (c.isUncommitted) return;
|
||||
|
||||
const decoration = `${GlyphChars.ArrowDropRight} ${c.author}, ${moment(c.date).format(dateFormat)}`;
|
||||
const decoration = `${GlyphChars.ArrowDropRight} ${c.author}, ${c.formatDate(dateFormat)}`;
|
||||
const uri = GitService.toReferenceGitContentUri(c, i + 1, commitCount, c.originalFileName, decoration, dateFormat);
|
||||
locations.push(new Location(uri, new Position(0, 0)));
|
||||
if (c.sha === selectedSha) {
|
||||
@@ -884,21 +912,26 @@ export class GitService extends Disposable {
|
||||
return locations;
|
||||
}
|
||||
|
||||
hasRemotes(repoPath: string): boolean {
|
||||
const remotes = this._remotesCache.get(repoPath);
|
||||
return remotes !== undefined && remotes.length > 0;
|
||||
}
|
||||
|
||||
async getRemotes(repoPath: string): Promise<GitRemote[]> {
|
||||
if (!repoPath) return [];
|
||||
|
||||
Logger.log(`getRemotes('${repoPath}')`);
|
||||
|
||||
if (this.UseCaching) {
|
||||
const remotes = this._remotesCache.get(repoPath);
|
||||
if (remotes !== undefined) return remotes;
|
||||
}
|
||||
let remotes = this._remotesCache.get(repoPath);
|
||||
if (remotes !== undefined) return remotes;
|
||||
|
||||
const data = await Git.remote(repoPath);
|
||||
const remotes = data.split('\n').filter(_ => !!_).map(_ => new GitRemote(_));
|
||||
if (this.UseCaching) {
|
||||
remotes = GitRemoteParser.parse(data, repoPath);
|
||||
|
||||
if (remotes !== undefined) {
|
||||
this._remotesCache.set(repoPath, remotes);
|
||||
}
|
||||
|
||||
return remotes;
|
||||
}
|
||||
|
||||
@@ -1003,12 +1036,45 @@ export class GitService extends Disposable {
|
||||
return !!result;
|
||||
}
|
||||
|
||||
openDiffTool(repoPath: string, uri: Uri, staged: boolean) {
|
||||
Logger.log(`openDiffTool('${repoPath}', '${uri}', ${staged})`);
|
||||
|
||||
return Git.difftool_fileDiff(repoPath, uri.fsPath, staged);
|
||||
}
|
||||
|
||||
openDirectoryDiff(repoPath: string, sha1: string, sha2?: string) {
|
||||
Logger.log(`openDirectoryDiff('${repoPath}', ${sha1}, ${sha2})`);
|
||||
|
||||
return Git.difftool_dirDiff(repoPath, sha1, sha2);
|
||||
}
|
||||
|
||||
private _fsWatcherDisposable: Disposable | undefined;
|
||||
|
||||
startWatchingFileSystem() {
|
||||
if (this._fsWatcherDisposable !== undefined) return;
|
||||
|
||||
const debouncedFn = Functions.debounce((uri: Uri) => this._onDidChangeFileSystem.fire(uri), 2500);
|
||||
const fn = (uri: Uri) => {
|
||||
// Ignore .git changes
|
||||
if (/\.git/.test(uri.fsPath)) return;
|
||||
|
||||
debouncedFn(uri);
|
||||
};
|
||||
|
||||
const watcher = workspace.createFileSystemWatcher(`**`);
|
||||
this._fsWatcherDisposable = Disposable.from(
|
||||
watcher,
|
||||
watcher.onDidChange(fn),
|
||||
watcher.onDidCreate(fn),
|
||||
watcher.onDidDelete(fn)
|
||||
);
|
||||
}
|
||||
|
||||
stopWatchingFileSystem() {
|
||||
this._fsWatcherDisposable && this._fsWatcherDisposable.dispose();
|
||||
this._fsWatcherDisposable = undefined;
|
||||
}
|
||||
|
||||
stashApply(repoPath: string, stashName: string, deleteAfter: boolean = false) {
|
||||
Logger.log(`stashApply('${repoPath}', ${stashName}, ${deleteAfter})`);
|
||||
|
||||
@@ -1123,7 +1189,7 @@ export class GitService extends Disposable {
|
||||
}
|
||||
|
||||
// NOTE: Need to specify an index here, since I can't control the sort order -- just alphabetic or by file location
|
||||
return Uri.parse(`${scheme}:${pad(data.index || 0)} ${GlyphChars.Dot} ${encodeURIComponent(message)} ${GlyphChars.Dot} ${moment(commit.date).format(dateFormat)} ${GlyphChars.Dot} ${encodeURIComponent(uriPath)}?${JSON.stringify(data)}`);
|
||||
return Uri.parse(`${scheme}:${pad(data.index || 0)} ${GlyphChars.Dot} ${encodeURIComponent(message)} ${GlyphChars.Dot} ${commit.formatDate(dateFormat)} ${GlyphChars.Dot} ${encodeURIComponent(uriPath)}?${JSON.stringify(data)}`);
|
||||
}
|
||||
|
||||
private static _toGitUriData<T extends IGitUriData>(commit: IGitUriData, index?: number, originalFileName?: string, decoration?: string): T {
|
||||
|
||||
@@ -3,7 +3,6 @@ import { commands, ExtensionContext, Uri, window } from 'vscode';
|
||||
import { BuiltInCommands } from './constants';
|
||||
import { GitCommit } from './gitService';
|
||||
import { Logger } from './logger';
|
||||
import * as moment from 'moment';
|
||||
|
||||
export type SuppressedKeys = 'suppressCommitHasNoPreviousCommitWarning' |
|
||||
'suppressCommitNotFoundWarning' |
|
||||
@@ -34,7 +33,7 @@ export class Messages {
|
||||
|
||||
static showCommitHasNoPreviousCommitWarningMessage(commit?: GitCommit): Promise<string | undefined> {
|
||||
if (commit === undefined) return Messages._showMessage('info', `Commit has no previous commit`, SuppressedKeys.CommitHasNoPreviousCommitWarning);
|
||||
return Messages._showMessage('info', `Commit ${commit.shortSha} (${commit.author}, ${moment(commit.date).fromNow()}) has no previous commit`, SuppressedKeys.CommitHasNoPreviousCommitWarning);
|
||||
return Messages._showMessage('info', `Commit ${commit.shortSha} (${commit.author}, ${commit.fromNow()}) has no previous commit`, SuppressedKeys.CommitHasNoPreviousCommitWarning);
|
||||
}
|
||||
|
||||
static showCommitNotFoundWarningMessage(message: string): Promise<string | undefined> {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
'use strict';
|
||||
import { Arrays, Iterables, Strings } from '../system';
|
||||
import { Iterables, Strings } from '../system';
|
||||
import { CancellationTokenSource, QuickPickOptions, Uri, window } from 'vscode';
|
||||
import { Commands, ShowCommitSearchCommandArgs, ShowQuickBranchHistoryCommandArgs } from '../commands';
|
||||
import { CommandQuickPickItem, CommitQuickPickItem, getQuickPickIgnoreFocusOut, showQuickPickProgress } from './common';
|
||||
@@ -35,7 +35,7 @@ export class BranchHistoryQuickPick {
|
||||
} as ShowQuickBranchHistoryCommandArgs
|
||||
]);
|
||||
|
||||
const remotes = Arrays.uniqueBy(await git.getRemotes((uri && uri.repoPath) || git.repoPath), _ => _.url, _ => !!_.provider);
|
||||
const remotes = (await git.getRemotes((uri && uri.repoPath) || git.repoPath)).filter(r => r.provider !== undefined);
|
||||
if (remotes.length) {
|
||||
items.splice(0, 0, new OpenRemotesCommandQuickPickItem(remotes, {
|
||||
type: 'branch',
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
'use strict';
|
||||
import { Arrays, Iterables, Strings } from '../system';
|
||||
import { Iterables, Strings } from '../system';
|
||||
import { commands, QuickPickOptions, TextDocumentShowOptions, Uri, window } from 'vscode';
|
||||
import { Commands, CopyMessageToClipboardCommandArgs, CopyShaToClipboardCommandArgs, DiffDirectoryCommandCommandArgs, DiffWithPreviousCommandArgs, ShowQuickCommitDetailsCommandArgs, StashApplyCommandArgs, StashDeleteCommandArgs } from '../commands';
|
||||
import { CommandQuickPickItem, getQuickPickIgnoreFocusOut, KeyCommandQuickPickItem, OpenFileCommandQuickPickItem, OpenFilesCommandQuickPickItem, QuickPickItem } from './common';
|
||||
@@ -7,7 +7,6 @@ import { GlyphChars } from '../constants';
|
||||
import { getGitStatusOcticon, GitCommit, GitLog, GitLogCommit, GitService, GitStashCommit, GitStatusFile, GitStatusFileStatus, GitUri, IGitCommitInfo, IGitStatusFile, RemoteResource } from '../gitService';
|
||||
import { Keyboard, KeyCommand, KeyNoopCommand, Keys } from '../keyboard';
|
||||
import { OpenRemotesCommandQuickPickItem } from './remotes';
|
||||
import * as moment from 'moment';
|
||||
import * as path from 'path';
|
||||
|
||||
export class CommitWithFileStatusQuickPickItem extends OpenFileCommandQuickPickItem {
|
||||
@@ -153,7 +152,7 @@ export class CommitDetailsQuickPick {
|
||||
]));
|
||||
|
||||
if (!stash) {
|
||||
const remotes = Arrays.uniqueBy(await git.getRemotes(commit.repoPath), _ => _.url, _ => !!_.provider);
|
||||
const remotes = (await git.getRemotes(commit.repoPath)).filter(r => r.provider !== undefined);
|
||||
if (remotes.length) {
|
||||
items.splice(index++, 0, new OpenRemotesCommandQuickPickItem(remotes, {
|
||||
type: 'commit',
|
||||
@@ -299,7 +298,7 @@ export class CommitDetailsQuickPick {
|
||||
const pick = await window.showQuickPick(items, {
|
||||
matchOnDescription: true,
|
||||
matchOnDetail: true,
|
||||
placeHolder: `${commit.shortSha} ${Strings.pad(GlyphChars.Dot, 1, 1)} ${commit.author ? `${commit.author}, ` : ''}${moment(commit.date).fromNow()} ${Strings.pad(GlyphChars.Dot, 1, 1)} ${commit.message}`,
|
||||
placeHolder: `${commit.shortSha} ${Strings.pad(GlyphChars.Dot, 1, 1)} ${commit.author ? `${commit.author}, ` : ''}${commit.fromNow()} ${Strings.pad(GlyphChars.Dot, 1, 1)} ${commit.message}`,
|
||||
ignoreFocusOut: getQuickPickIgnoreFocusOut(),
|
||||
onDidSelectItem: (item: QuickPickItem) => {
|
||||
scope.setKeyCommand('right', item);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
'use strict';
|
||||
import { Arrays, Iterables, Strings } from '../system';
|
||||
import { Iterables, Strings } from '../system';
|
||||
import { QuickPickItem, QuickPickOptions, Uri, window } from 'vscode';
|
||||
import { Commands, CopyMessageToClipboardCommandArgs, CopyShaToClipboardCommandArgs, DiffWithPreviousCommandArgs, DiffWithWorkingCommandArgs, ShowQuickCommitDetailsCommandArgs, ShowQuickCommitFileDetailsCommandArgs, ShowQuickFileHistoryCommandArgs } from '../commands';
|
||||
import { CommandQuickPickItem, getQuickPickIgnoreFocusOut, KeyCommandQuickPickItem, OpenFileCommandQuickPickItem } from './common';
|
||||
@@ -7,7 +7,6 @@ import { GlyphChars } from '../constants';
|
||||
import { GitLog, GitLogCommit, GitService, GitUri, RemoteResource } from '../gitService';
|
||||
import { Keyboard, KeyCommand, KeyNoopCommand } from '../keyboard';
|
||||
import { OpenRemotesCommandQuickPickItem } from './remotes';
|
||||
import * as moment from 'moment';
|
||||
import * as path from 'path';
|
||||
|
||||
export class OpenCommitFileCommandQuickPickItem extends OpenFileCommandQuickPickItem {
|
||||
@@ -125,7 +124,7 @@ export class CommitFileDetailsQuickPick {
|
||||
}
|
||||
items.push(new OpenCommitFileRevisionCommandQuickPickItem(commit));
|
||||
|
||||
const remotes = Arrays.uniqueBy(await git.getRemotes(commit.repoPath), _ => _.url, _ => !!_.provider);
|
||||
const remotes = (await git.getRemotes(commit.repoPath)).filter(r => r.provider !== undefined);
|
||||
if (remotes.length) {
|
||||
if (commit.workingFileName && commit.status !== 'D') {
|
||||
const branch = await git.getBranch(commit.repoPath || git.repoPath);
|
||||
@@ -275,7 +274,7 @@ export class CommitFileDetailsQuickPick {
|
||||
|
||||
const pick = await window.showQuickPick(items, {
|
||||
matchOnDescription: true,
|
||||
placeHolder: `${commit.getFormattedPath()} ${Strings.pad(GlyphChars.Dot, 1, 1)} ${isUncommitted ? `Uncommitted ${GlyphChars.ArrowRightHollow} ` : '' }${commit.shortSha} ${Strings.pad(GlyphChars.Dot, 1, 1)} ${commit.author}, ${moment(commit.date).fromNow()} ${Strings.pad(GlyphChars.Dot, 1, 1)} ${commit.message}`,
|
||||
placeHolder: `${commit.getFormattedPath()} ${Strings.pad(GlyphChars.Dot, 1, 1)} ${isUncommitted ? `Uncommitted ${GlyphChars.ArrowRightHollow} ` : '' }${commit.shortSha} ${Strings.pad(GlyphChars.Dot, 1, 1)} ${commit.author}, ${commit.fromNow()} ${Strings.pad(GlyphChars.Dot, 1, 1)} ${commit.message}`,
|
||||
ignoreFocusOut: getQuickPickIgnoreFocusOut(),
|
||||
onDidSelectItem: (item: QuickPickItem) => {
|
||||
scope.setKeyCommand('right', item as KeyCommand);
|
||||
|
||||
@@ -7,7 +7,6 @@ import { GlyphChars } from '../constants';
|
||||
import { GitCommit, GitLogCommit, GitStashCommit } from '../gitService';
|
||||
import { Keyboard, KeyboardScope, KeyMapping, Keys } from '../keyboard';
|
||||
// import { Logger } from '../logger';
|
||||
import * as moment from 'moment';
|
||||
|
||||
export function getQuickPickIgnoreFocusOut() {
|
||||
const cfg = workspace.getConfiguration(ExtensionKey).get<IAdvancedConfig>('advanced')!;
|
||||
@@ -174,12 +173,12 @@ export class CommitQuickPickItem implements QuickPickItem {
|
||||
if (commit instanceof GitStashCommit) {
|
||||
this.label = message;
|
||||
this.description = '';
|
||||
this.detail = `${GlyphChars.Space} ${commit.stashName} ${Strings.pad(GlyphChars.Dot, 1, 1)} ${moment(commit.date).fromNow()} ${Strings.pad(GlyphChars.Dot, 1, 1)} ${commit.getDiffStatus()}`;
|
||||
this.detail = `${GlyphChars.Space} ${commit.stashName} ${Strings.pad(GlyphChars.Dot, 1, 1)} ${commit.fromNow()} ${Strings.pad(GlyphChars.Dot, 1, 1)} ${commit.getDiffStatus()}`;
|
||||
}
|
||||
else {
|
||||
this.label = message;
|
||||
this.description = `${Strings.pad('$(git-commit)', 1, 1)} ${commit.shortSha}`;
|
||||
this.detail = `${GlyphChars.Space} ${commit.author}, ${moment(commit.date).fromNow()}${(commit.type === 'branch') ? ` ${Strings.pad(GlyphChars.Dot, 1, 1)} ${(commit as GitLogCommit).getDiffStatus()}` : ''}`;
|
||||
this.detail = `${GlyphChars.Space} ${commit.author}, ${commit.fromNow()}${(commit.type === 'branch') ? ` ${Strings.pad(GlyphChars.Dot, 1, 1)} ${(commit as GitLogCommit).getDiffStatus()}` : ''}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
'use strict';
|
||||
import { Arrays, Iterables, Strings } from '../system';
|
||||
import { Iterables, Strings } from '../system';
|
||||
import { CancellationTokenSource, QuickPickOptions, Uri, window } from 'vscode';
|
||||
import { Commands, ShowQuickCurrentBranchHistoryCommandArgs, ShowQuickFileHistoryCommandArgs } from '../commands';
|
||||
import { CommandQuickPickItem, CommitQuickPickItem, getQuickPickIgnoreFocusOut, showQuickPickProgress } from './common';
|
||||
@@ -136,7 +136,7 @@ export class FileHistoryQuickPick {
|
||||
]));
|
||||
}
|
||||
|
||||
const remotes = Arrays.uniqueBy(await git.getRemotes(uri.repoPath!), _ => _.url, _ => !!_.provider);
|
||||
const remotes = (await git.getRemotes(uri.repoPath!)).filter(r => r.provider !== undefined);
|
||||
if (remotes.length) {
|
||||
items.splice(index++, 0, new OpenRemotesCommandQuickPickItem(remotes, {
|
||||
type: 'revision',
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
'use strict';
|
||||
export * from './system/array';
|
||||
export * from './system/date';
|
||||
// export * from './system/disposable';
|
||||
// export * from './system/element';
|
||||
// export * from './system/event';
|
||||
|
||||
@@ -1,6 +1,115 @@
|
||||
'use strict';
|
||||
import { Objects } from './object';
|
||||
|
||||
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[] } {
|
||||
return array.reduce((previous, current) => {
|
||||
const value = accessor(current);
|
||||
previous[value] = previous[value] || [];
|
||||
previous[value].push(current);
|
||||
return previous;
|
||||
}, 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[] {
|
||||
const uniqueValues = Object.create(null);
|
||||
return array.filter(_ => {
|
||||
|
||||
33
src/system/date.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
'use strict';
|
||||
import * as moment from 'moment';
|
||||
|
||||
const MillisecondsPerMinute = 60000; // 60 * 1000
|
||||
const MillisecondsPerDay = 86400000; // 24 * 60 * 60 * 1000
|
||||
|
||||
export namespace Dates {
|
||||
|
||||
export interface IDateFormatter {
|
||||
fromNow: () => string;
|
||||
format: (format: string) => string;
|
||||
}
|
||||
|
||||
export function dateDaysFromNow(date: Date, now: number = Date.now()) {
|
||||
const startOfDayLeft = startOfDay(now);
|
||||
const startOfDayRight = startOfDay(date);
|
||||
|
||||
const timestampLeft = startOfDayLeft.getTime() - startOfDayLeft.getTimezoneOffset() * MillisecondsPerMinute;
|
||||
const timestampRight = startOfDayRight.getTime() - startOfDayRight.getTimezoneOffset() * MillisecondsPerMinute;
|
||||
|
||||
return Math.round((timestampLeft - timestampRight) / MillisecondsPerDay);
|
||||
}
|
||||
|
||||
export function startOfDay(date: Date | number) {
|
||||
const newDate = new Date(typeof date === 'number' ? date : date.getTime());
|
||||
newDate.setHours(0, 0, 0, 0);
|
||||
return newDate;
|
||||
}
|
||||
|
||||
export function toFormatter(date: Date): IDateFormatter {
|
||||
return moment(date);
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,9 @@ export namespace Objects {
|
||||
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) {
|
||||
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> {
|
||||
for (const key in o) {
|
||||
yield o[key];
|
||||
|
||||
@@ -1,16 +1,11 @@
|
||||
'use strict';
|
||||
const _escapeRegExp = require('lodash.escaperegexp');
|
||||
const stringWidth = require('string-width');
|
||||
|
||||
export namespace Strings {
|
||||
export function escapeRegExp(s: string): string {
|
||||
return _escapeRegExp(s);
|
||||
}
|
||||
|
||||
export function getWidth(s: string): number {
|
||||
return stringWidth(s);
|
||||
}
|
||||
|
||||
const TokenRegex = /\$\{([^|]*?)(?:\|(\d+)(\-|\?)?)?\}/g;
|
||||
const TokenSanitizeRegex = /\$\{(\w*?)(?:\W|\d)*?\}/g;
|
||||
|
||||
@@ -68,19 +63,19 @@ export namespace Strings {
|
||||
}
|
||||
|
||||
export function padLeft(s: string, padTo: number, padding: string = '\u00a0') {
|
||||
const diff = padTo - getWidth(s);
|
||||
const diff = padTo - width(s);
|
||||
return (diff <= 0) ? s : '\u00a0'.repeat(diff) + s;
|
||||
}
|
||||
|
||||
export function padLeftOrTruncate(s: string, max: number, padding?: string) {
|
||||
const len = getWidth(s);
|
||||
const len = width(s);
|
||||
if (len < max) return padLeft(s, max, padding);
|
||||
if (len > max) return truncate(s, max);
|
||||
return s;
|
||||
}
|
||||
|
||||
export function padRight(s: string, padTo: number, padding: string = '\u00a0') {
|
||||
const diff = padTo - getWidth(s);
|
||||
const diff = padTo - width(s);
|
||||
return (diff <= 0) ? s : s + '\u00a0'.repeat(diff);
|
||||
}
|
||||
|
||||
@@ -88,14 +83,14 @@ export namespace Strings {
|
||||
const left = max < 0;
|
||||
max = Math.abs(max);
|
||||
|
||||
const len = getWidth(s);
|
||||
const len = width(s);
|
||||
if (len < max) return left ? padLeft(s, max, padding) : padRight(s, max, padding);
|
||||
if (len > max) return truncate(s, max);
|
||||
return s;
|
||||
}
|
||||
|
||||
export function padRightOrTruncate(s: string, max: number, padding?: string) {
|
||||
const len = getWidth(s);
|
||||
const len = width(s);
|
||||
if (len < max) return padRight(s, max, padding);
|
||||
if (len > max) return truncate(s, max);
|
||||
return s;
|
||||
@@ -112,15 +107,15 @@ export namespace Strings {
|
||||
export function truncate(s: string, truncateTo: number, ellipsis: string = '\u2026') {
|
||||
if (!s) return s;
|
||||
|
||||
const len = getWidth(s);
|
||||
const len = width(s);
|
||||
if (len <= truncateTo) return s;
|
||||
if (len === s.length) return `${s.substring(0, truncateTo - 1)}${ellipsis}`;
|
||||
|
||||
// Skip ahead to start as far as we can by assuming all the double-width characters won't be truncated
|
||||
let chars = Math.floor(truncateTo / (len / s.length));
|
||||
let count = getWidth(s.substring(0, chars));
|
||||
let count = width(s.substring(0, chars));
|
||||
while (count < truncateTo) {
|
||||
count += getWidth(s[chars++]);
|
||||
count += width(s[chars++]);
|
||||
}
|
||||
|
||||
if (count >= truncateTo) {
|
||||
@@ -129,4 +124,107 @@ export namespace Strings {
|
||||
|
||||
return `${s.substring(0, chars)}${ellipsis}`;
|
||||
}
|
||||
|
||||
const ansiRegex = /[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[a-zA-Z\\d]*)*)?\\u0007)|(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PRZcf-ntqry=><~]))/g;
|
||||
|
||||
export function width(s: string): number {
|
||||
if (!s || s.length === 0) return 0;
|
||||
|
||||
s = s.replace(ansiRegex, '');
|
||||
|
||||
let count = 0;
|
||||
let emoji = 0;
|
||||
let joiners = 0;
|
||||
|
||||
const graphemes = [...s];
|
||||
for (let i = 0; i < graphemes.length; i++) {
|
||||
const code = graphemes[i].codePointAt(0)!;
|
||||
|
||||
// Ignore control characters
|
||||
if (code <= 0x1F || (code >= 0x7F && code <= 0x9F)) continue;
|
||||
|
||||
// Ignore combining characters
|
||||
if (code >= 0x300 && code <= 0x36F) continue;
|
||||
|
||||
// https://stackoverflow.com/questions/30757193/find-out-if-character-in-string-is-emoji
|
||||
if (
|
||||
(code >= 0x1F600 && code <= 0x1F64F) || // Emoticons
|
||||
(code >= 0x1F300 && code <= 0x1F5FF) || // Misc Symbols and Pictographs
|
||||
(code >= 0x1F680 && code <= 0x1F6FF) || // Transport and Map
|
||||
(code >= 0x2600 && code <= 0x26FF) || // Misc symbols
|
||||
(code >= 0x2700 && code <= 0x27BF) || // Dingbats
|
||||
(code >= 0xFE00 && code <= 0xFE0F) || // Variation Selectors
|
||||
(code >= 0x1F900 && code <= 0x1F9FF) || // Supplemental Symbols and Pictographs
|
||||
(code >= 65024 && code <= 65039) || // Variation selector
|
||||
(code >= 8400 && code <= 8447) // Combining Diacritical Marks for Symbols
|
||||
) {
|
||||
if (code >= 0x1F3FB && code <= 0x1F3FF) continue; // emoji modifier fitzpatrick type
|
||||
|
||||
emoji++;
|
||||
count += 2;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Ignore zero-width joiners '\u200d'
|
||||
if (code === 8205) {
|
||||
joiners++;
|
||||
count -= 2;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Surrogates
|
||||
if (code > 0xFFFF) {
|
||||
i++;
|
||||
}
|
||||
|
||||
count += isFullwidthCodePoint(code) ? 2 : 1;
|
||||
}
|
||||
|
||||
const offset = emoji - joiners;
|
||||
if (offset > 1) {
|
||||
count += offset - 1;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
function isFullwidthCodePoint(cp: number) {
|
||||
// code points are derived from:
|
||||
// http://www.unix.org/Public/UNIDATA/EastAsianWidth.txt
|
||||
if (
|
||||
cp >= 0x1100 && (
|
||||
cp <= 0x115f || // Hangul Jamo
|
||||
cp === 0x2329 || // LEFT-POINTING ANGLE BRACKET
|
||||
cp === 0x232a || // RIGHT-POINTING ANGLE BRACKET
|
||||
// CJK Radicals Supplement .. Enclosed CJK Letters and Months
|
||||
(0x2e80 <= cp && cp <= 0x3247 && cp !== 0x303f) ||
|
||||
// Enclosed CJK Letters and Months .. CJK Unified Ideographs Extension A
|
||||
(0x3250 <= cp && cp <= 0x4dbf) ||
|
||||
// CJK Unified Ideographs .. Yi Radicals
|
||||
(0x4e00 <= cp && cp <= 0xa4c6) ||
|
||||
// Hangul Jamo Extended-A
|
||||
(0xa960 <= cp && cp <= 0xa97c) ||
|
||||
// Hangul Syllables
|
||||
(0xac00 <= cp && cp <= 0xd7a3) ||
|
||||
// CJK Compatibility Ideographs
|
||||
(0xf900 <= cp && cp <= 0xfaff) ||
|
||||
// Vertical Forms
|
||||
(0xfe10 <= cp && cp <= 0xfe19) ||
|
||||
// CJK Compatibility Forms .. Small Form Variants
|
||||
(0xfe30 <= cp && cp <= 0xfe6b) ||
|
||||
// Halfwidth and Fullwidth Forms
|
||||
(0xff01 <= cp && cp <= 0xff60) ||
|
||||
(0xffe0 <= cp && cp <= 0xffe6) ||
|
||||
// Kana Supplement
|
||||
(0x1b000 <= cp && cp <= 0x1b001) ||
|
||||
// Enclosed Ideographic Supplement
|
||||
(0x1f200 <= cp && cp <= 0x1f251) ||
|
||||
// CJK Unified Ideographs Extension B .. Tertiary Ideographic Plane
|
||||
(0x20000 <= cp && cp <= 0x3fffd)
|
||||
)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -3,22 +3,33 @@ import { Iterables } from '../system';
|
||||
import { ExtensionContext, TreeItem, TreeItemCollapsibleState } from 'vscode';
|
||||
import { CommitNode } from './commitNode';
|
||||
import { GlyphChars } from '../constants';
|
||||
import { ExplorerNode, ResourceType } from './explorerNode';
|
||||
import { ExplorerNode, ResourceType, ShowAllNode } from './explorerNode';
|
||||
import { GitBranch, GitService, GitUri } from '../gitService';
|
||||
|
||||
export class BranchHistoryNode extends ExplorerNode {
|
||||
|
||||
readonly resourceType: ResourceType = 'gitlens:branch-history';
|
||||
|
||||
constructor(public readonly branch: GitBranch, uri: GitUri, private readonly template: string, protected readonly context: ExtensionContext, protected readonly git: GitService) {
|
||||
maxCount: number | undefined = undefined;
|
||||
|
||||
constructor(
|
||||
public readonly branch: GitBranch,
|
||||
uri: GitUri,
|
||||
protected readonly context: ExtensionContext,
|
||||
protected readonly git: GitService
|
||||
) {
|
||||
super(uri);
|
||||
}
|
||||
|
||||
async getChildren(): Promise<ExplorerNode[]> {
|
||||
const log = await this.git.getLogForRepo(this.uri.repoPath!, this.branch.name, 0);
|
||||
const log = await this.git.getLogForRepo(this.uri.repoPath!, this.branch.name, this.maxCount);
|
||||
if (log === undefined) return [];
|
||||
|
||||
return [...Iterables.map(log.commits.values(), c => new CommitNode(c, this.template, this.context, this.git))];
|
||||
const children: (CommitNode | ShowAllNode)[] = [...Iterables.map(log.commits.values(), c => new CommitNode(c, this.context, this.git, this.branch))];
|
||||
if (log.truncated) {
|
||||
children.push(new ShowAllNode('Show All Commits', this, this.context));
|
||||
}
|
||||
return children;
|
||||
}
|
||||
|
||||
async getTreeItem(): Promise<TreeItem> {
|
||||
@@ -27,7 +38,7 @@ export class BranchHistoryNode extends ExplorerNode {
|
||||
name += ` ${GlyphChars.Space}${GlyphChars.ArrowLeftRight}${GlyphChars.Space} ${this.branch.tracking}`;
|
||||
}
|
||||
const item = new TreeItem(`${this.branch!.current ? `${GlyphChars.Check} ${GlyphChars.Space}` : ''}${name}`, TreeItemCollapsibleState.Collapsed);
|
||||
item.contextValue = this.resourceType;
|
||||
item.contextValue = this.branch.tracking ? `${this.resourceType}:remote` : this.resourceType;
|
||||
|
||||
item.iconPath = {
|
||||
dark: this.context.asAbsolutePath('images/dark/icon-branch.svg'),
|
||||
|
||||
@@ -9,7 +9,11 @@ export class BranchesNode extends ExplorerNode {
|
||||
|
||||
readonly resourceType: ResourceType = 'gitlens:branches';
|
||||
|
||||
constructor(uri: GitUri, protected readonly context: ExtensionContext, protected readonly git: GitService) {
|
||||
constructor(
|
||||
uri: GitUri,
|
||||
protected readonly context: ExtensionContext,
|
||||
protected readonly git: GitService
|
||||
) {
|
||||
super(uri);
|
||||
}
|
||||
|
||||
@@ -18,12 +22,16 @@ export class BranchesNode extends ExplorerNode {
|
||||
if (branches === undefined) return [];
|
||||
|
||||
branches.sort((a, b) => (a.current ? -1 : 1) - (b.current ? -1 : 1) || a.name.localeCompare(b.name));
|
||||
return [...Iterables.filterMap(branches, b => b.remote ? undefined : new BranchHistoryNode(b, this.uri, this.git.config.gitExplorer.commitFormat, this.context, this.git))];
|
||||
return [...Iterables.filterMap(branches, b => b.remote ? undefined : new BranchHistoryNode(b, this.uri, this.context, this.git))];
|
||||
}
|
||||
|
||||
getTreeItem(): TreeItem {
|
||||
async getTreeItem(): Promise<TreeItem> {
|
||||
const item = new TreeItem(`Branches`, TreeItemCollapsibleState.Expanded);
|
||||
item.contextValue = this.resourceType;
|
||||
|
||||
const remotes = await this.git.getRemotes(this.uri.repoPath!);
|
||||
item.contextValue = (remotes !== undefined && remotes.length > 0)
|
||||
? `${this.resourceType}:remote`
|
||||
: this.resourceType;
|
||||
|
||||
item.iconPath = {
|
||||
dark: this.context.asAbsolutePath('images/dark/icon-branch.svg'),
|
||||
|
||||
@@ -2,33 +2,56 @@
|
||||
import { Command, ExtensionContext, TreeItem, TreeItemCollapsibleState, Uri } from 'vscode';
|
||||
import { Commands, DiffWithPreviousCommandArgs } from '../commands';
|
||||
import { ExplorerNode, ResourceType } from './explorerNode';
|
||||
import { getGitStatusIcon, GitCommit, GitService, GitUri, IGitStatusFile, StatusFileFormatter } from '../gitService';
|
||||
import { CommitFormatter, getGitStatusIcon, GitBranch, GitCommit, GitService, GitUri, ICommitFormatOptions, IGitStatusFile, IStatusFormatOptions, StatusFileFormatter } from '../gitService';
|
||||
import * as path from 'path';
|
||||
|
||||
export enum CommitFileNodeDisplayAs {
|
||||
CommitLabel = 1 << 0,
|
||||
CommitIcon = 1 << 1,
|
||||
FileLabel = 1 << 2,
|
||||
StatusIcon = 1 << 3,
|
||||
|
||||
Commit = CommitLabel | CommitIcon,
|
||||
File = FileLabel | StatusIcon
|
||||
}
|
||||
|
||||
export class CommitFileNode extends ExplorerNode {
|
||||
|
||||
readonly priority: boolean = false;
|
||||
readonly repoPath: string;
|
||||
readonly resourceType: ResourceType = 'gitlens:commit-file';
|
||||
|
||||
constructor(public readonly status: IGitStatusFile, public commit: GitCommit, protected readonly context: ExtensionContext, protected readonly git: GitService) {
|
||||
constructor(
|
||||
public readonly status: IGitStatusFile,
|
||||
public commit: GitCommit,
|
||||
protected readonly context: ExtensionContext,
|
||||
protected readonly git: GitService,
|
||||
private displayAs: CommitFileNodeDisplayAs = CommitFileNodeDisplayAs.Commit,
|
||||
public readonly branch?: GitBranch
|
||||
) {
|
||||
super(new GitUri(Uri.file(path.resolve(commit.repoPath, status.fileName)), { repoPath: commit.repoPath, fileName: status.fileName, sha: commit.sha }));
|
||||
this.repoPath = commit.repoPath;
|
||||
}
|
||||
|
||||
getChildren(): Promise<ExplorerNode[]> {
|
||||
return Promise.resolve([]);
|
||||
async getChildren(): Promise<ExplorerNode[]> {
|
||||
return [];
|
||||
}
|
||||
|
||||
async getTreeItem(): Promise<TreeItem> {
|
||||
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) {
|
||||
this.commit = log.commits.get(this.commit.sha) || this.commit;
|
||||
}
|
||||
}
|
||||
|
||||
const item = new TreeItem(StatusFileFormatter.fromTemplate(this.git.config.gitExplorer.commitFileFormat, this.status), TreeItemCollapsibleState.None);
|
||||
const item = new TreeItem(this.label, TreeItemCollapsibleState.None);
|
||||
item.contextValue = this.resourceType;
|
||||
|
||||
const icon = getGitStatusIcon(this.status.status);
|
||||
const icon = (this.displayAs & CommitFileNodeDisplayAs.CommitIcon)
|
||||
? 'icon-commit.svg'
|
||||
: getGitStatusIcon(this.status.status);
|
||||
|
||||
item.iconPath = {
|
||||
dark: this.context.asAbsolutePath(path.join('images', 'dark', icon)),
|
||||
light: this.context.asAbsolutePath(path.join('images', 'light', icon))
|
||||
@@ -36,9 +59,52 @@ export class CommitFileNode extends ExplorerNode {
|
||||
|
||||
item.command = this.getCommand();
|
||||
|
||||
// Only cache the label for a single refresh
|
||||
this._label = undefined;
|
||||
|
||||
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;
|
||||
get label() {
|
||||
if (this._label === undefined) {
|
||||
this._label = (this.displayAs & CommitFileNodeDisplayAs.CommitLabel)
|
||||
? CommitFormatter.fromTemplate(this.getCommitTemplate(), this.commit, {
|
||||
truncateMessageAtNewLine: true,
|
||||
dataFormat: this.git.config.defaultDateFormat
|
||||
} as ICommitFormatOptions)
|
||||
: StatusFileFormatter.fromTemplate(this.getCommitFileTemplate(),
|
||||
this.status,
|
||||
{ relativePath: this.relativePath } as IStatusFormatOptions);
|
||||
}
|
||||
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() {
|
||||
return this.git.config.gitExplorer.commitFormat;
|
||||
}
|
||||
|
||||
protected getCommitFileTemplate() {
|
||||
return this.git.config.gitExplorer.commitFileFormat;
|
||||
}
|
||||
|
||||
getCommand(): Command | undefined {
|
||||
return {
|
||||
title: 'Compare File with Previous Revision',
|
||||
|
||||
@@ -1,59 +1,66 @@
|
||||
'use strict';
|
||||
import { Iterables } from '../system';
|
||||
import { Arrays, Iterables } from '../system';
|
||||
import { Command, ExtensionContext, TreeItem, TreeItemCollapsibleState } from 'vscode';
|
||||
import { Commands, DiffWithPreviousCommandArgs } from '../commands';
|
||||
import { CommitFileNode } from './commitFileNode';
|
||||
import { CommitFileNode, CommitFileNodeDisplayAs } from './commitFileNode';
|
||||
import { GitExplorerFilesLayout } from '../configuration';
|
||||
import { FolderNode, IFileExplorerNode } from './folderNode';
|
||||
import { ExplorerNode, ResourceType } from './explorerNode';
|
||||
import { CommitFormatter, getGitStatusIcon, 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 {
|
||||
|
||||
readonly repoPath: string;
|
||||
readonly resourceType: ResourceType = 'gitlens:commit';
|
||||
|
||||
constructor(public readonly commit: GitLogCommit, private readonly template: string, protected readonly context: ExtensionContext, protected readonly git: GitService) {
|
||||
constructor(
|
||||
public readonly commit: GitLogCommit,
|
||||
protected readonly context: ExtensionContext,
|
||||
protected readonly git: GitService,
|
||||
public readonly branch?: GitBranch
|
||||
) {
|
||||
super(new GitUri(commit.uri, commit));
|
||||
this.repoPath = commit.repoPath;
|
||||
}
|
||||
|
||||
async getChildren(): Promise<ExplorerNode[]> {
|
||||
if (this.commit.type === 'file') Promise.resolve([]);
|
||||
const repoPath = this.repoPath;
|
||||
|
||||
const log = await this.git.getLogForRepo(this.commit.repoPath, this.commit.sha, 1);
|
||||
const log = await this.git.getLogForRepo(repoPath, this.commit.sha, 1);
|
||||
if (log === undefined) return [];
|
||||
|
||||
const commit = Iterables.first(log.commits.values());
|
||||
if (commit === undefined) return [];
|
||||
|
||||
return [...Iterables.map(commit.fileStatuses, s => new CommitFileNode(s, commit, this.context, this.git))];
|
||||
let children: IFileExplorerNode[] = [
|
||||
...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;
|
||||
}
|
||||
|
||||
getTreeItem(): TreeItem {
|
||||
const item = new TreeItem(CommitFormatter.fromTemplate(this.template, this.commit, {
|
||||
const item = new TreeItem(CommitFormatter.fromTemplate(this.git.config.gitExplorer.commitFormat, this.commit, {
|
||||
truncateMessageAtNewLine: true,
|
||||
dataFormat: this.git.config.defaultDateFormat
|
||||
} as ICommitFormatOptions));
|
||||
} as ICommitFormatOptions), TreeItemCollapsibleState.Collapsed);
|
||||
|
||||
if (this.commit.type === 'file') {
|
||||
item.collapsibleState = TreeItemCollapsibleState.None;
|
||||
item.command = this.getCommand();
|
||||
const resourceType: ResourceType = 'gitlens:commit-file';
|
||||
item.contextValue = resourceType;
|
||||
|
||||
const icon = getGitStatusIcon(this.commit.status!);
|
||||
item.iconPath = {
|
||||
dark: this.context.asAbsolutePath(path.join('images', 'dark', icon)),
|
||||
light: this.context.asAbsolutePath(path.join('images', 'light', icon))
|
||||
};
|
||||
}
|
||||
else {
|
||||
item.collapsibleState = TreeItemCollapsibleState.Collapsed;
|
||||
item.contextValue = this.resourceType;
|
||||
|
||||
item.iconPath = {
|
||||
dark: this.context.asAbsolutePath('images/dark/icon-commit.svg'),
|
||||
light: this.context.asAbsolutePath('images/light/icon-commit.svg')
|
||||
};
|
||||
}
|
||||
item.contextValue = this.resourceType;
|
||||
item.iconPath = {
|
||||
dark: this.context.asAbsolutePath('images/dark/icon-commit.svg'),
|
||||
light: this.context.asAbsolutePath('images/light/icon-commit.svg')
|
||||
};
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
'use strict';
|
||||
import { Command, Event, TreeItem, TreeItemCollapsibleState } from 'vscode';
|
||||
import { Command, ExtensionContext, TreeItem, TreeItemCollapsibleState } from 'vscode';
|
||||
import { GlyphChars } from '../constants';
|
||||
import { GitUri } from '../gitService';
|
||||
import { RefreshNodeCommandArgs } from './gitExplorer';
|
||||
|
||||
export declare type ResourceType =
|
||||
'gitlens:branches' |
|
||||
@@ -8,8 +10,10 @@ export declare type ResourceType =
|
||||
'gitlens:commit' |
|
||||
'gitlens:commit-file' |
|
||||
'gitlens:file-history' |
|
||||
'gitlens:folder' |
|
||||
'gitlens:history' |
|
||||
'gitlens:message' |
|
||||
'gitlens:pager' |
|
||||
'gitlens:remote' |
|
||||
'gitlens:remotes' |
|
||||
'gitlens:repository' |
|
||||
@@ -17,6 +21,9 @@ export declare type ResourceType =
|
||||
'gitlens:stash-file' |
|
||||
'gitlens:stashes' |
|
||||
'gitlens:status' |
|
||||
'gitlens:status-file' |
|
||||
'gitlens:status-files' |
|
||||
'gitlens:status-file-commits' |
|
||||
'gitlens:status-upstream';
|
||||
|
||||
export abstract class ExplorerNode {
|
||||
@@ -31,10 +38,6 @@ export abstract class ExplorerNode {
|
||||
getCommand(): Command | undefined {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
onDidChangeTreeData?: Event<ExplorerNode>;
|
||||
|
||||
refresh?(): void;
|
||||
}
|
||||
|
||||
export class MessageNode extends ExplorerNode {
|
||||
@@ -54,4 +57,46 @@ export class MessageNode extends ExplorerNode {
|
||||
item.contextValue = this.resourceType;
|
||||
return item;
|
||||
}
|
||||
}
|
||||
|
||||
export class PagerNode extends ExplorerNode {
|
||||
|
||||
readonly resourceType: ResourceType = 'gitlens:pager';
|
||||
args: RefreshNodeCommandArgs = {};
|
||||
|
||||
constructor(private message: string, private node: ExplorerNode, protected readonly context: ExtensionContext) {
|
||||
super(new GitUri());
|
||||
}
|
||||
|
||||
getChildren(): ExplorerNode[] | Promise<ExplorerNode[]> {
|
||||
return [];
|
||||
}
|
||||
|
||||
getTreeItem(): TreeItem | Promise<TreeItem> {
|
||||
const item = new TreeItem(this.message, TreeItemCollapsibleState.None);
|
||||
item.contextValue = this.resourceType;
|
||||
item.command = this.getCommand();
|
||||
item.iconPath = {
|
||||
dark: this.context.asAbsolutePath('images/dark/icon-unfold.svg'),
|
||||
light: this.context.asAbsolutePath('images/light/icon-unfold.svg')
|
||||
};
|
||||
return item;
|
||||
}
|
||||
|
||||
getCommand(): Command | undefined {
|
||||
return {
|
||||
title: 'Refresh',
|
||||
command: 'gitlens.gitExplorer.refreshNode',
|
||||
arguments: [this.node, this.args]
|
||||
} as Command;
|
||||
}
|
||||
}
|
||||
|
||||
export class ShowAllNode extends PagerNode {
|
||||
|
||||
args: RefreshNodeCommandArgs = { maxCount: 0 };
|
||||
|
||||
constructor(message: string, node: ExplorerNode, context: ExtensionContext) {
|
||||
super(`${message} ${GlyphChars.Space}${GlyphChars.Dash}${GlyphChars.Space} this may take a while`, node, context);
|
||||
}
|
||||
}
|
||||
@@ -10,8 +10,10 @@ export * from './historyNode';
|
||||
export * from './remoteNode';
|
||||
export * from './remotesNode';
|
||||
export * from './repositoryNode';
|
||||
export * from './stashesNode';
|
||||
export * from './stashFileNode';
|
||||
export * from './stashNode';
|
||||
export * from './stashesNode';
|
||||
export * from './statusFileCommitsNode';
|
||||
export * from './statusFilesNode';
|
||||
export * from './statusNode';
|
||||
export * from './statusUpstreamNode';
|
||||
@@ -1,7 +1,7 @@
|
||||
'use strict';
|
||||
import { Iterables } from '../system';
|
||||
import { ExtensionContext, TreeItem, TreeItemCollapsibleState } from 'vscode';
|
||||
import { CommitNode } from './commitNode';
|
||||
import { CommitFileNode, CommitFileNodeDisplayAs } from './commitFileNode';
|
||||
import { ExplorerNode, MessageNode, ResourceType } from './explorerNode';
|
||||
import { GitService, GitUri } from '../gitService';
|
||||
|
||||
@@ -9,15 +9,19 @@ export class FileHistoryNode extends ExplorerNode {
|
||||
|
||||
readonly resourceType: ResourceType = 'gitlens:file-history';
|
||||
|
||||
constructor(uri: GitUri, protected readonly context: ExtensionContext, protected readonly git: GitService) {
|
||||
constructor(
|
||||
uri: GitUri,
|
||||
protected readonly context: ExtensionContext,
|
||||
protected readonly git: GitService
|
||||
) {
|
||||
super(uri);
|
||||
}
|
||||
}
|
||||
|
||||
async getChildren(): Promise<ExplorerNode[]> {
|
||||
const log = await this.git.getLogForFile(this.uri.repoPath, this.uri.fsPath, this.uri.sha);
|
||||
if (log === undefined) return [new MessageNode('No file history')];
|
||||
|
||||
return [...Iterables.map(log.commits.values(), c => new CommitNode(c, this.git.config.gitExplorer.commitFormat, this.context, this.git))];
|
||||
return [...Iterables.map(log.commits.values(), c => new CommitFileNode(c.fileStatuses[0], c, this.context, this.git, CommitFileNodeDisplayAs.CommitLabel | CommitFileNodeDisplayAs.StatusIcon))];
|
||||
}
|
||||
|
||||
getTreeItem(): TreeItem {
|
||||
|
||||
85
src/views/folderNode.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -1,19 +1,21 @@
|
||||
'use strict';
|
||||
import { Functions, Objects } from '../system';
|
||||
import { commands, Event, EventEmitter, ExtensionContext, TextDocumentShowOptions, TextEditor, TreeDataProvider, TreeItem, Uri, window, workspace } from 'vscode';
|
||||
import { Commands, DiffWithPreviousCommandArgs, DiffWithWorkingCommandArgs, openEditor, OpenFileInRemoteCommandArgs } from '../commands';
|
||||
import { Commands, DiffWithCommandArgs, DiffWithCommandArgsRevision, DiffWithPreviousCommandArgs, DiffWithWorkingCommandArgs, openEditor, OpenFileInRemoteCommandArgs } from '../commands';
|
||||
import { UriComparer } from '../comparers';
|
||||
import { ExtensionKey, IConfig } from '../configuration';
|
||||
import { CommandContext, setCommandContext } from '../constants';
|
||||
import { CommitFileNode, CommitNode, ExplorerNode, HistoryNode, MessageNode, RepositoryNode, StashNode } from './explorerNodes';
|
||||
import { CommandContext, setCommandContext, WorkspaceState } from '../constants';
|
||||
import { BranchHistoryNode, CommitFileNode, CommitNode, ExplorerNode, HistoryNode, MessageNode, RepositoryNode, StashNode } from './explorerNodes';
|
||||
import { GitService, GitUri, RepoChangedReasons } from '../gitService';
|
||||
|
||||
export * from './explorerNodes';
|
||||
|
||||
export type GitExplorerView =
|
||||
'auto' |
|
||||
'history' |
|
||||
'repository';
|
||||
export const GitExplorerView = {
|
||||
Auto: 'auto' as GitExplorerView,
|
||||
History: 'history' as GitExplorerView,
|
||||
Repository: 'repository' as GitExplorerView
|
||||
};
|
||||
@@ -23,11 +25,15 @@ export interface OpenFileRevisionCommandArgs {
|
||||
showOptions?: TextDocumentShowOptions;
|
||||
}
|
||||
|
||||
export interface RefreshNodeCommandArgs {
|
||||
maxCount?: number;
|
||||
}
|
||||
|
||||
export class GitExplorer implements TreeDataProvider<ExplorerNode> {
|
||||
|
||||
private _config: IConfig;
|
||||
private _root?: ExplorerNode;
|
||||
private _view: GitExplorerView = GitExplorerView.Repository;
|
||||
private _view: GitExplorerView | undefined;
|
||||
|
||||
private _onDidChangeTreeData = new EventEmitter<ExplorerNode>();
|
||||
public get onDidChangeTreeData(): Event<ExplorerNode> {
|
||||
@@ -38,12 +44,15 @@ export class GitExplorer implements TreeDataProvider<ExplorerNode> {
|
||||
commands.registerCommand('gitlens.gitExplorer.switchToHistoryView', () => this.switchTo(GitExplorerView.History), this);
|
||||
commands.registerCommand('gitlens.gitExplorer.switchToRepositoryView', () => this.switchTo(GitExplorerView.Repository), this);
|
||||
commands.registerCommand('gitlens.gitExplorer.refresh', this.refresh, this);
|
||||
commands.registerCommand('gitlens.gitExplorer.refreshNode', this.refreshNode, this);
|
||||
commands.registerCommand('gitlens.gitExplorer.openChanges', this.openChanges, this);
|
||||
commands.registerCommand('gitlens.gitExplorer.openChangesWithWorking', this.openChangesWithWorking, this);
|
||||
commands.registerCommand('gitlens.gitExplorer.openFile', this.openFile, this);
|
||||
commands.registerCommand('gitlens.gitExplorer.openFileRevision', this.openFileRevision, this);
|
||||
commands.registerCommand('gitlens.gitExplorer.openFileRevisionInRemote', this.openFileRevisionInRemote, this);
|
||||
commands.registerCommand('gitlens.gitExplorer.openChangedFiles', this.openChangedFiles, this);
|
||||
commands.registerCommand('gitlens.gitExplorer.openChangedFileChanges', this.openChangedFileChanges, this);
|
||||
commands.registerCommand('gitlens.gitExplorer.openChangedFileChangesWithWorking', this.openChangedFileChangesWithWorking, this);
|
||||
commands.registerCommand('gitlens.gitExplorer.openChangedFileRevisions', this.openChangedFileRevisions, this);
|
||||
commands.registerCommand('gitlens.gitExplorer.applyChanges', this.applyChanges, this);
|
||||
|
||||
@@ -55,10 +64,6 @@ export class GitExplorer implements TreeDataProvider<ExplorerNode> {
|
||||
context.subscriptions.push(workspace.onDidChangeConfiguration(this.onConfigurationChanged, this));
|
||||
|
||||
this.onConfigurationChanged();
|
||||
|
||||
this._view = this._config.gitExplorer.view;
|
||||
setCommandContext(CommandContext.GitExplorerView, this._view);
|
||||
this._root = this.getRootNode();
|
||||
}
|
||||
|
||||
async getTreeItem(node: ExplorerNode): Promise<TreeItem> {
|
||||
@@ -76,14 +81,14 @@ export class GitExplorer implements TreeDataProvider<ExplorerNode> {
|
||||
}
|
||||
|
||||
private getRootNode(editor?: TextEditor): ExplorerNode | undefined {
|
||||
const uri = new GitUri(Uri.file(this.git.repoPath), { repoPath: this.git.repoPath, fileName: this.git.repoPath });
|
||||
|
||||
switch (this._view) {
|
||||
case GitExplorerView.History: return this.getHistoryNode(editor || window.activeTextEditor);
|
||||
case GitExplorerView.Repository: return new RepositoryNode(uri, this.context, this.git);
|
||||
}
|
||||
case GitExplorerView.History:
|
||||
return this.getHistoryNode(editor || window.activeTextEditor);
|
||||
|
||||
return undefined;
|
||||
default:
|
||||
const uri = new GitUri(Uri.file(this.git.repoPath), { repoPath: this.git.repoPath, fileName: this.git.repoPath });
|
||||
return new RepositoryNode(uri, this.context, this.git);
|
||||
}
|
||||
}
|
||||
|
||||
private getHistoryNode(editor: TextEditor | undefined): ExplorerNode | undefined {
|
||||
@@ -109,15 +114,21 @@ export class GitExplorer implements TreeDataProvider<ExplorerNode> {
|
||||
private onConfigurationChanged() {
|
||||
const cfg = workspace.getConfiguration().get<IConfig>(ExtensionKey)!;
|
||||
|
||||
if (!Objects.areEquivalent(cfg.gitExplorer, this._config && this._config.gitExplorer)) {
|
||||
setTimeout(() => {
|
||||
this._root = this.getRootNode(window.activeTextEditor);
|
||||
this.refresh();
|
||||
}, 1);
|
||||
}
|
||||
const changed = !Objects.areEquivalent(cfg.gitExplorer, this._config && this._config.gitExplorer);
|
||||
|
||||
this._config = cfg;
|
||||
}
|
||||
|
||||
if (changed) {
|
||||
let view = cfg.gitExplorer.view;
|
||||
if (view === GitExplorerView.Auto) {
|
||||
view = this.context.workspaceState.get<GitExplorerView>(WorkspaceState.GitExplorerView, GitExplorerView.Repository);
|
||||
}
|
||||
|
||||
this.setView(view);
|
||||
this._root = this.getRootNode(window.activeTextEditor);
|
||||
this.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
private onRepoChanged(reasons: RepoChangedReasons[]) {
|
||||
if (this._view !== GitExplorerView.Repository) return;
|
||||
@@ -133,12 +144,34 @@ export class GitExplorer implements TreeDataProvider<ExplorerNode> {
|
||||
this._onDidChangeTreeData.fire(node);
|
||||
}
|
||||
|
||||
switchTo(view: GitExplorerView) {
|
||||
refreshNode(node: ExplorerNode, args: RefreshNodeCommandArgs) {
|
||||
if (node instanceof BranchHistoryNode) {
|
||||
node.maxCount = args.maxCount;
|
||||
}
|
||||
|
||||
this.refresh(node);
|
||||
}
|
||||
|
||||
setView(view: GitExplorerView) {
|
||||
if (this._view === view) return;
|
||||
|
||||
if (this._config.gitExplorer.view === GitExplorerView.Auto) {
|
||||
this.context.workspaceState.update(WorkspaceState.GitExplorerView, view);
|
||||
}
|
||||
|
||||
this._view = view;
|
||||
setCommandContext(CommandContext.GitExplorerView, this._view);
|
||||
|
||||
if (view !== GitExplorerView.Repository) {
|
||||
this.git.stopWatchingFileSystem();
|
||||
}
|
||||
}
|
||||
|
||||
switchTo(view: GitExplorerView) {
|
||||
if (this._view === view) return;
|
||||
|
||||
this.setView(view);
|
||||
|
||||
this._root = this.getRootNode(window.activeTextEditor);
|
||||
this.refresh();
|
||||
}
|
||||
@@ -177,6 +210,29 @@ export class GitExplorer implements TreeDataProvider<ExplorerNode> {
|
||||
return openEditor(options.uri || GitService.toGitContentUri(node.uri), options.showOptions || { preserveFocus: true, preview: false });
|
||||
}
|
||||
|
||||
private async openChangedFileChanges(node: CommitNode | StashNode, options: TextDocumentShowOptions = { preserveFocus: false, preview: false }) {
|
||||
const repoPath = node.commit.repoPath;
|
||||
const uris = node.commit.fileStatuses
|
||||
.map(s => GitUri.fromFileStatus(s, repoPath));
|
||||
for (const uri of uris) {
|
||||
await this.openDiffWith(repoPath,
|
||||
{ uri: uri, sha: node.commit.previousSha !== undefined ? node.commit.previousSha : GitService.fakeSha },
|
||||
{ uri: uri, sha: node.commit.sha }, options);
|
||||
}
|
||||
}
|
||||
|
||||
private async openChangedFileChangesWithWorking(node: CommitNode | StashNode, options: TextDocumentShowOptions = { preserveFocus: false, preview: false }) {
|
||||
const repoPath = node.commit.repoPath;
|
||||
const uris = node.commit.fileStatuses
|
||||
.filter(s => s.status !== 'D')
|
||||
.map(s => GitUri.fromFileStatus(s, repoPath));
|
||||
for (const uri of uris) {
|
||||
await this.openDiffWith(repoPath,
|
||||
{ uri: uri, sha: node.commit.sha },
|
||||
{ uri: uri, sha: '' }, options);
|
||||
}
|
||||
}
|
||||
|
||||
private async openChangedFiles(node: CommitNode | StashNode, options: TextDocumentShowOptions = { preserveFocus: false, preview: false }) {
|
||||
const repoPath = node.commit.repoPath;
|
||||
const uris = node.commit.fileStatuses.filter(s => s.status !== 'D').map(s => GitUri.fromFileStatus(s, repoPath));
|
||||
@@ -194,6 +250,16 @@ export class GitExplorer implements TreeDataProvider<ExplorerNode> {
|
||||
}
|
||||
}
|
||||
|
||||
private async openDiffWith(repoPath: string, lhs: DiffWithCommandArgsRevision, rhs: DiffWithCommandArgsRevision, options: TextDocumentShowOptions = { preserveFocus: false, preview: false }) {
|
||||
const diffArgs: DiffWithCommandArgs = {
|
||||
repoPath: repoPath,
|
||||
lhs: lhs,
|
||||
rhs: rhs,
|
||||
showOptions: options
|
||||
};
|
||||
return commands.executeCommand(Commands.DiffWith, diffArgs);
|
||||
}
|
||||
|
||||
private async openFileRevisionInRemote(node: CommitNode | StashNode) {
|
||||
return commands.executeCommand(Commands.OpenFileInRemote, new GitUri(node.commit.uri, node.commit), { range: false } as OpenFileInRemoteCommandArgs);
|
||||
}
|
||||
|
||||
@@ -8,9 +8,13 @@ export class HistoryNode extends ExplorerNode {
|
||||
|
||||
readonly resourceType: ResourceType = 'gitlens:history';
|
||||
|
||||
constructor(uri: GitUri, protected readonly context: ExtensionContext, protected readonly git: GitService) {
|
||||
constructor(
|
||||
uri: GitUri,
|
||||
protected readonly context: ExtensionContext,
|
||||
protected readonly git: GitService
|
||||
) {
|
||||
super(uri);
|
||||
}
|
||||
}
|
||||
|
||||
async getChildren(): Promise<ExplorerNode[]> {
|
||||
return [new FileHistoryNode(this.uri, this.context, this.git)];
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import { Iterables } from '../system';
|
||||
import { ExtensionContext, TreeItem, TreeItemCollapsibleState } from 'vscode';
|
||||
import { BranchHistoryNode } from './branchHistoryNode';
|
||||
import { GlyphChars } from '../constants';
|
||||
import { ExplorerNode, ResourceType } from './explorerNode';
|
||||
import { GitRemote, GitService, GitUri } from '../gitService';
|
||||
|
||||
@@ -9,7 +10,12 @@ export class RemoteNode extends ExplorerNode {
|
||||
|
||||
readonly resourceType: ResourceType = 'gitlens:remote';
|
||||
|
||||
constructor(public readonly remote: GitRemote, uri: GitUri, protected readonly context: ExtensionContext, protected readonly git: GitService) {
|
||||
constructor(
|
||||
public readonly remote: GitRemote,
|
||||
uri: GitUri,
|
||||
protected readonly context: ExtensionContext,
|
||||
protected readonly git: GitService
|
||||
) {
|
||||
super(uri);
|
||||
}
|
||||
|
||||
@@ -18,11 +24,30 @@ export class RemoteNode extends ExplorerNode {
|
||||
if (branches === undefined) return [];
|
||||
|
||||
branches.sort((a, b) => a.name.localeCompare(b.name));
|
||||
return [...Iterables.filterMap(branches, b => !b.remote || !b.name.startsWith(this.remote.name) ? undefined : new BranchHistoryNode(b, this.uri, this.git.config.gitExplorer.commitFormat, this.context, this.git))];
|
||||
return [...Iterables.filterMap(branches, b => !b.remote || !b.name.startsWith(this.remote.name) ? undefined : new BranchHistoryNode(b, this.uri, this.context, this.git))];
|
||||
}
|
||||
|
||||
getTreeItem(): TreeItem {
|
||||
const item = new TreeItem(this.remote.name, TreeItemCollapsibleState.Collapsed);
|
||||
const fetch = this.remote.types.includes('push');
|
||||
const push = this.remote.types.includes('push');
|
||||
|
||||
let separator;
|
||||
if (fetch && push) {
|
||||
separator = GlyphChars.ArrowLeftRight;
|
||||
}
|
||||
else if (fetch) {
|
||||
separator = GlyphChars.ArrowLeft;
|
||||
}
|
||||
else if (push) {
|
||||
separator = GlyphChars.ArrowRight;
|
||||
}
|
||||
else {
|
||||
separator = GlyphChars.Dash;
|
||||
}
|
||||
|
||||
const label = `${this.remote.name} ${GlyphChars.Space}${separator}${GlyphChars.Space} ${(this.remote.provider !== undefined) ? this.remote.provider.name : this.remote.domain} ${GlyphChars.Space}${GlyphChars.Dot}${GlyphChars.Space} ${this.remote.path}`;
|
||||
|
||||
const item = new TreeItem(label, TreeItemCollapsibleState.Collapsed);
|
||||
item.contextValue = this.resourceType;
|
||||
|
||||
// item.iconPath = {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
'use strict';
|
||||
import { Arrays, Iterables } from '../system';
|
||||
import { Iterables } from '../system';
|
||||
import { ExtensionContext, TreeItem, TreeItemCollapsibleState } from 'vscode';
|
||||
import { ExplorerNode, MessageNode, ResourceType } from './explorerNode';
|
||||
import { GitService, GitUri } from '../gitService';
|
||||
@@ -9,12 +9,16 @@ export class RemotesNode extends ExplorerNode {
|
||||
|
||||
readonly resourceType: ResourceType = 'gitlens:remotes';
|
||||
|
||||
constructor(uri: GitUri, protected readonly context: ExtensionContext, protected readonly git: GitService) {
|
||||
constructor(
|
||||
uri: GitUri,
|
||||
protected readonly context: ExtensionContext,
|
||||
protected readonly git: GitService
|
||||
) {
|
||||
super(uri);
|
||||
}
|
||||
|
||||
async getChildren(): Promise<ExplorerNode[]> {
|
||||
const remotes = Arrays.uniqueBy(await this.git.getRemotes(this.uri.repoPath!), r => r.url, r => !!r.provider);
|
||||
const remotes = await this.git.getRemotes(this.uri.repoPath!);
|
||||
if (remotes === undefined || remotes.length === 0) return [new MessageNode('No remotes configured')];
|
||||
|
||||
remotes.sort((a, b) => a.name.localeCompare(b.name));
|
||||
|
||||
@@ -12,9 +12,13 @@ export class RepositoryNode extends ExplorerNode {
|
||||
|
||||
readonly resourceType: ResourceType = 'gitlens:repository';
|
||||
|
||||
constructor(uri: GitUri, protected readonly context: ExtensionContext, protected readonly git: GitService) {
|
||||
constructor(
|
||||
uri: GitUri,
|
||||
protected readonly context: ExtensionContext,
|
||||
protected readonly git: GitService
|
||||
) {
|
||||
super(uri);
|
||||
}
|
||||
}
|
||||
|
||||
async getChildren(): Promise<ExplorerNode[]> {
|
||||
return [
|
||||
|
||||
@@ -2,13 +2,26 @@
|
||||
import { ExtensionContext } from 'vscode';
|
||||
import { ResourceType } from './explorerNode';
|
||||
import { GitService, GitStashCommit, IGitStatusFile } from '../gitService';
|
||||
import { CommitFileNode } from './commitFileNode';
|
||||
import { CommitFileNode, CommitFileNodeDisplayAs } from './commitFileNode';
|
||||
|
||||
export class StashFileNode extends CommitFileNode {
|
||||
|
||||
readonly resourceType: ResourceType = 'gitlens:stash-file';
|
||||
|
||||
constructor(readonly status: IGitStatusFile, readonly commit: GitStashCommit, readonly context: ExtensionContext, readonly git: GitService) {
|
||||
super(status, commit, context, git);
|
||||
constructor(
|
||||
readonly status: IGitStatusFile,
|
||||
readonly commit: GitStashCommit,
|
||||
readonly context: ExtensionContext,
|
||||
readonly git: GitService
|
||||
) {
|
||||
super(status, commit, context, git, CommitFileNodeDisplayAs.File);
|
||||
}
|
||||
|
||||
protected getCommitTemplate() {
|
||||
return this.git.config.gitExplorer.stashFormat;
|
||||
}
|
||||
|
||||
protected getCommitFileTemplate() {
|
||||
return this.git.config.gitExplorer.stashFileFormat;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
'use strict';
|
||||
import { Event, EventEmitter, ExtensionContext, TreeItem, TreeItemCollapsibleState } from 'vscode';
|
||||
import { Iterables } from '../system';
|
||||
import { ExtensionContext, TreeItem, TreeItemCollapsibleState } from 'vscode';
|
||||
import { ExplorerNode, ResourceType } from './explorerNode';
|
||||
import { CommitFormatter, GitService, GitStashCommit, GitUri, ICommitFormatOptions } from '../gitService';
|
||||
import { StashFileNode } from './stashFileNode';
|
||||
@@ -8,17 +9,31 @@ export class StashNode extends ExplorerNode {
|
||||
|
||||
readonly resourceType: ResourceType = 'gitlens:stash';
|
||||
|
||||
private _onDidChangeTreeData = new EventEmitter<ExplorerNode>();
|
||||
public get onDidChangeTreeData(): Event<ExplorerNode> {
|
||||
return this._onDidChangeTreeData.event;
|
||||
}
|
||||
|
||||
constructor(public readonly commit: GitStashCommit, protected readonly context: ExtensionContext, protected readonly git: GitService) {
|
||||
constructor(
|
||||
public readonly commit: GitStashCommit,
|
||||
protected readonly context: ExtensionContext,
|
||||
protected readonly git: GitService
|
||||
) {
|
||||
super(new GitUri(commit.uri, commit));
|
||||
}
|
||||
|
||||
async getChildren(): Promise<ExplorerNode[]> {
|
||||
return Promise.resolve((this.commit as GitStashCommit).fileStatuses.map(s => new StashFileNode(s, this.commit, this.context, this.git)));
|
||||
const statuses = (this.commit as GitStashCommit).fileStatuses;
|
||||
|
||||
// Check for any untracked files -- since git doesn't return them via `git stash list` :(
|
||||
const log = await this.git.getLogForRepo(this.commit.repoPath, `${(this.commit as GitStashCommit).stashName}^3`, 1);
|
||||
if (log !== undefined) {
|
||||
const commit = Iterables.first(log.commits.values());
|
||||
if (commit !== undefined && commit.fileStatuses.length !== 0) {
|
||||
// Since these files are untracked -- make them look that way
|
||||
commit.fileStatuses.forEach(s => s.status = '?');
|
||||
statuses.splice(statuses.length, 0, ...commit.fileStatuses);
|
||||
}
|
||||
}
|
||||
|
||||
const children = statuses.map(s => new StashFileNode(s, this.commit, this.context, this.git));
|
||||
children.sort((a, b) => a.label!.localeCompare(b.label!));
|
||||
return children;
|
||||
}
|
||||
|
||||
getTreeItem(): TreeItem {
|
||||
@@ -29,8 +44,4 @@ export class StashNode extends ExplorerNode {
|
||||
item.contextValue = this.resourceType;
|
||||
return item;
|
||||
}
|
||||
|
||||
refresh() {
|
||||
this._onDidChangeTreeData.fire();
|
||||
}
|
||||
}
|
||||
@@ -9,9 +9,13 @@ export class StashesNode extends ExplorerNode {
|
||||
|
||||
readonly resourceType: ResourceType = 'gitlens:stashes';
|
||||
|
||||
constructor(uri: GitUri, protected readonly context: ExtensionContext, protected readonly git: GitService) {
|
||||
constructor(
|
||||
uri: GitUri,
|
||||
protected readonly context: ExtensionContext,
|
||||
protected readonly git: GitService
|
||||
) {
|
||||
super(uri);
|
||||
}
|
||||
}
|
||||
|
||||
async getChildren(): Promise<ExplorerNode[]> {
|
||||
const stash = await this.git.getStashList(this.uri.repoPath!);
|
||||
|
||||
102
src/views/statusFileCommitsNode.ts
Normal file
@@ -0,0 +1,102 @@
|
||||
'use strict';
|
||||
import { Command, ExtensionContext, TreeItem, TreeItemCollapsibleState, Uri } from 'vscode';
|
||||
import { Commands, DiffWithPreviousCommandArgs } from '../commands';
|
||||
import { CommitFileNode, CommitFileNodeDisplayAs } from './commitFileNode';
|
||||
import { ExplorerNode, ResourceType } from './explorerNode';
|
||||
import { getGitStatusIcon, GitBranch, GitLogCommit, GitService, GitUri, IGitStatusFile, IGitStatusFileWithCommit, IStatusFormatOptions, StatusFileFormatter } from '../gitService';
|
||||
import * as path from 'path';
|
||||
|
||||
export class StatusFileCommitsNode extends ExplorerNode {
|
||||
|
||||
readonly resourceType: ResourceType = 'gitlens:status-file-commits';
|
||||
|
||||
constructor(
|
||||
public readonly repoPath: string,
|
||||
public readonly status: IGitStatusFile,
|
||||
public commits: GitLogCommit[],
|
||||
protected readonly context: ExtensionContext,
|
||||
protected readonly git: GitService,
|
||||
public readonly branch?: GitBranch
|
||||
) {
|
||||
super(new GitUri(Uri.file(path.resolve(repoPath, status.fileName)), { repoPath: repoPath, fileName: status.fileName, sha: 'HEAD' }));
|
||||
}
|
||||
|
||||
async getChildren(): Promise<ExplorerNode[]> {
|
||||
return this.commits.map(c => new CommitFileNode(this.status, c, this.context, this.git, CommitFileNodeDisplayAs.Commit, this.branch));
|
||||
}
|
||||
|
||||
async getTreeItem(): Promise<TreeItem> {
|
||||
const item = new TreeItem(this.label, TreeItemCollapsibleState.Collapsed);
|
||||
item.contextValue = this.resourceType;
|
||||
|
||||
const icon = getGitStatusIcon(this.status.status);
|
||||
item.iconPath = {
|
||||
dark: this.context.asAbsolutePath(path.join('images', 'dark', icon)),
|
||||
light: this.context.asAbsolutePath(path.join('images', 'light', icon))
|
||||
};
|
||||
|
||||
if (this.commits.length === 1 && this.commits[0].isUncommitted) {
|
||||
item.collapsibleState = TreeItemCollapsibleState.None;
|
||||
item.contextValue = 'gitlens:status-file' as ResourceType;
|
||||
item.command = this.getCommand();
|
||||
}
|
||||
|
||||
// Only cache the label for a single refresh
|
||||
this._label = undefined;
|
||||
|
||||
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;
|
||||
get label() {
|
||||
if (this._label === undefined) {
|
||||
this._label = StatusFileFormatter.fromTemplate(this.git.config.gitExplorer.statusFileFormat,
|
||||
{ ...this.status, commit: this.commit } as IGitStatusFileWithCommit,
|
||||
{ relativePath: this.relativePath } as IStatusFormatOptions);
|
||||
}
|
||||
return this._label;
|
||||
}
|
||||
|
||||
get commit() {
|
||||
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 {
|
||||
return {
|
||||
title: 'Compare File with Previous Revision',
|
||||
command: Commands.DiffWithPrevious,
|
||||
arguments: [
|
||||
GitUri.fromFileStatus(this.status, this.repoPath),
|
||||
{
|
||||
commit: this.commit,
|
||||
line: 0,
|
||||
showOptions: {
|
||||
preserveFocus: true,
|
||||
preview: true
|
||||
}
|
||||
} as DiffWithPreviousCommandArgs
|
||||
]
|
||||
};
|
||||
}
|
||||
}
|
||||
103
src/views/statusFilesNode.ts
Normal file
@@ -0,0 +1,103 @@
|
||||
'use strict';
|
||||
import { Arrays, Iterables, Objects } from '../system';
|
||||
import { ExtensionContext, TreeItem, TreeItemCollapsibleState, Uri } from 'vscode';
|
||||
import { GitExplorerFilesLayout } from '../configuration';
|
||||
import { ExplorerNode, ResourceType, ShowAllNode } from './explorerNode';
|
||||
import { FolderNode, IFileExplorerNode } from './folderNode';
|
||||
import { GitBranch, GitLog, GitLogCommit, GitService, GitStatus, GitUri, IGitStatusFileWithCommit } from '../gitService';
|
||||
import { StatusFileCommitsNode } from './statusFileCommitsNode';
|
||||
import * as path from 'path';
|
||||
|
||||
export class StatusFilesNode extends ExplorerNode {
|
||||
|
||||
readonly repoPath: string;
|
||||
readonly resourceType: ResourceType = 'gitlens:status-files';
|
||||
|
||||
maxCount: number | undefined = undefined;
|
||||
|
||||
constructor(
|
||||
public readonly status: GitStatus,
|
||||
public readonly range: string | undefined,
|
||||
protected readonly context: ExtensionContext,
|
||||
protected readonly git: GitService,
|
||||
public readonly branch?: GitBranch
|
||||
) {
|
||||
super(new GitUri(Uri.file(status.repoPath), { repoPath: status.repoPath, fileName: status.repoPath }));
|
||||
this.repoPath = status.repoPath;
|
||||
}
|
||||
|
||||
async getChildren(): Promise<ExplorerNode[]> {
|
||||
let statuses: IGitStatusFileWithCommit[] = [];
|
||||
|
||||
const repoPath = this.repoPath;
|
||||
|
||||
let log: GitLog | undefined;
|
||||
if (this.range !== undefined) {
|
||||
log = await this.git.getLogForRepo(repoPath, this.range, this.maxCount);
|
||||
if (log !== undefined) {
|
||||
statuses = Array.from(Iterables.flatMap(log.commits.values(), c => {
|
||||
return c.fileStatuses.map(s => {
|
||||
return { ...s, commit: c } as IGitStatusFileWithCommit;
|
||||
});
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
if (this.status.files.length !== 0 && this.includeWorkingTree) {
|
||||
statuses.splice(0, 0, ...this.status.files.map(s => {
|
||||
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());
|
||||
|
||||
const groups = Arrays.groupBy(statuses, s => s.fileName);
|
||||
|
||||
let children: IFileExplorerNode[] = [
|
||||
...Iterables.map(Objects.values(groups), statuses => new StatusFileCommitsNode(repoPath, statuses[statuses.length - 1], statuses.map(s => s.commit), this.context, this.git, 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.priority ? -1 : 1) - (b.priority ? -1 : 1) || a.label!.localeCompare(b.label!));
|
||||
}
|
||||
|
||||
if (log !== undefined && log.truncated) {
|
||||
(children as (IFileExplorerNode | ShowAllNode)[]).push(new ShowAllNode('Show All Changes', this, this.context));
|
||||
}
|
||||
return children;
|
||||
}
|
||||
|
||||
async getTreeItem(): Promise<TreeItem> {
|
||||
let files = (this.status.files !== undefined && this.includeWorkingTree) ? this.status.files.length : 0;
|
||||
|
||||
if (this.status.upstream !== undefined) {
|
||||
const stats = await this.git.getChangedFilesCount(this.repoPath, `${this.status.upstream}...`);
|
||||
if (stats !== undefined) {
|
||||
files += stats.files;
|
||||
}
|
||||
}
|
||||
|
||||
const label = `${files} file${files > 1 ? 's' : ''} changed`; // ${this.status.upstream === undefined ? '' : ` (ahead of ${this.status.upstream})`}`;
|
||||
const item = new TreeItem(label, TreeItemCollapsibleState.Collapsed);
|
||||
item.contextValue = this.resourceType;
|
||||
item.iconPath = {
|
||||
dark: this.context.asAbsolutePath(`images/dark/icon-diff.svg`),
|
||||
light: this.context.asAbsolutePath(`images/light/icon-diff.svg`)
|
||||
};
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
private get includeWorkingTree(): boolean {
|
||||
return this.git.config.gitExplorer.includeWorkingTree;
|
||||
}
|
||||
}
|
||||
@@ -1,46 +1,76 @@
|
||||
import { ExtensionContext, TreeItem, TreeItemCollapsibleState } from 'vscode';
|
||||
import { commands, Disposable, ExtensionContext, TreeItem, TreeItemCollapsibleState, Uri } from 'vscode';
|
||||
import { ExplorerNode, ResourceType } from './explorerNode';
|
||||
import { GitService, GitUri } from '../gitService';
|
||||
import { GitService, GitStatus, GitUri } from '../gitService';
|
||||
import { StatusFilesNode } from './statusFilesNode';
|
||||
import { StatusUpstreamNode } from './statusUpstreamNode';
|
||||
|
||||
let _eventDisposable: Disposable | undefined;
|
||||
|
||||
export class StatusNode extends ExplorerNode {
|
||||
|
||||
readonly resourceType: ResourceType = 'gitlens:status';
|
||||
|
||||
constructor(uri: GitUri, protected readonly context: ExtensionContext, protected readonly git: GitService) {
|
||||
constructor(
|
||||
uri: GitUri,
|
||||
protected readonly context: ExtensionContext,
|
||||
protected readonly git: GitService
|
||||
) {
|
||||
super(uri);
|
||||
}
|
||||
}
|
||||
|
||||
async getChildren(): Promise<ExplorerNode[]> {
|
||||
const status = await this.git.getStatusForRepo(this.uri.repoPath!);
|
||||
if (status === undefined) return [];
|
||||
|
||||
const children = [];
|
||||
const children: ExplorerNode[] = [];
|
||||
|
||||
if (status.state.behind) {
|
||||
children.push(new StatusUpstreamNode(status, 'behind', this.git.config.gitExplorer.commitFormat, this.context, this.git));
|
||||
children.push(new StatusUpstreamNode(status, 'behind', this.context, this.git));
|
||||
}
|
||||
|
||||
if (status.state.ahead) {
|
||||
children.push(new StatusUpstreamNode(status, 'ahead', this.git.config.gitExplorer.commitFormat, this.context, this.git));
|
||||
children.push(new StatusUpstreamNode(status, 'ahead', this.context, this.git));
|
||||
}
|
||||
|
||||
if (status.state.ahead || (status.files.length !== 0 && this.includeWorkingTree)) {
|
||||
const range = status.upstream
|
||||
? `${status.upstream}..${status.branch}`
|
||||
: undefined;
|
||||
children.push(new StatusFilesNode(status, range, this.context, this.git));
|
||||
}
|
||||
|
||||
return children;
|
||||
}
|
||||
|
||||
async getTreeItem(): Promise<TreeItem> {
|
||||
private _status: GitStatus | undefined;
|
||||
|
||||
async getTreeItem(): Promise < TreeItem > {
|
||||
const status = await this.git.getStatusForRepo(this.uri.repoPath!);
|
||||
if (status === undefined) return new TreeItem('No repo status');
|
||||
|
||||
if (_eventDisposable !== undefined) {
|
||||
_eventDisposable.dispose();
|
||||
_eventDisposable = undefined;
|
||||
}
|
||||
|
||||
if (this.includeWorkingTree) {
|
||||
this._status = status;
|
||||
|
||||
_eventDisposable = this.git.onDidChangeFileSystem(this.onFileSystemChanged, this);
|
||||
this.git.startWatchingFileSystem();
|
||||
}
|
||||
|
||||
let hasChildren = false;
|
||||
const hasWorkingChanges = status.files.length !== 0 && this.includeWorkingTree;
|
||||
let label = '';
|
||||
let iconSuffix = '';
|
||||
if (status.upstream) {
|
||||
if (!status.state.ahead && !status.state.behind) {
|
||||
label = `${status.branch} is up-to-date with ${status.upstream}`;
|
||||
label = `${status.branch}${hasWorkingChanges ? ' has uncommitted changes and' : ''} is up-to-date with ${status.upstream}`;
|
||||
}
|
||||
else {
|
||||
label = `${status.branch} is not up-to-date with ${status.upstream}`;
|
||||
label = `${status.branch}${hasWorkingChanges ? ' has uncommitted changes and' : ''} is not up-to-date with ${status.upstream}`;
|
||||
|
||||
hasChildren = true;
|
||||
if (status.state.ahead && status.state.behind) {
|
||||
iconSuffix = '-yellow';
|
||||
@@ -54,10 +84,10 @@ export class StatusNode extends ExplorerNode {
|
||||
}
|
||||
}
|
||||
else {
|
||||
label = `${status.branch} is up-to-date`;
|
||||
label = `${status.branch} ${hasWorkingChanges ? 'has uncommitted changes' : this.includeWorkingTree ? 'has no changes' : 'has nothing to commit'}`;
|
||||
}
|
||||
|
||||
const item = new TreeItem(label, hasChildren ? TreeItemCollapsibleState.Expanded : TreeItemCollapsibleState.None);
|
||||
const item = new TreeItem(label, (hasChildren || hasWorkingChanges) ? TreeItemCollapsibleState.Expanded : TreeItemCollapsibleState.None);
|
||||
item.contextValue = this.resourceType;
|
||||
|
||||
item.iconPath = {
|
||||
@@ -67,4 +97,21 @@ export class StatusNode extends ExplorerNode {
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
private get includeWorkingTree(): boolean {
|
||||
return this.git.config.gitExplorer.includeWorkingTree;
|
||||
}
|
||||
|
||||
private async onFileSystemChanged(uri: Uri) {
|
||||
const status = await this.git.getStatusForRepo(this.uri.repoPath!);
|
||||
|
||||
// If we haven't changed from having some working changes to none or vice versa then just refresh the node
|
||||
// This is because of https://github.com/Microsoft/vscode/issues/34789
|
||||
if (this._status !== undefined && status !== undefined &&
|
||||
((this._status.files.length === status.files.length) || (this._status.files.length > 0 && status.files.length > 0))) {
|
||||
commands.executeCommand('gitlens.gitExplorer.refreshNode', this);
|
||||
}
|
||||
|
||||
commands.executeCommand('gitlens.gitExplorer.refresh');
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,20 @@
|
||||
'use strict';
|
||||
import { Iterables } from '../system';
|
||||
import { ExtensionContext, TreeItem, TreeItemCollapsibleState, Uri } from 'vscode';
|
||||
import { CommitNode } from './commitNode';
|
||||
import { ExplorerNode, ResourceType } from './explorerNode';
|
||||
import { GitService, GitStatus, GitUri } from '../gitService';
|
||||
import { CommitNode } from './commitNode';
|
||||
|
||||
export class StatusUpstreamNode extends ExplorerNode {
|
||||
|
||||
readonly resourceType: ResourceType = 'gitlens:status-upstream';
|
||||
|
||||
constructor(public readonly status: GitStatus, public readonly direction: 'ahead' | 'behind', private readonly template: string, protected readonly context: ExtensionContext, protected readonly git: GitService) {
|
||||
constructor(
|
||||
public readonly status: GitStatus,
|
||||
public readonly direction: 'ahead' | 'behind',
|
||||
protected readonly context: ExtensionContext,
|
||||
protected readonly git: GitService
|
||||
) {
|
||||
super(new GitUri(Uri.file(status.repoPath), { repoPath: status.repoPath, fileName: status.repoPath }));
|
||||
}
|
||||
|
||||
@@ -17,10 +22,11 @@ export class StatusUpstreamNode extends ExplorerNode {
|
||||
const range = this.direction === 'ahead'
|
||||
? `${this.status.upstream}..${this.status.branch}`
|
||||
: `${this.status.branch}..${this.status.upstream}`;
|
||||
|
||||
let log = await this.git.getLogForRepo(this.uri.repoPath!, range, 0);
|
||||
if (log === undefined) return [];
|
||||
|
||||
if (this.direction !== 'ahead') return [...Iterables.map(log.commits.values(), c => new CommitNode(c, this.template, this.context, this.git))];
|
||||
if (this.direction !== 'ahead') return [...Iterables.map(log.commits.values(), c => new CommitNode(c, this.context, this.git))];
|
||||
|
||||
// Since the last commit when we are looking 'ahead' can have no previous (because of the range given) -- look it up
|
||||
const commits = Array.from(log.commits.values());
|
||||
@@ -32,13 +38,13 @@ export class StatusUpstreamNode extends ExplorerNode {
|
||||
}
|
||||
}
|
||||
|
||||
return [...Iterables.map(commits, c => new CommitNode(c, this.template, this.context, this.git))];
|
||||
}
|
||||
return [...Iterables.map(commits, c => new CommitNode(c, this.context, this.git))];
|
||||
}
|
||||
|
||||
async getTreeItem(): Promise<TreeItem> {
|
||||
const label = this.direction === 'ahead'
|
||||
? `${this.status.state.ahead} commit${this.status.state.ahead > 1 ? 's' : ''} ahead (local changes)` // of ${this.status.upstream}`
|
||||
: `${this.status.state.behind} commit${this.status.state.behind > 1 ? 's' : ''} behind (remote changes)`; // ${this.status.upstream}`;
|
||||
? `${this.status.state.ahead} commit${this.status.state.ahead > 1 ? 's' : ''} (ahead of ${this.status.upstream})`
|
||||
: `${this.status.state.behind} commit${this.status.state.behind > 1 ? 's' : ''} (behind ${this.status.upstream})`;
|
||||
|
||||
const item = new TreeItem(label, TreeItemCollapsibleState.Collapsed);
|
||||
item.contextValue = this.resourceType;
|
||||
|
||||