@ -0,0 +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" x="0px" y="0px" width="256" height="256" viewBox="0 0 14 16" xml:space="preserve"> | |||||
<path fill="#FFFFFF" fill-rule="evenodd" d="M10.86 7c-.45-1.72-2-3-3.86-3-1.86 0-3.41 1.28-3.86 3H0v2h3.14c.45 1.72 2 3 3.86 3 1.86 0 3.41-1.28 3.86-3H14V7h-3.14zM7 10.2c-1.22 0-2.2-.98-2.2-2.2 0-1.22.98-2.2 2.2-2.2 1.22 0 2.2.98 2.2 2.2 0 1.22-.98 2.2-2.2 2.2z"></path> | |||||
</svg> |
@ -0,0 +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" x="0px" y="0px" width="256" height="256" viewBox="0 0 14 16" xml:space="preserve"> | |||||
<path fill="#00000" fill-rule="evenodd" d="M10.86 7c-.45-1.72-2-3-3.86-3-1.86 0-3.41 1.28-3.86 3H0v2h3.14c.45 1.72 2 3 3.86 3 1.86 0 3.41-1.28 3.86-3H14V7h-3.14zM7 10.2c-1.22 0-2.2-.98-2.2-2.2 0-1.22.98-2.2 2.2-2.2 1.22 0 2.2.98 2.2 2.2 0 1.22-.98 2.2-2.2 2.2z"></path> | |||||
</svg> |
@ -0,0 +1,30 @@ | |||||
'use strict'; | |||||
import { TextEditor, TextEditorEdit, Uri, window } from 'vscode'; | |||||
import { Commands, EditorCommand, getCommandUri } from './common'; | |||||
import { GitExplorer } from '../views/gitExplorer'; | |||||
import { GitService, GitUri } from '../gitService'; | |||||
import { Messages } from '../messages'; | |||||
import { Logger } from '../logger'; | |||||
export class ShowStashListCommand extends EditorCommand { | |||||
constructor(private git: GitService, private explorer: GitExplorer) { | |||||
super(Commands.ShowStashList); | |||||
} | |||||
async execute(editor: TextEditor, edit: TextEditorEdit, uri?: Uri) { | |||||
uri = getCommandUri(uri, editor); | |||||
try { | |||||
const repoPath = await this.git.getRepoPathFromUri(uri); | |||||
if (!repoPath) return Messages.showNoRepositoryWarningMessage(`Unable to show stashed changes`); | |||||
this.explorer.addStash(new GitUri(uri, { repoPath: repoPath, fileName: uri!.fsPath })); | |||||
return undefined; | |||||
} | |||||
catch (ex) { | |||||
Logger.error(ex, 'ShowStashListCommand'); | |||||
return window.showErrorMessage(`Unable to show stash list. See output channel for more details`); | |||||
} | |||||
} | |||||
} |
@ -0,0 +1,41 @@ | |||||
'use strict'; | |||||
import { Command, ExtensionContext, TreeItem, TreeItemCollapsibleState } from 'vscode'; | |||||
import { Commands, DiffWithPreviousCommandArgs } from '../commands'; | |||||
import { ExplorerNode, ResourceType } from './explorerNode'; | |||||
import { GitCommit, GitService, GitUri, IGitStatusFile } from '../gitService'; | |||||
export class CommitFileNode extends ExplorerNode { | |||||
readonly resourceType: ResourceType = 'commit-file'; | |||||
command: Command; | |||||
constructor(public status: IGitStatusFile, public commit: GitCommit, uri: GitUri, context: ExtensionContext, git: GitService) { | |||||
super(uri, context, git); | |||||
this.command = { | |||||
title: 'Compare File with Previous', | |||||
command: Commands.DiffWithPrevious, | |||||
arguments: [ | |||||
GitUri.fromFileStatus(this.status, this.commit.repoPath), | |||||
{ | |||||
commit: commit, | |||||
showOptions: { | |||||
preserveFocus: true, | |||||
preview: true | |||||
} | |||||
} as DiffWithPreviousCommandArgs | |||||
] | |||||
}; | |||||
} | |||||
getChildren(): Promise<ExplorerNode[]> { | |||||
return Promise.resolve([]); | |||||
} | |||||
getTreeItem(): TreeItem { | |||||
const item = new TreeItem(`${GitUri.getFormattedPath(this.status.fileName)}`, TreeItemCollapsibleState.None); | |||||
item.contextValue = this.resourceType; | |||||
item.command = this.command; | |||||
return item; | |||||
} | |||||
} |
@ -0,0 +1,54 @@ | |||||
'use strict'; | |||||
import { Iterables } from '../system'; | |||||
import { Command, ExtensionContext, TreeItem, TreeItemCollapsibleState } from 'vscode'; | |||||
import { CommitFileNode } from './commitFileNode'; | |||||
import { ExplorerNode, ResourceType } from './explorerNode'; | |||||
import { CommitFormatter, GitCommit, GitService, GitUri } from '../gitService'; | |||||
export class CommitNode extends ExplorerNode { | |||||
readonly resourceType: ResourceType = 'commit'; | |||||
command: Command; | |||||
constructor(public commit: GitCommit, uri: GitUri, context: ExtensionContext, git: GitService) { | |||||
super(uri, context, git); | |||||
// this.command = { | |||||
// title: 'Compare File with Previous', | |||||
// command: Commands.DiffWithPrevious, | |||||
// arguments: [ | |||||
// Uri.file(commit.uri.fsPath), | |||||
// { | |||||
// commit: commit, | |||||
// showOptions: { | |||||
// preserveFocus: true, | |||||
// preview: true | |||||
// } | |||||
// } as DiffWithPreviousCommandArgs | |||||
// ] | |||||
// }; | |||||
} | |||||
async getChildren(): Promise<ExplorerNode[]> { | |||||
const log = await this.git.getLogForRepo(this.commit.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.uri, this.context, this.git))]; | |||||
} | |||||
getTreeItem(): TreeItem { | |||||
const label = CommitFormatter.fromTemplate(this.git.config.explorer.commitFormat, this.commit, this.git.config.defaultDateFormat); | |||||
const item = new TreeItem(label, 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.command = this.command; | |||||
return item; | |||||
} | |||||
} |
@ -0,0 +1,15 @@ | |||||
'use strict'; | |||||
import { ExtensionContext, TreeItem } from 'vscode'; | |||||
import { GitService, GitUri } from '../gitService'; | |||||
export declare type ResourceType = 'status' | 'branches' | 'repository' | 'branch-history' | 'file-history' | 'stash-history' | 'commit' | 'stash-commit' | 'commit-file'; | |||||
export abstract class ExplorerNode { | |||||
abstract readonly resourceType: ResourceType; | |||||
constructor(public uri: GitUri, protected context: ExtensionContext, protected git: GitService) { } | |||||
abstract getChildren(): ExplorerNode[] | Promise<ExplorerNode[]>; | |||||
abstract getTreeItem(): TreeItem | Promise<TreeItem>; | |||||
} |
@ -0,0 +1,29 @@ | |||||
'use strict'; | |||||
import { Iterables } from '../system'; | |||||
import { ExtensionContext, TreeItem, TreeItemCollapsibleState } from 'vscode'; | |||||
import { CommitNode } from './commitNode'; | |||||
import { ExplorerNode, ResourceType } from './explorerNode'; | |||||
import { GitService, GitUri } from '../gitService'; | |||||
export class FileHistoryNode extends ExplorerNode { | |||||
static readonly rootType: ResourceType = 'file-history'; | |||||
readonly resourceType: ResourceType = 'file-history'; | |||||
constructor(uri: GitUri, context: ExtensionContext, git: GitService) { | |||||
super(uri, context, git); | |||||
} | |||||
async getChildren(): Promise<CommitNode[]> { | |||||
const log = await this.git.getLogForFile(this.uri.repoPath, this.uri.fsPath, this.uri.sha); | |||||
if (log === undefined) return []; | |||||
return [...Iterables.map(log.commits.values(), c => new CommitNode(c, this.uri, this.context, this.git))]; | |||||
} | |||||
getTreeItem(): TreeItem { | |||||
const item = new TreeItem(`History of ${this.uri.getFormattedPath()}`, TreeItemCollapsibleState.Expanded); | |||||
item.contextValue = this.resourceType; | |||||
return item; | |||||
} | |||||
} |
@ -0,0 +1,62 @@ | |||||
'use strict'; | |||||
import { Event, EventEmitter, ExtensionContext, TreeDataProvider, TreeItem, Uri, window } from 'vscode'; | |||||
import { UriComparer } from '../comparers'; | |||||
import { ExplorerNode, FileHistoryNode, RepositoryNode, ResourceType, StashNode } from './gitExplorerNodes'; | |||||
import { GitService, GitUri } from '../gitService'; | |||||
export * from './gitExplorerNodes'; | |||||
export class GitExplorer implements TreeDataProvider<ExplorerNode> { | |||||
private _onDidChangeTreeData = new EventEmitter<ExplorerNode>(); | |||||
public get onDidChangeTreeData(): Event<ExplorerNode> { | |||||
return this._onDidChangeTreeData.event; | |||||
} | |||||
private _roots: ExplorerNode[] = []; | |||||
constructor(private context: ExtensionContext, private git: GitService) { | |||||
const editor = window.activeTextEditor; | |||||
const uri = (editor !== undefined && editor.document !== undefined) | |||||
? new GitUri(editor.document.uri, { repoPath: git.repoPath, fileName: editor.document.uri.fsPath }) | |||||
: new GitUri(Uri.file(git.repoPath), { repoPath: git.repoPath, fileName: git.repoPath }); | |||||
this._roots.push(new RepositoryNode(uri, context, git)); | |||||
} | |||||
async getTreeItem(node: ExplorerNode): Promise<TreeItem> { | |||||
return node.getTreeItem(); | |||||
} | |||||
async getChildren(node?: ExplorerNode): Promise<ExplorerNode[]> { | |||||
if (this._roots.length === 0) return []; | |||||
if (node === undefined) return this._roots; | |||||
return node.getChildren(); | |||||
} | |||||
addHistory(uri: GitUri) { | |||||
this._add(uri, FileHistoryNode); | |||||
} | |||||
addStash(uri: GitUri) { | |||||
this._add(uri, StashNode); | |||||
} | |||||
private _add<T extends ExplorerNode>(uri: GitUri, type: { new (uri: GitUri, context: ExtensionContext, git: GitService): T, rootType: ResourceType }) { | |||||
if (!this._roots.some(_ => _.resourceType === type.rootType && UriComparer.equals(uri, _.uri))) { | |||||
this._roots.push(new type(uri, this.context, this.git)); | |||||
} | |||||
this._onDidChangeTreeData.fire(); | |||||
} | |||||
clear() { | |||||
this._roots = []; | |||||
this._onDidChangeTreeData.fire(); | |||||
} | |||||
refresh() { | |||||
this._onDidChangeTreeData.fire(); | |||||
} | |||||
} |
@ -0,0 +1,9 @@ | |||||
'use strict'; | |||||
export * from './explorerNode'; | |||||
export * from './commitFileNode'; | |||||
export * from './commitNode'; | |||||
export * from './fileHistoryNode'; | |||||
export * from './repositoryNode'; | |||||
export * from './stashCommitNode'; | |||||
export * from './stashNode'; |
@ -0,0 +1,105 @@ | |||||
'use strict'; | |||||
import { Iterables, Strings } from '../system'; | |||||
import { ExtensionContext, TreeItem, TreeItemCollapsibleState } from 'vscode'; | |||||
import { CommitNode } from './commitNode'; | |||||
import { GlyphChars } from '../constants'; | |||||
import { ExplorerNode, ResourceType } from './explorerNode'; | |||||
import { GitBranch, GitService, GitUri } from '../gitService'; | |||||
import { StashNode } from './stashNode'; | |||||
export class RepositoryNode extends ExplorerNode { | |||||
static readonly rootType: ResourceType = 'repository'; | |||||
readonly resourceType: ResourceType = 'repository'; | |||||
constructor(uri: GitUri, context: ExtensionContext, git: GitService) { | |||||
super(uri, context, git); | |||||
} | |||||
async getChildren(): Promise<ExplorerNode[]> { | |||||
return [ | |||||
new StatusNode(this.uri, this.context, this.git), | |||||
new StashNode(this.uri, this.context, this.git), | |||||
new BranchesNode(this.uri, this.context, this.git) | |||||
]; | |||||
} | |||||
getTreeItem(): TreeItem { | |||||
const item = new TreeItem(`Repository ${GlyphChars.Dash} ${this.uri.repoPath}`, TreeItemCollapsibleState.Expanded); | |||||
item.contextValue = this.resourceType; | |||||
return item; | |||||
} | |||||
} | |||||
export class BranchesNode extends ExplorerNode { | |||||
readonly resourceType: ResourceType = 'branches'; | |||||
constructor(uri: GitUri, context: ExtensionContext, git: GitService) { | |||||
super(uri, context, git); | |||||
} | |||||
async getChildren(): Promise<BranchHistoryNode[]> { | |||||
const branches = await this.git.getBranches(this.uri.repoPath!); | |||||
if (branches === undefined) return []; | |||||
return [...Iterables.filterMap(branches.sort(_ => _.current ? 0 : 1), b => b.remote ? undefined : new BranchHistoryNode(b, this.uri, this.context, this.git))]; | |||||
} | |||||
getTreeItem(): TreeItem { | |||||
const item = new TreeItem(`Branches`, TreeItemCollapsibleState.Collapsed); | |||||
item.contextValue = this.resourceType; | |||||
return item; | |||||
} | |||||
} | |||||
export class BranchHistoryNode extends ExplorerNode { | |||||
readonly resourceType: ResourceType = 'branch-history'; | |||||
constructor(public branch: GitBranch, uri: GitUri, context: ExtensionContext, git: GitService) { | |||||
super(uri, context, git); | |||||
} | |||||
async getChildren(): Promise<CommitNode[]> { | |||||
const log = await this.git.getLogForRepo(this.uri.repoPath!, this.branch.name); | |||||
if (log === undefined) return []; | |||||
return [...Iterables.map(log.commits.values(), c => new CommitNode(c, this.uri, this.context, this.git))]; | |||||
} | |||||
getTreeItem(): TreeItem { | |||||
const item = new TreeItem(`${this.branch.name}${this.branch.current ? ` ${GlyphChars.Dash} current` : ''}`, TreeItemCollapsibleState.Collapsed); | |||||
item.contextValue = this.resourceType; | |||||
return item; | |||||
} | |||||
} | |||||
export class StatusNode extends ExplorerNode { | |||||
readonly resourceType: ResourceType = 'status'; | |||||
constructor(uri: GitUri, context: ExtensionContext, git: GitService) { | |||||
super(uri, context, git); | |||||
} | |||||
async getChildren(): Promise<ExplorerNode[]> { | |||||
return []; | |||||
// const status = await this.git.getStatusForRepo(this.uri.repoPath!); | |||||
// if (status === undefined) return []; | |||||
// return [...Iterables.map(status.files, b => new CommitFile(b, this.uri, this.context, this.git))]; | |||||
} | |||||
async getTreeItem(): Promise<TreeItem> { | |||||
const status = await this.git.getStatusForRepo(this.uri.repoPath!); | |||||
let suffix = ''; | |||||
if (status !== undefined) { | |||||
suffix = ` ${GlyphChars.Dash} ${GlyphChars.ArrowUp} ${status.state.ahead} ${GlyphChars.ArrowDown} ${status.state.behind} ${Strings.pad(GlyphChars.Dot, 1, 1)} ${status.branch} ${GlyphChars.ArrowLeftRight} ${status.upstream}`; | |||||
} | |||||
const item = new TreeItem(`Status${suffix}`, TreeItemCollapsibleState.Collapsed); | |||||
item.contextValue = this.resourceType; | |||||
return item; | |||||
} | |||||
} |
@ -0,0 +1,40 @@ | |||||
'use strict'; | |||||
import { Command, ExtensionContext, TreeItem, TreeItemCollapsibleState } from 'vscode'; | |||||
import { CommitFileNode } from './commitFileNode'; | |||||
import { ExplorerNode, ResourceType } from './explorerNode'; | |||||
import { CommitFormatter, GitService, GitStashCommit, GitUri } from '../gitService'; | |||||
export class StashCommitNode extends ExplorerNode { | |||||
readonly resourceType: ResourceType = 'stash-commit'; | |||||
command: Command; | |||||
constructor(public commit: GitStashCommit, uri: GitUri, context: ExtensionContext, git: GitService) { | |||||
super(uri, context, git); | |||||
// this.command = { | |||||
// title: 'Show Stash Details', | |||||
// command: Commands.ShowQuickCommitDetails, | |||||
// arguments: [ | |||||
// new GitUri(commit.uri, commit), | |||||
// { | |||||
// commit: commit, | |||||
// sha: commit.sha | |||||
// } as ShowQuickCommitDetailsCommandArgs | |||||
// ] | |||||
// }; | |||||
} | |||||
getChildren(): Promise<CommitFileNode[]> { | |||||
return Promise.resolve((this.commit as GitStashCommit).fileStatuses.map(_ => new CommitFileNode(_, this.commit, this.uri, this.context, this.git))); | |||||
} | |||||
getTreeItem(): TreeItem { | |||||
const label = CommitFormatter.fromTemplate(this.git.config.explorer.stashFormat, this.commit, this.git.config.defaultDateFormat); | |||||
const item = new TreeItem(label, TreeItemCollapsibleState.Collapsed); | |||||
item.contextValue = this.resourceType; | |||||
item.command = this.command; | |||||
return item; | |||||
} | |||||
} |
@ -0,0 +1,29 @@ | |||||
'use strict'; | |||||
import { Iterables } from '../system'; | |||||
import { ExtensionContext, TreeItem, TreeItemCollapsibleState } from 'vscode'; | |||||
import { ExplorerNode, ResourceType } from './explorerNode'; | |||||
import { GitService, GitUri } from '../gitService'; | |||||
import { StashCommitNode } from './stashCommitNode'; | |||||
export class StashNode extends ExplorerNode { | |||||
static readonly rootType: ResourceType = 'stash-history'; | |||||
readonly resourceType: ResourceType = 'stash-history'; | |||||
constructor(uri: GitUri, context: ExtensionContext, git: GitService) { | |||||
super(uri, context, git); | |||||
} | |||||
async getChildren(): Promise<StashCommitNode[]> { | |||||
const stash = await this.git.getStashList(this.uri.repoPath!); | |||||
if (stash === undefined) return []; | |||||
return [...Iterables.map(stash.commits.values(), c => new StashCommitNode(c, this.uri, this.context, this.git))]; | |||||
} | |||||
getTreeItem(): TreeItem { | |||||
const item = new TreeItem(`Stashed Changes`, TreeItemCollapsibleState.Collapsed); | |||||
item.contextValue = this.resourceType; | |||||
return item; | |||||
} | |||||
} |