Переглянути джерело

Adds support for staged changes

main
Eric Amodio 7 роки тому
джерело
коміт
227ebeb18d
18 змінених файлів з 299 додано та 77 видалено
  1. +6
    -2
      CHANGELOG.md
  2. +21
    -6
      src/annotations/annotations.ts
  3. +1
    -1
      src/annotations/recentChangesAnnotationProvider.ts
  4. +10
    -0
      src/commands/diffLineWithPrevious.ts
  5. +6
    -2
      src/commands/diffLineWithWorking.ts
  6. +6
    -12
      src/commands/diffWith.ts
  7. +36
    -9
      src/commands/diffWithNext.ts
  8. +49
    -3
      src/commands/diffWithPrevious.ts
  9. +26
    -3
      src/commands/diffWithWorking.ts
  10. +1
    -1
      src/extension.ts
  11. +2
    -1
      src/git/formatters/commit.ts
  12. +49
    -13
      src/git/git.ts
  13. +2
    -2
      src/git/gitUri.ts
  14. +14
    -1
      src/git/models/commit.ts
  15. +5
    -5
      src/git/models/logCommit.ts
  16. +10
    -10
      src/gitService.ts
  17. +52
    -4
      src/views/fileHistoryNode.ts
  18. +3
    -2
      src/views/gitExplorer.ts

+ 6
- 2
CHANGELOG.md Переглянути файл

@ -4,6 +4,10 @@ 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/).
## [Unreleased]
### Added
- Adds support for staged changes in the `GitLens` view, blame annotations, comparison commands, etc
## [6.2.0] - 2017-11-27
### Added
- Adds theming support - vscode themes can now specify GitLens colors as well as directly by using [`workbench.colorCustomization`](https://code.visualstudio.com/docs/getstarted/themes#_customize-a-color-theme))
@ -262,8 +266,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p
- 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 view
- Fixes an issue where stashes with untracked files would not show its untracked files in the GitLens view
- Fixes an issue where stashes with only untracked files would not show in the `Stashes` node of the `GitLens` view
- Fixes an issue where stashes with untracked files would not show its untracked files in the `GitLens` view
## [5.0.0] - 2017-09-12
### Added

+ 21
- 6
src/annotations/annotations.ts Переглянути файл

@ -94,13 +94,25 @@ export class Annotations {
return markdown;
}
static getHoverDiffMessage(commit: GitCommit, chunkLine: GitDiffChunkLine | undefined): MarkdownString | undefined {
static getHoverDiffMessage(commit: GitCommit, uri: GitUri, chunkLine: GitDiffChunkLine | undefined): MarkdownString | undefined {
if (chunkLine === undefined || commit.previousSha === undefined) return undefined;
const codeDiff = this.getCodeDiff(chunkLine);
const markdown = new MarkdownString(commit.isUncommitted
? `[\`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}`);
let message: string;
if (commit.isUncommitted) {
if (uri.sha !== undefined && GitService.isStagedUncommitted(uri.sha)) {
message = `[\`Changes\`](${DiffWithCommand.getMarkdownCommandArgs(commit)} "Open Changes")   ${GlyphChars.Dash}   [\`${commit.previousShortSha}\`](${ShowQuickCommitDetailsCommand.getMarkdownCommandArgs(commit.previousSha!)} "Show Commit Details") ${GlyphChars.ArrowLeftRight} _${uri.shortSha}_\n${codeDiff}`;
}
else {
message = `[\`Changes\`](${DiffWithCommand.getMarkdownCommandArgs(commit)} "Open Changes")   ${GlyphChars.Dash}   _uncommitted_\n${codeDiff}`;
}
}
else {
message = `[\`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}`;
}
const markdown = new MarkdownString(message);
markdown.isTrusted = true;
return markdown;
}
@ -114,8 +126,11 @@ export class Annotations {
}
static async changesHover(commit: GitCommit, line: number, uri: GitUri, git: GitService): Promise<DecorationOptions> {
const chunkLine = await git.getDiffForLine(uri, line, commit.isUncommitted ? undefined : commit.previousSha);
const message = this.getHoverDiffMessage(commit, chunkLine);
const sha = !commit.isUncommitted || (uri.sha !== undefined && GitService.isStagedUncommitted(uri.sha))
? commit.previousSha
: undefined;
const chunkLine = await git.getDiffForLine(uri, line, sha);
const message = this.getHoverDiffMessage(commit, uri, chunkLine);
return {
hoverMessage: message

+ 1
- 1
src/annotations/recentChangesAnnotationProvider.ts Переглянути файл

@ -55,7 +55,7 @@ export class RecentChangesAnnotationProvider extends AnnotationProviderBase {
let message: MarkdownString | undefined = undefined;
if (cfg.hover.changes) {
message = Annotations.getHoverDiffMessage(commit, line);
message = Annotations.getHoverDiffMessage(commit, this.uri, line);
}
this._decorations.push({

+ 10
- 0
src/commands/diffLineWithPrevious.ts Переглянути файл

@ -43,6 +43,16 @@ export class DiffLineWithPreviousCommand extends ActiveEditorCommand {
if (blame === undefined) return Messages.showFileNotUnderSourceControlWarningMessage('Unable to open compare');
args.commit = blame.commit;
// If the line is uncommitted, change the previous commit
if (args.commit.isUncommitted) {
const status = await this.git.getStatusForFile(gitUri.repoPath!, gitUri.fsPath);
if (status !== undefined && status.indexStatus !== undefined) {
args.commit = args.commit.with({
sha: GitService.stagedUncommittedSha
});
}
}
}
catch (ex) {
Logger.error(ex, 'DiffLineWithPreviousCommand', `getBlameForLine(${blameline})`);

+ 6
- 2
src/commands/diffLineWithWorking.ts Переглянути файл

@ -43,10 +43,14 @@ export class DiffLineWithWorkingCommand extends ActiveEditorCommand {
if (blame === undefined) return Messages.showFileNotUnderSourceControlWarningMessage('Unable to open compare');
args.commit = blame.commit;
// If the line is uncommitted, find the previous commit
// If the line is uncommitted, change the previous commit
if (args.commit.isUncommitted) {
const status = await this.git.getStatusForFile(gitUri.repoPath!, gitUri.fsPath);
args.commit = args.commit.with({
sha: args.commit.previousSha!,
sha: status !== undefined && status.indexStatus !== undefined
? GitService.stagedUncommittedSha
: args.commit.previousSha!,
fileName: args.commit.previousFileName!,
originalFileName: null,
previousSha: null,

+ 6
- 12
src/commands/diffWith.ts Переглянути файл

@ -91,12 +91,8 @@ export class DiffWithCommand extends ActiveEditorCommand {
try {
const [lhs, rhs] = await Promise.all([
args.lhs.sha !== '' && !GitService.isUncommitted(args.lhs.sha)
? this.git.getVersionedFile(args.repoPath, args.lhs.uri.fsPath, args.lhs.sha)
: args.lhs.uri.fsPath,
args.rhs.sha !== '' && !GitService.isUncommitted(args.rhs.sha)
? this.git.getVersionedFile(args.repoPath, args.rhs.uri.fsPath, args.rhs.sha)
: args.rhs.uri.fsPath
this.git.getVersionedFile(args.repoPath, args.lhs.uri.fsPath, args.lhs.sha),
this.git.getVersionedFile(args.repoPath, args.rhs.uri.fsPath, args.rhs.sha)
]);
if (args.line !== undefined && args.line !== 0) {
@ -115,14 +111,12 @@ export class DiffWithCommand extends ActiveEditorCommand {
}
if (args.lhs.title === undefined && lhs !== undefined && args.lhs.sha !== GitService.deletedSha) {
args.lhs.title = (args.lhs.sha === '' || GitService.isUncommitted(args.lhs.sha))
? `${path.basename(args.lhs.uri.fsPath)}`
: `${path.basename(args.lhs.uri.fsPath)} (${GitService.shortenSha(args.lhs.sha)})`;
const suffix = GitService.shortenSha(args.lhs.sha) || '';
args.lhs.title = `${path.basename(args.lhs.uri.fsPath)}${suffix !== '' ? ` (${suffix})` : ''}`;
}
if (args.rhs.title === undefined && args.rhs.sha !== GitService.deletedSha) {
args.rhs.title = (args.rhs.sha === '' || GitService.isUncommitted(args.rhs.sha))
? `${path.basename(args.rhs.uri.fsPath)}`
: `${path.basename(args.rhs.uri.fsPath)} (${rhsPrefix}${GitService.shortenSha(args.rhs.sha)})`;
const suffix = GitService.shortenSha(args.rhs.sha) || '';
args.rhs.title = `${path.basename(args.rhs.uri.fsPath)}${suffix !== '' ? ` (${rhsPrefix}${suffix})` : ''}`;
}
const title = (args.lhs.title !== undefined && args.rhs.title !== undefined)

+ 36
- 9
src/commands/diffWithNext.ts Переглянути файл

@ -3,7 +3,7 @@ import { Iterables } from '../system';
import { commands, Range, TextDocumentShowOptions, TextEditor, Uri, window } from 'vscode';
import { ActiveEditorCommand, Commands, getCommandUri } from './common';
import { DiffWithCommandArgs } from './diffWith';
import { GitLogCommit, GitService, GitUri } from '../gitService';
import { GitLogCommit, GitService, GitStatusFile, GitUri } from '../gitService';
import { Logger } from '../logger';
import { Messages } from '../messages';
@ -32,21 +32,26 @@ export class DiffWithNextCommand extends ActiveEditorCommand {
args.line = editor === undefined ? 0 : editor.selection.active.line;
}
if (args.commit === undefined || !(args.commit instanceof GitLogCommit) || args.range !== undefined) {
const gitUri = await GitUri.fromUri(uri, this.git);
const gitUri = await GitUri.fromUri(uri, this.git);
let status: GitStatusFile | undefined;
if (args.commit === undefined || !(args.commit instanceof GitLogCommit) || args.range !== undefined) {
try {
// If the sha is missing or the file is uncommitted, treat it as a DiffWithWorking
if (gitUri.sha === undefined && await this.git.isFileUncommitted(gitUri)) {
return commands.executeCommand(Commands.DiffWithWorking, uri);
}
const sha = args.commit === undefined ? gitUri.sha : args.commit.sha;
// If we are a fake "staged" sha, treat it as a DiffWithWorking
if (GitService.isStagedUncommitted(sha!)) return commands.executeCommand(Commands.DiffWithWorking, uri);
const log = await this.git.getLogForFile(gitUri.repoPath, gitUri.fsPath, undefined, { maxCount: sha !== undefined ? undefined : 2, range: args.range! });
if (log === undefined) return Messages.showFileNotUnderSourceControlWarningMessage('Unable to open compare');
args.commit = (sha && log.commits.get(sha)) || Iterables.first(log.commits.values());
// If the sha is missing or the file is uncommitted, treat it as a DiffWithWorking
if (gitUri.sha === undefined) {
status = await this.git.getStatusForFile(gitUri.repoPath!, gitUri.fsPath);
if (status !== undefined) return commands.executeCommand(Commands.DiffWithWorking, uri);
}
}
catch (ex) {
Logger.error(ex, 'DiffWithNextCommand', `getLogForFile(${gitUri.repoPath}, ${gitUri.fsPath})`);
@ -54,7 +59,29 @@ export class DiffWithNextCommand extends ActiveEditorCommand {
}
}
if (args.commit.nextSha === undefined) return commands.executeCommand(Commands.DiffWithWorking, uri);
if (args.commit.nextSha === undefined) {
// Check if the file is staged
status = status || await this.git.getStatusForFile(gitUri.repoPath!, gitUri.fsPath);
if (status !== undefined && status.indexStatus === 'M') {
const diffArgs: DiffWithCommandArgs = {
repoPath: args.commit.repoPath,
lhs: {
sha: args.commit.sha,
uri: args.commit.uri
},
rhs: {
sha: GitService.stagedUncommittedSha,
uri: args.commit.uri
},
line: args.line,
showOptions: args.showOptions
};
return commands.executeCommand(Commands.DiffWith, diffArgs);
}
return commands.executeCommand(Commands.DiffWithWorking, uri);
}
const diffArgs: DiffWithCommandArgs = {
repoPath: args.commit.repoPath,

+ 49
- 3
src/commands/diffWithPrevious.ts Переглянути файл

@ -37,17 +37,63 @@ export class DiffWithPreviousCommand extends ActiveEditorCommand {
const gitUri = await GitUri.fromUri(uri, this.git);
try {
const sha = args.commit === undefined ? gitUri.sha : args.commit.sha;
let sha = args.commit === undefined ? gitUri.sha : args.commit.sha;
if (sha === GitService.deletedSha) return Messages.showCommitHasNoPreviousCommitWarningMessage();
// If we are a fake "staged" sha, remove it
let isStagedUncommitted = false;
if (GitService.isStagedUncommitted(sha!)) {
gitUri.sha = sha = undefined;
isStagedUncommitted = true;
}
const log = await this.git.getLogForFile(gitUri.repoPath, gitUri.fsPath, sha, { maxCount: 2, range: args.range!, skipMerges: true });
if (log === undefined) return Messages.showFileNotUnderSourceControlWarningMessage('Unable to open compare');
args.commit = (sha && log.commits.get(sha)) || Iterables.first(log.commits.values());
// If the sha is missing and the file is uncommitted, then treat it as a DiffWithWorking
if (gitUri.sha === undefined && await this.git.isFileUncommitted(gitUri)) {
return commands.executeCommand(Commands.DiffWithWorking, uri, { commit: args.commit, showOptions: args.showOptions } as DiffWithWorkingCommandArgs);
if (gitUri.sha === undefined) {
const status = await this.git.getStatusForFile(gitUri.repoPath!, gitUri.fsPath);
if (status !== undefined) {
if (isStagedUncommitted) {
const diffArgs: DiffWithCommandArgs = {
repoPath: args.commit.repoPath,
lhs: {
sha: args.commit.sha,
uri: args.commit.uri
},
rhs: {
sha: GitService.stagedUncommittedSha,
uri: args.commit.uri
},
line: args.line,
showOptions: args.showOptions
};
return commands.executeCommand(Commands.DiffWith, diffArgs);
}
// Check if the file is staged
if (status.indexStatus !== undefined) {
const diffArgs: DiffWithCommandArgs = {
repoPath: args.commit.repoPath,
lhs: {
sha: GitService.stagedUncommittedSha,
uri: args.commit.uri
},
rhs: {
sha: '',
uri: args.commit.uri
},
line: args.line,
showOptions: args.showOptions
};
return commands.executeCommand(Commands.DiffWith, diffArgs);
}
return commands.executeCommand(Commands.DiffWithWorking, uri, { commit: args.commit, showOptions: args.showOptions } as DiffWithWorkingCommandArgs);
}
}
}
catch (ex) {

+ 26
- 3
src/commands/diffWithWorking.ts Переглянути файл

@ -25,16 +25,41 @@ export class DiffWithWorkingCommand extends ActiveEditorCommand {
uri = getCommandUri(uri, editor);
if (uri === undefined) return undefined;
const gitUri = await GitUri.fromUri(uri, this.git);
args = { ...args };
if (args.line === undefined) {
args.line = editor === undefined ? 0 : editor.selection.active.line;
}
if (args.commit === undefined || GitService.isUncommitted(args.commit.sha)) {
const gitUri = await GitUri.fromUri(uri, this.git);
// If the sha is missing, just let the user know the file matches
if (gitUri.sha === undefined) return window.showInformationMessage(`File matches the working tree`);
// If we are a fake "staged" sha, check the status
if (GitService.isStagedUncommitted(gitUri.sha!)) {
gitUri.sha = undefined;
const status = await this.git.getStatusForFile(gitUri.repoPath!, gitUri.fsPath);
if (status !== undefined && status.indexStatus !== undefined) {
const diffArgs: DiffWithCommandArgs = {
repoPath: gitUri.repoPath,
lhs: {
sha: GitService.stagedUncommittedSha,
uri: gitUri.fileUri()
},
rhs: {
sha: '',
uri: gitUri.fileUri()
},
line: args.line,
showOptions: args.showOptions
};
return commands.executeCommand(Commands.DiffWith, diffArgs);
}
}
try {
args.commit = await this.git.getLogCommit(gitUri.repoPath, gitUri.fsPath, gitUri.sha, { firstIfMissing: true });
if (args.commit === undefined) return Messages.showFileNotUnderSourceControlWarningMessage('Unable to open compare');
@ -45,8 +70,6 @@ export class DiffWithWorkingCommand extends ActiveEditorCommand {
}
}
const gitUri = await GitUri.fromUri(uri, this.git);
const workingFileName = await this.git.findWorkingFileName(gitUri.repoPath, gitUri.fsPath);
if (workingFileName === undefined) return undefined;

+ 1
- 1
src/extension.ts Переглянути файл

@ -74,7 +74,7 @@ export async function activate(context: ExtensionContext) {
context.subscriptions.push(workspace.registerTextDocumentContentProvider(GitContentProvider.scheme, new GitContentProvider(context, git)));
context.subscriptions.push(languages.registerCodeLensProvider(GitRevisionCodeLensProvider.selector, new GitRevisionCodeLensProvider(context, git)));
context.subscriptions.push(window.registerTreeDataProvider('gitlens.gitExplorer', new GitExplorer(context, git)));
context.subscriptions.push(window.registerTreeDataProvider('gitlens.gitExplorer', new GitExplorer(context, git, gitContextTracker)));
context.subscriptions.push(new Keyboard());

+ 2
- 1
src/git/formatters/commit.ts Переглянути файл

@ -39,7 +39,8 @@ export class CommitFormatter extends Formatter
}
get id() {
return this._item.isUncommitted ? 'index' : this._item.shortSha;
if (this._item.isUncommitted && !this._item.isStagedUncommitted) return '00000000';
return this._item.shortSha;
}
get message() {

+ 49
- 13
src/git/git.ts Переглянути файл

@ -3,11 +3,12 @@ import { Strings } from '../system';
import { SpawnOptions } from 'child_process';
import { findGitPath, IGit } from './gitLocator';
import { Logger } from '../logger';
import { Observable } from 'rxjs';
import { spawnPromise } from 'spawn-rx';
import * as fs from 'fs';
import * as iconv from 'iconv-lite';
import * as path from 'path';
import * as tmp from 'tmp';
import * as iconv from 'iconv-lite';
export { IGit };
export * from './models/models';
@ -40,6 +41,7 @@ interface GitCommandOptions {
cwd: string;
env?: any;
encoding?: string;
stdin?: Observable<string> | undefined;
willHandleErrors?: boolean;
}
@ -75,7 +77,8 @@ async function gitCommandCore(options: GitCommandOptions, ...args: any[]): Promi
// Adds GCM environment variables to avoid any possible credential issues -- from https://github.com/Microsoft/vscode/issues/26573#issuecomment-338686581
// Shouldn't *really* be needed but better safe than sorry
env: { ...(options.env || process.env), GCM_INTERACTIVE: 'NEVER', GCM_PRESERVE_CREDS: 'TRUE' },
encoding: (opts.encoding === 'utf8') ? 'utf8' : 'binary'
encoding: (opts.encoding === 'utf8') ? 'utf8' : 'binary',
stdin: opts.stdin
} as SpawnOptions);
pendingCommands.set(command, promise);
@ -116,7 +119,10 @@ function gitCommandDefaultErrorHandler(ex: Error, options: GitCommandOptions, ..
export class Git {
static shaRegex = /^[0-9a-f]{40}(\^[0-9]*?)??( -)?$/;
static uncommittedRegex = /^[0]{40}(\^[0-9]*?)??$/;
static stagedUncommittedRegex = /^[0]{40}(\^[0-9]*?)??:$/;
static stagedUncommittedSha = '0000000000000000000000000000000000000000:';
static uncommittedRegex = /^[0]{40}(\^[0-9]*?)??:??$/;
static uncommittedSha = '0000000000000000000000000000000000000000';
static gitInfo(): IGit {
return git;
@ -138,6 +144,10 @@ export class Git {
const data = await Git.show(repoPath, fileName, branchOrSha, { encoding: 'binary' });
if (data === undefined) return undefined;
if (Git.isStagedUncommitted(branchOrSha)) {
branchOrSha = '';
}
const suffix = Strings.truncate(Strings.sanitizeForFileSystem(Git.isSha(branchOrSha) ? Git.shortenSha(branchOrSha) : branchOrSha), 50, '');
const ext = path.extname(fileName);
return new Promise<string>((resolve, reject) => {
@ -165,6 +175,10 @@ export class Git {
return Git.shaRegex.test(sha);
}
static isStagedUncommitted(sha: string): boolean {
return Git.stagedUncommittedRegex.test(sha);
}
static isUncommitted(sha: string) {
return Git.uncommittedRegex.test(sha);
}
@ -174,6 +188,9 @@ export class Git {
}
static shortenSha(sha: string) {
if (Git.isStagedUncommitted(sha)) return 'index';
if (Git.isUncommitted(sha)) return '';
const index = sha.indexOf('^');
// This is lame, but assume there is only 1 character after the ^
if (index > 6) return `${sha.substring(0, 6)}${sha.substring(index)}`;
@ -205,7 +222,7 @@ export class Git {
// Git commands
static blame(repoPath: string | undefined, fileName: string, sha?: string, options: { ignoreWhitespace?: boolean, startLine?: number, endLine?: number } = {}) {
static async blame(repoPath: string | undefined, fileName: string, sha?: string, options: { ignoreWhitespace?: boolean, startLine?: number, endLine?: number } = {}) {
const [file, root] = Git.splitPath(fileName, repoPath);
const params = [...defaultBlameParams];
@ -216,11 +233,23 @@ export class Git {
if (options.startLine != null && options.endLine != null) {
params.push(`-L ${options.startLine},${options.endLine}`);
}
let stdin: Observable<string> | undefined;
if (sha) {
params.push(sha);
if (Git.isStagedUncommitted(sha)) {
// Pipe the blame contents to stdin
params.push(`--contents`);
params.push('-');
// Get the file contents for the staged version using `:`
stdin = Observable.from<string>(Git.show(repoPath, fileName, ':') as any);
}
else {
params.push(sha);
}
}
return gitCommand({ cwd: root }, ...params, `--`, file);
return gitCommand({ cwd: root, stdin: stdin }, ...params, `--`, file);
}
static branch(repoPath: string, options: { all: boolean } = { all: false }) {
@ -251,10 +280,10 @@ export class Git {
static diff(repoPath: string, fileName: string, sha1?: string, sha2?: string, options: { encoding?: string } = {}) {
const params = [`diff`, `--diff-filter=M`, `-M`, `--no-ext-diff`];
if (sha1) {
params.push(sha1);
params.push(Git.isStagedUncommitted(sha1) ? '--staged' : sha1);
}
if (sha2) {
params.push(sha2);
params.push(Git.isStagedUncommitted(sha2) ? '--staged' : sha2);
}
return gitCommand({ cwd: repoPath, encoding: options.encoding || 'utf8' }, ...params, '--', fileName);
@ -308,7 +337,7 @@ export class Git {
if (maxCount && !reverse) {
params.push(`-n${maxCount}`);
}
if (sha) {
if (sha && !Git.isStagedUncommitted(sha)) {
if (reverse) {
params.push(`--reverse`);
params.push(`--ancestry-path`);
@ -337,7 +366,7 @@ export class Git {
params.push(`-m`);
}
if (sha) {
if (sha && !Git.isStagedUncommitted(sha)) {
if (options.reverse) {
params.push(`--reverse`);
params.push(`--ancestry-path`);
@ -369,7 +398,7 @@ export class Git {
static log_shortstat(repoPath: string, sha?: string) {
const params = [`log`, `--shortstat`, `--oneline`];
if (sha) {
if (sha && !Git.isStagedUncommitted(sha)) {
params.push(sha);
}
return gitCommand({ cwd: repoPath }, ...params);
@ -377,7 +406,7 @@ export class Git {
static async ls_files(repoPath: string, fileName: string, sha?: string): Promise<string> {
const params = [`ls-files`];
if (sha) {
if (sha && !Git.isStagedUncommitted(sha)) {
params.push(`--with-tree=${sha}`);
}
@ -429,10 +458,17 @@ export class Git {
static async show(repoPath: string | undefined, fileName: string, branchOrSha: string, options: { encoding?: string } = {}) {
const [file, root] = Git.splitPath(fileName, repoPath);
if (Git.isStagedUncommitted(branchOrSha)) {
branchOrSha = ':';
}
if (Git.isUncommitted(branchOrSha)) throw new Error(`sha=${branchOrSha} is uncommitted`);
const opts = { cwd: root, encoding: options.encoding || 'utf8', willHandleErrors: true } as GitCommandOptions;
const args = `${branchOrSha}:./${file}`;
const args = branchOrSha.endsWith(':')
? `${branchOrSha}./${file}`
: `${branchOrSha}:./${file}`;
try {
const data = await gitCommand(opts, 'show', args);
return data;

+ 2
- 2
src/git/gitUri.ts Переглянути файл

@ -29,7 +29,7 @@ export class GitUri extends ((Uri as any) as UriEx) {
const data = GitService.fromGitContentUri(uri);
super(uri.scheme, uri.authority, path.resolve(data.repoPath, data.originalFileName || data.fileName), uri.query, uri.fragment);
if (!GitService.isUncommitted(data.sha)) {
if (GitService.isStagedUncommitted(data.sha) || !GitService.isUncommitted(data.sha)) {
this.sha = data.sha;
this.repoPath = data.repoPath;
}
@ -58,7 +58,7 @@ export class GitUri extends ((Uri as any) as UriEx) {
this.repoPath = commit.repoPath;
}
if (commit.sha !== undefined && !GitService.isUncommitted(commit.sha)) {
if (commit.sha !== undefined && (GitService.isStagedUncommitted(commit.sha) || !GitService.isUncommitted(commit.sha))) {
this.sha = commit.sha;
}
}

+ 14
- 1
src/git/models/commit.ts Переглянути файл

@ -33,7 +33,10 @@ export class GitCommit {
previousSha?: string;
previousFileName?: string;
workingFileName?: string;
private _isStagedUncommitted: boolean | undefined;
private _isUncommitted: boolean | undefined;
private _shortSha: string | undefined;
constructor(
type: GitCommitType,
@ -56,7 +59,17 @@ export class GitCommit {
}
get shortSha() {
return Git.shortenSha(this.sha);
if (this._shortSha === undefined) {
this._shortSha = Git.shortenSha(this.sha);
}
return this._shortSha;
}
get isStagedUncommitted(): boolean {
if (this._isStagedUncommitted === undefined) {
this._isStagedUncommitted = Git.isStagedUncommitted(this.sha);
}
return this._isStagedUncommitted;
}
get isUncommitted(): boolean {

+ 5
- 5
src/git/models/logCommit.ts Переглянути файл

@ -96,14 +96,14 @@ export class GitLogCommit extends GitCommit {
});
}
with(changes: { type?: GitCommitType, sha?: string, fileName?: string, originalFileName?: string | null, previousFileName?: string | null, previousSha?: string | null, status?: GitStatusFileStatus, fileStatuses?: IGitStatusFile[] | null }): GitLogCommit {
with(changes: { type?: GitCommitType, sha?: string | null, fileName?: string, author?: string, date?: Date, message?: string, originalFileName?: string | null, previousFileName?: string | null, previousSha?: string | null, status?: GitStatusFileStatus, fileStatuses?: IGitStatusFile[] | null }): GitLogCommit {
return new GitLogCommit(changes.type || this.type,
this.repoPath,
changes.sha || this.sha,
this.getChangedValue(changes.sha, this.sha)!,
changes.fileName || this.fileName,
this.author,
this.date,
this.message,
changes.author || this.author,
changes.date || this.date,
changes.message || this.message,
changes.status || this.status,
this.getChangedValue(changes.fileStatuses, this.fileStatuses),
this.getChangedValue(changes.originalFileName, this.originalFileName),

+ 10
- 10
src/gitService.ts Переглянути файл

@ -82,7 +82,8 @@ export class GitService extends Disposable {
static emptyPromise: Promise<GitBlame | GitDiff | GitLog | undefined> = Promise.resolve(undefined);
static deletedSha = 'ffffffffffffffffffffffffffffffffffffffff';
static uncommittedSha = '0000000000000000000000000000000000000000';
static stagedUncommittedSha = Git.stagedUncommittedSha;
static uncommittedSha = Git.uncommittedSha;
config: IConfig;
@ -1165,14 +1166,16 @@ export class GitService extends Disposable {
return status;
}
async getVersionedFile(repoPath: string | undefined, fileName: string, sha: string) {
async getVersionedFile(repoPath: string | undefined, fileName: string, sha?: string) {
Logger.log(`getVersionedFile('${repoPath}', '${fileName}', '${sha}')`);
if (!sha || (Git.isUncommitted(sha) && !Git.isStagedUncommitted(sha))) return fileName;
const file = await Git.getVersionedFile(repoPath, fileName, sha);
if (file === undefined) return undefined;
const cacheKey = this.getCacheEntryKey(file);
const entry = new UriCacheEntry(new GitUri(Uri.file(fileName), { sha, repoPath: repoPath!, fileName }));
const entry = new UriCacheEntry(new GitUri(Uri.file(fileName), { sha: sha, repoPath: repoPath!, fileName }));
this._versionedUriCache.set(cacheKey, entry);
return file;
}
@ -1194,13 +1197,6 @@ export class GitService extends Disposable {
return (editor.viewColumn !== undefined || this.isTrackable(editor.document.uri) || this.hasGitUriForFile(editor));
}
async isFileUncommitted(uri: GitUri): Promise<boolean> {
Logger.log(`isFileUncommitted('${uri.repoPath}', '${uri.fsPath}')`);
const status = await this.getStatusForFile(uri.repoPath!, uri.fsPath);
return !!status;
}
isTrackable(scheme: string): boolean;
isTrackable(uri: Uri): boolean;
isTrackable(schemeOruri: string | Uri): boolean {
@ -1344,6 +1340,10 @@ export class GitService extends Disposable {
return Git.isSha(sha);
}
static isStagedUncommitted(sha: string): boolean {
return Git.isStagedUncommitted(sha);
}
static isUncommitted(sha: string): boolean {
return Git.isUncommitted(sha);
}

+ 52
- 4
src/views/fileHistoryNode.ts Переглянути файл

@ -3,7 +3,7 @@ import { Iterables } from '../system';
import { Disposable, TreeItem, TreeItemCollapsibleState } from 'vscode';
import { CommitFileNode, CommitFileNodeDisplayAs } from './commitFileNode';
import { ExplorerNode, MessageNode, ResourceType } from './explorerNode';
import { GitUri, Repository, RepositoryChange, RepositoryChangeEvent } from '../gitService';
import { BlameabilityChangeEvent, BlameabilityChangeReason, GitCommitType, GitLogCommit, GitService, GitUri, Repository, RepositoryChange, RepositoryChangeEvent } from '../gitService';
import { GitExplorer } from './gitExplorer';
import { Logger } from '../logger';
@ -20,10 +20,49 @@ export class FileHistoryNode extends ExplorerNode {
async getChildren(): Promise<ExplorerNode[]> {
this.updateSubscription();
const children: ExplorerNode[] = [];
const status = await this.explorer.git.getStatusForFile(this.uri.repoPath!, this.uri.fsPath);
if (status !== undefined && (status.indexStatus !== undefined || status.workTreeStatus !== undefined)) {
let sha;
let previousSha;
if (status.workTreeStatus !== undefined) {
sha = GitService.uncommittedSha;
if (status.indexStatus !== undefined) {
previousSha = GitService.stagedUncommittedSha;
}
else if (status.workTreeStatus !== '?') {
previousSha = 'HEAD';
}
}
else {
sha = GitService.stagedUncommittedSha;
previousSha = 'HEAD';
}
const commit = new GitLogCommit(
GitCommitType.File,
this.uri.repoPath!,
sha,
status.fileName,
'You',
new Date(),
'',
status.status,
[status],
status.originalFileName,
previousSha,
status.originalFileName || status.fileName);
children.push(new CommitFileNode(status, commit, this.explorer, CommitFileNodeDisplayAs.CommitLabel | CommitFileNodeDisplayAs.StatusIcon));
}
const log = await this.explorer.git.getLogForFile(this.uri.repoPath, this.uri.fsPath, this.uri.sha);
if (log === undefined) return [new MessageNode('No file history')];
if (log !== undefined) {
children.push(...Iterables.map(log.commits.values(), c => new CommitFileNode(c.fileStatuses[0], c, this.explorer, CommitFileNodeDisplayAs.CommitLabel | CommitFileNodeDisplayAs.StatusIcon)));
}
return [...Iterables.map(log.commits.values(), c => new CommitFileNode(c.fileStatuses[0], c, this.explorer, CommitFileNodeDisplayAs.CommitLabel | CommitFileNodeDisplayAs.StatusIcon))];
if (children.length === 0) return [new MessageNode('No file history')];
return children;
}
getTreeItem(): TreeItem {
@ -45,7 +84,8 @@ export class FileHistoryNode extends ExplorerNode {
if (this.explorer.autoRefresh) {
this.disposable = this.disposable || Disposable.from(
this.explorer.onDidChangeAutoRefresh(this.onAutoRefreshChanged, this),
this.repo.onDidChange(this.onRepoChanged, this)
this.repo.onDidChange(this.onRepoChanged, this),
this.explorer.gitContextTracker.onDidChangeBlameability(this.onBlameabilityChanged, this)
);
}
else if (this.disposable !== undefined) {
@ -58,6 +98,14 @@ export class FileHistoryNode extends ExplorerNode {
this.updateSubscription();
}
private onBlameabilityChanged(e: BlameabilityChangeEvent) {
if (!e.blameable || e.reason !== BlameabilityChangeReason.DocumentChanged) return;
// Logger.log(`RepositoryNode.onBlameabilityChanged(${e.reason}); triggering node refresh`);
this.explorer.refreshNode(this);
}
private onRepoChanged(e: RepositoryChangeEvent) {
if (e.changed(RepositoryChange.Stashes, true)) return;

+ 3
- 2
src/views/gitExplorer.ts Переглянути файл

@ -6,7 +6,7 @@ import { configuration, IGitExplorerConfig } from '../configuration';
import { CommandContext, GlyphChars, setCommandContext, WorkspaceState } from '../constants';
import { ExplorerCommands } from './explorerCommands';
import { BranchHistoryNode, ExplorerNode, HistoryNode, MessageNode, RepositoriesNode, RepositoryNode } from './explorerNodes';
import { GitChangeEvent, GitChangeReason, GitService, GitUri } from '../gitService';
import { GitChangeEvent, GitChangeReason, GitContextTracker, GitService, GitUri } from '../gitService';
import { Logger } from '../logger';
export * from './explorerNodes';
@ -54,7 +54,8 @@ export class GitExplorer implements TreeDataProvider {
constructor(
public readonly context: ExtensionContext,
public readonly git: GitService
public readonly git: GitService,
public readonly gitContextTracker: GitContextTracker
) {
context.subscriptions.push(
new ExplorerCommands(this),

Завантаження…
Відмінити
Зберегти