Browse Source

Closes #165 - support renames w/ branch compare

main
Eric Amodio 7 years ago
parent
commit
e2e306fde4
6 changed files with 73 additions and 41 deletions
  1. +3
    -2
      CHANGELOG.md
  2. +16
    -2
      src/commands/diffWithBranch.ts
  3. +5
    -2
      src/git/git.ts
  4. +21
    -2
      src/git/parsers/diffParser.ts
  5. +15
    -31
      src/git/parsers/statusParser.ts
  6. +13
    -2
      src/gitService.ts

+ 3
- 2
CHANGELOG.md View File

@ -6,16 +6,17 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p
## [Unreleased]
## [6.0.0-alpha] - 2017-10-21
## [6.0.0-alpha] - 2017-10-22
ATTENTION! To support multi-root workspaces some underlying fundamentals had to change, so please expect and report issues. Thanks!
### Added
- Adds multi-root workspace support
- Adds a progress indicator to the `Search Commits` command (`gitlens.showCommitSearch`)
- Adds code search support to the `Search Commits` command (`gitlens.showCommitSearch`) -- closes [#127](https://github.com/eamodio/vscode-gitlens/issues/127)
- Adds code search support to the `Search Commits` command (`gitlens.showCommitSearch`) -- closes [#127](https://github.com/eamodio/vscode-gitlens/issues/127)
- Use `~<regex>` to search for commits with differences whose patch text contains added/removed lines that match `<regex>`
- Use `=<regex>` to search for commits with differences that change the number of occurrences of the specified string (i.e. addition/deletion) in a file
- Adds support to the `Compare File with Branch...` command (`gitlens.diffWithBranch`) work with renamed files -- closes [#165](https://github.com/eamodio/vscode-gitlens/issues/165)
### Changed
- `GitLens` custom view will no longer show if there is no Git repository -- closes [#159](https://github.com/eamodio/vscode-gitlens/issues/159)

+ 16
- 2
src/commands/diffWithBranch.ts View File

@ -42,12 +42,26 @@ export class DiffWithBranchCommand extends ActiveEditorCommand {
const branch = pick.branch.name;
if (branch === undefined) return undefined;
let renamedUri: Uri | undefined;
let renamedTitle: string | undefined;
// Check to see if this file has been renamed
const statuses = await this.git.getDiffStatus(gitUri.repoPath, 'HEAD', branch, { filter: 'R' });
if (statuses !== undefined) {
const fileName = GitService.normalizePath(path.relative(gitUri.repoPath, gitUri.fsPath));
const rename = statuses.find(s => s.fileName === fileName);
if (rename !== undefined && rename.originalFileName !== undefined) {
renamedUri = Uri.file(path.join(gitUri.repoPath, rename.originalFileName));
renamedTitle = `${path.basename(rename.originalFileName)} (${branch})`;
}
}
const diffArgs: DiffWithCommandArgs = {
repoPath: gitUri.repoPath,
lhs: {
sha: pick.branch.remote ? `remotes/${branch}` : branch,
uri: gitUri as Uri,
title: `${path.basename(gitUri.fsPath)} (${branch})`
uri: renamedUri || gitUri as Uri,
title: renamedTitle || `${path.basename(gitUri.fsPath)} (${branch})`
},
rhs: {
sha: '',

+ 5
- 2
src/git/git.ts View File

@ -143,7 +143,7 @@ export class Git {
return Git.uncommittedRegex.test(sha);
}
static normalizePath(fileName: string, repoPath?: string) {
static normalizePath(fileName: string) {
return fileName && fileName.replace(/\\/g, '/');
}
@ -234,8 +234,11 @@ export class Git {
return gitCommand({ cwd: repoPath, encoding: encoding || defaultEncoding }, ...params, '--', fileName);
}
static diff_nameStatus(repoPath: string, sha1?: string, sha2?: string) {
static diff_nameStatus(repoPath: string, sha1?: string, sha2?: string, options: { filter?: string } = {}) {
const params = [`diff`, `--name-status`, `-M`, `--no-ext-diff`];
if (options && options.filter) {
params.push(`--diff-filter=${options.filter}`);
}
if (sha1) {
params.push(sha1);
}

+ 21
- 2
src/git/parsers/diffParser.ts View File

@ -1,9 +1,10 @@
'use strict';
import { Iterables, Strings } from '../../system';
import { GitDiff, GitDiffChunk, GitDiffChunkLine, GitDiffLine, GitDiffShortStat } from './../git';
import { GitDiff, GitDiffChunk, GitDiffChunkLine, GitDiffLine, GitDiffShortStat, GitStatusFile, GitStatusParser } from './../git';
const unifiedDiffRegex = /^@@ -([\d]+),([\d]+) [+]([\d]+),([\d]+) @@([\s\S]*?)(?=^@@)/gm;
const nameStatusDiffRegex = /^(.*?)\t(.*?)(?:\t(.*?))?$/gm;
const shortStatDiffRegex = /^\s*(\d+)\sfiles? changed(?:,\s+(\d+)\s+insertions?\(\+\))?(?:,\s+(\d+)\s+deletions?\(-\))?/;
const unifiedDiffRegex = /^@@ -([\d]+),([\d]+) [+]([\d]+),([\d]+) @@([\s\S]*?)(?=^@@)/gm;
export class GitDiffParser {
@ -118,6 +119,24 @@ export class GitDiffParser {
return chunkLines;
}
static parseNameStatus(data: string, repoPath: string): GitStatusFile[] | undefined {
if (!data) return undefined;
const statuses: GitStatusFile[] = [];
let match: RegExpExecArray | null = null;
do {
match = nameStatusDiffRegex.exec(data);
if (match == null) break;
statuses.push(GitStatusParser.parseStatusFile(repoPath, match[1], match[2], match[3]));
} while (match != null);
if (!statuses.length) return undefined;
return statuses;
}
static parseShortStat(data: string): GitDiffShortStat | undefined {
if (!data) return undefined;

+ 15
- 31
src/git/parsers/statusParser.ts View File

@ -1,15 +1,6 @@
'use strict';
import { Git, GitStatus, GitStatusFile, GitStatusFileStatus } from './../git';
interface FileStatusEntry {
staged: boolean;
status: GitStatusFileStatus;
fileName: string;
originalFileName: string;
workTreeStatus: GitStatusFileStatus;
indexStatus: GitStatusFileStatus;
}
const aheadStatusV1Regex = /(?:ahead ([0-9]+))/;
const behindStatusV1Regex = /(?:behind ([0-9]+))/;
@ -61,17 +52,15 @@ export class GitStatusParser {
}
}
else {
let entry: FileStatusEntry;
const rawStatus = line.substring(0, 2);
const fileName = line.substring(3);
if (rawStatus[0] === 'R') {
const [file1, file2] = fileName.replace(/\"/g, '').split('->');
entry = this._parseFileEntry(rawStatus, file2.trim(), file1.trim());
status.files.push(this.parseStatusFile(repoPath, rawStatus, file2.trim(), file1.trim()));
}
else {
entry = this._parseFileEntry(rawStatus, fileName);
status.files.push(this.parseStatusFile(repoPath, rawStatus, fileName));
}
status.files.push(new GitStatusFile(repoPath, entry.status, entry.workTreeStatus, entry.indexStatus, entry.fileName, entry.staged, entry.originalFileName));
}
}
}
@ -101,41 +90,36 @@ export class GitStatusParser {
}
else {
const lineParts = line.split(' ');
let entry: FileStatusEntry | undefined = undefined;
switch (lineParts[0][0]) {
case '1': // normal
entry = this._parseFileEntry(lineParts[1], lineParts.slice(8).join(' '));
status.files.push(this.parseStatusFile(repoPath, lineParts[1], lineParts.slice(8).join(' ')));
break;
case '2': // rename
const file = lineParts.slice(9).join(' ').split('\t');
entry = this._parseFileEntry(lineParts[1], file[0], file[1]);
status.files.push(this.parseStatusFile(repoPath, lineParts[1], file[0], file[1]));
break;
case 'u': // unmerged
entry = this._parseFileEntry(lineParts[1], lineParts.slice(10).join(' '));
status.files.push(this.parseStatusFile(repoPath, lineParts[1], lineParts.slice(10).join(' ')));
break;
case '?': // untracked
entry = this._parseFileEntry(' ?', lineParts.slice(1).join(' '));
status.files.push(this.parseStatusFile(repoPath, ' ?', lineParts.slice(1).join(' ')));
break;
}
if (entry !== undefined) {
status.files.push(new GitStatusFile(repoPath, entry.status, entry.workTreeStatus, entry.indexStatus, entry.fileName, entry.staged, entry.originalFileName));
}
}
}
}
private static _parseFileEntry(rawStatus: string, fileName: string, originalFileName?: string): FileStatusEntry {
static parseStatusFile(repoPath: string, rawStatus: string, fileName: string, originalFileName?: string): GitStatusFile {
const indexStatus = rawStatus[0] !== '.' ? rawStatus[0].trim() : undefined;
const workTreeStatus = rawStatus[1] !== '.' ? rawStatus[1].trim() : undefined;
return {
status: (indexStatus || workTreeStatus || '?') as GitStatusFileStatus,
fileName: fileName,
originalFileName: originalFileName,
staged: !!indexStatus,
indexStatus: indexStatus,
workTreeStatus: workTreeStatus
} as FileStatusEntry;
return new GitStatusFile(
repoPath,
(indexStatus || workTreeStatus || '?') as GitStatusFileStatus,
workTreeStatus as GitStatusFileStatus,
indexStatus as GitStatusFileStatus,
fileName,
!!indexStatus,
originalFileName);
}
}

+ 13
- 2
src/gitService.ts View File

@ -752,6 +752,17 @@ export class GitService extends Disposable {
}
}
async getDiffStatus(repoPath: string, sha1?: string, sha2?: string, options: { filter?: string } = {}): Promise<GitStatusFile[] | undefined> {
try {
const data = await Git.diff_nameStatus(repoPath, sha1, sha2, options);
const diff = GitDiffParser.parseNameStatus(data, repoPath);
return diff;
}
catch (ex) {
return undefined;
}
}
async getLogCommit(repoPath: string | undefined, fileName: string, options?: { firstIfMissing?: boolean, previous?: boolean }): Promise<GitLogCommit | undefined>;
async getLogCommit(repoPath: string | undefined, fileName: string, sha: string | undefined, options?: { firstIfMissing?: boolean, previous?: boolean }): Promise<GitLogCommit | undefined>;
async getLogCommit(repoPath: string | undefined, fileName: string, shaOrOptions?: string | undefined | { firstIfMissing?: boolean, previous?: boolean }, options?: { firstIfMissing?: boolean, previous?: boolean }): Promise<GitLogCommit | undefined> {
@ -1162,8 +1173,8 @@ export class GitService extends Disposable {
return Git.isUncommitted(sha);
}
static normalizePath(fileName: string, repoPath?: string): string {
return Git.normalizePath(fileName, repoPath);
static normalizePath(fileName: string): string {
return Git.normalizePath(fileName);
}
static shortenSha(sha: string | undefined) {

Loading…
Cancel
Save