Parcourir la source

Fixes file history issues w/ copied/deleted files

Fixes getWorkingUri to ensure file exists (and deal with deleted files)
Fixes rev_parse__show_toplevel to deal with paths that don't exist
main
Eric Amodio il y a 5 ans
Parent
révision
6e610d8867
9 fichiers modifiés avec 80 ajouts et 46 suppressions
  1. +1
    -1
      src/commands/diffWithRef.ts
  2. +42
    -19
      src/git/git.ts
  3. +17
    -16
      src/git/gitService.ts
  4. +1
    -1
      src/git/models/file.ts
  5. +12
    -6
      src/git/parsers/logParser.ts
  6. +1
    -1
      src/git/parsers/statusParser.ts
  7. +4
    -0
      src/git/shell.ts
  8. +1
    -1
      src/views/nodes/resultsFileNode.ts
  9. +1
    -1
      src/views/viewCommands.ts

+ 1
- 1
src/commands/diffWithRef.ts Voir le fichier

@ -54,7 +54,7 @@ export class DiffWithRefCommand extends ActiveEditorCommand {
let renamedTitle: string | undefined;
// Check to see if this file has been renamed
const files = await Container.git.getDiffStatus(gitUri.repoPath, 'HEAD', ref, { filter: 'R' });
const files = await Container.git.getDiffStatus(gitUri.repoPath, 'HEAD', ref, { filters: ['R', 'C'] });
if (files !== undefined) {
const fileName = Strings.normalizePath(paths.relative(gitUri.repoPath, gitUri.fsPath));
const rename = files.find(s => s.fileName === fileName);

+ 42
- 19
src/git/git.ts Voir le fichier

@ -1,14 +1,13 @@
'use strict';
/* eslint-disable @typescript-eslint/camelcase */
import * as paths from 'path';
import * as fs from 'fs';
import * as iconv from 'iconv-lite';
import { GlyphChars } from '../constants';
import { Container } from '../container';
import { Logger } from '../logger';
import { Objects, Strings } from '../system';
import { findGitPath, GitLocation } from './locator';
import { run, RunOptions } from './shell';
import { fsExists, run, RunOptions } from './shell';
import { GitBranchParser, GitLogParser, GitReflogParser, GitStashParser, GitTagParser } from './parsers/parsers';
import { GitFileStatus } from './models/file';
@ -16,7 +15,7 @@ export * from './models/models';
export * from './parsers/parsers';
export * from './remotes/provider';
export type GitLogDiffFilter = Exclude<GitFileStatus, '!' | '?'>;
export type GitDiffFilter = Exclude<GitFileStatus, '!' | '?'>;
const emptyArray = (Object.freeze([]) as any) as any[];
const emptyObj = Object.freeze({});
@ -371,9 +370,7 @@ export namespace Git {
} else {
// Ensure the specified --ignore-revs-file exists, otherwise the blame will fail
try {
supported = await new Promise(resolve =>
fs.exists(ignoreRevsFile, exists => resolve(exists))
);
supported = await fsExists(ignoreRevsFile);
} catch {
supported = false;
}
@ -566,7 +563,7 @@ export namespace Git {
fileName: string,
ref1?: string,
ref2?: string,
options: { encoding?: string; filter?: string; similarityThreshold?: number | null } = {}
options: { encoding?: string; filters?: GitDiffFilter[]; similarityThreshold?: number | null } = {}
): Promise<string> {
const params = [
'diff',
@ -575,8 +572,9 @@ export namespace Git {
'-U0',
'--minimal'
];
if (options.filter) {
params.push(`--diff-filter=${options.filter}`);
if (options.filters != null && options.filters.length !== 0) {
params.push(`--diff-filter=${options.filters.join(emptyStr)}`);
}
if (ref1) {
@ -590,11 +588,13 @@ export namespace Git {
params.push(Git.isUncommittedStaged(ref2) ? '--staged' : ref2);
}
const encoding: BufferEncoding = options.encoding === 'utf8' ? 'utf8' : 'binary';
try {
return await git<string>(
{ cwd: repoPath, configs: ['-c', 'color.diff=false'], encoding: encoding },
{
cwd: repoPath,
configs: ['-c', 'color.diff=false'],
encoding: options.encoding === 'utf8' ? 'utf8' : 'binary'
},
...params,
'--',
fileName
@ -618,7 +618,7 @@ export namespace Git {
repoPath: string,
ref1?: string,
ref2?: string,
{ filter, similarityThreshold }: { filter?: string; similarityThreshold?: number | null } = {}
{ filters, similarityThreshold }: { filters?: GitDiffFilter[]; similarityThreshold?: number | null } = {}
) {
const params = [
'diff',
@ -626,8 +626,8 @@ export namespace Git {
`-M${similarityThreshold == null ? '' : `${similarityThreshold}%`}`,
'--no-ext-diff'
];
if (filter) {
params.push(`--diff-filter=${filter}`);
if (filters != null && filters.length !== 0) {
params.push(`--diff-filter=${filters.join(emptyStr)}`);
}
if (ref1) {
params.push(ref1);
@ -767,7 +767,7 @@ export namespace Git {
startLine,
endLine
}: {
filters?: GitLogDiffFilter[];
filters?: GitDiffFilter[];
limit?: number;
firstParent?: boolean;
renames?: boolean;
@ -1030,9 +1030,32 @@ export namespace Git {
}
export async function rev_parse__show_toplevel(cwd: string): Promise<string | undefined> {
const data = await git<string>({ cwd: cwd, errors: GitErrorHandling.Ignore }, 'rev-parse', '--show-toplevel');
// Make sure to normalize: https://github.com/git-for-windows/git/issues/2478
return data.length === 0 ? undefined : Strings.normalizePath(data.trim());
try {
const data = await git<string>(
{ cwd: cwd, errors: GitErrorHandling.Throw },
'rev-parse',
'--show-toplevel'
);
// Make sure to normalize: https://github.com/git-for-windows/git/issues/2478
return data.length === 0 ? undefined : Strings.normalizePath(data.trim());
} catch (ex) {
if (ex.code === 'ENOENT') {
// If the `cwd` doesn't exist, walk backward to see if any parent folder exists
let exists = await fsExists(cwd);
if (!exists) {
do {
const parent = paths.dirname(cwd);
if (parent === cwd || parent.length === 0) return undefined;
cwd = parent;
exists = await fsExists(cwd);
} while (!exists);
return rev_parse__show_toplevel(cwd);
}
}
return undefined;
}
}
export function shortlog(repoPath: string) {

+ 17
- 16
src/git/gitService.ts Voir le fichier

@ -55,6 +55,7 @@ import {
GitCommitType,
GitContributor,
GitDiff,
GitDiffFilter,
GitDiffHunkLine,
GitDiffParser,
GitDiffShortStat,
@ -62,7 +63,6 @@ import {
GitFile,
GitLog,
GitLogCommit,
GitLogDiffFilter,
GitLogParser,
GitReflog,
GitRemote,
@ -83,7 +83,7 @@ import {
import { GitUri } from './gitUri';
import { RemoteProviderFactory, RemoteProviders, RemoteProviderWithApi } from './remotes/factory';
import { GitReflogParser, GitShortLogParser } from './parsers/parsers';
import { isWindows } from './shell';
import { fsExists, isWindows } from './shell';
import { PullRequest, PullRequestDateFormatting } from './models/models';
export * from './gitUri';
@ -1428,7 +1428,7 @@ export class GitService implements Disposable {
} else {
data = await Git.diff(root, file, ref1, ref2, {
...options,
filter: 'M',
filters: ['M'],
similarityThreshold: Container.config.advanced.similarityThreshold
});
}
@ -1481,7 +1481,7 @@ export class GitService implements Disposable {
repoPath: string,
ref1?: string,
ref2?: string,
options: { filter?: string; similarityThreshold?: number } = {}
options: { filters?: GitDiffFilter[]; similarityThreshold?: number } = {}
): Promise<GitFile[] | undefined> {
try {
const data = await Git.diff__name_status(repoPath, ref1, ref2, {
@ -2067,7 +2067,7 @@ export class GitService implements Disposable {
// If we have no ref (or staged ref) there is no next commit
if (ref === undefined || ref.length === 0 || Git.isUncommittedStaged(ref)) return undefined;
let filters: GitLogDiffFilter[] | undefined;
let filters: GitDiffFilter[] | undefined;
if (ref === GitService.deletedOrMissingSha) {
// If we are trying to move next from a deleted or missing ref then get the first commit
ref = undefined;
@ -2088,7 +2088,7 @@ export class GitService implements Disposable {
// If the file was deleted, check for a possible rename
if (status === 'D') {
data = await Git.log__file(repoPath, '.', nextRef, {
filters: ['R'],
filters: ['R', 'C'],
limit: 1,
// startLine: editorLine !== undefined ? editorLine + 1 : undefined
simple: true
@ -2851,7 +2851,8 @@ export class GitService implements Disposable {
do {
data = await Git.ls_files(repoPath, fileName);
if (data !== undefined) {
return GitUri.resolveToUri(Strings.splitSingle(data, '\n')[0], repoPath);
fileName = Strings.splitSingle(data, '\n')[0];
break;
}
// TODO: Add caching
@ -2863,21 +2864,21 @@ export class GitService implements Disposable {
// Now check if that commit had any renames
data = await Git.log__file(repoPath, '.', ref, {
filters: ['R'],
filters: ['R', 'C', 'D'],
limit: 1,
simple: true
});
if (data == null || data.length === 0) {
return GitUri.resolveToUri(fileName, repoPath);
}
if (data == null || data.length === 0) break;
const [renamedRef, renamedFile] = GitLogParser.parseSimpleRenamed(data, fileName);
if (renamedRef === undefined || renamedFile === undefined) {
return GitUri.resolveToUri(fileName, repoPath);
}
const [foundRef, foundFile, foundStatus] = GitLogParser.parseSimpleRenamed(data, fileName);
if (foundStatus === 'D' && foundFile != null) return undefined;
if (foundRef == null || foundFile == null) break;
fileName = renamedFile;
fileName = foundFile;
} while (true);
uri = GitUri.resolveToUri(fileName, repoPath);
return (await fsExists(uri.fsPath)) ? uri : undefined;
}
isTrackable(scheme: string): boolean;

+ 1
- 1
src/git/models/file.ts Voir le fichier

@ -26,7 +26,7 @@ export namespace GitFile {
relativeTo?: string
): string {
const directory = GitUri.getDirectory(file.fileName, relativeTo);
return includeOriginal && file.status === 'R' && file.originalFileName
return includeOriginal && (file.status === 'R' || file.status === 'C') && file.originalFileName
? `${directory} ${Strings.pad(GlyphChars.ArrowLeft, 1, 1)} ${file.originalFileName}`
: directory;
}

+ 12
- 6
src/git/parsers/logParser.ts Voir le fichier

@ -12,13 +12,13 @@ const diffRegex = /diff --git a\/(.*) b\/(.*)/;
const diffRangeRegex = /^@@ -(\d+?),(\d+?) \+(\d+?),(\d+?) @@/;
export const fileStatusRegex = /(\S)\S*\t([^\t\n]+)(?:\t(.+))?/;
const fileStatusAndSummaryRegex = /^(\d+?|-)\s+?(\d+?|-)\s+?(.*)(?:\n\s(delete|rename|create))?/;
const fileStatusAndSummaryRegex = /^(\d+?|-)\s+?(\d+?|-)\s+?(.*)(?:\n\s(delete|rename|copy|create))?/;
const fileStatusAndSummaryRenamedFileRegex = /(.+)\s=>\s(.+)/;
const fileStatusAndSummaryRenamedFilePathRegex = /(.*?){(.+?)\s=>\s(.*?)}(.*)/;
const logFileSimpleRegex = /^<r> (.*)\s*(?:(?:diff --git a\/(.*) b\/(.*))|(?:(\S)\S*\t([^\t\n]+)(?:\t(.+))?))/gm;
const logFileSimpleRenamedRegex = /^<r> (\S+)\s*(.*)$/s;
const logFileSimpleRenamedFilesRegex = /^(\S)\S*\t([^\t\n]+)(?:\t(.+)?)$/gm;
const logFileSimpleRenamedFilesRegex = /^(\S)\S*\t([^\t\n]+)(?:\t(.+)?)?$/gm;
// Using %x00 codes because some shells seem to try to expand things if not
const lb = '%x3c'; // `%x${'<'.charCodeAt(0).toString(16)}`;
@ -258,8 +258,9 @@ export class GitLogParser {
entry.status = 'M' as GitFileStatus;
entry.fileName = match[3];
break;
case 'copy':
case 'rename':
entry.status = 'R' as GitFileStatus;
entry.status = (match[4] === 'copy' ? 'C' : 'R') as GitFileStatus;
renamedFileName = match[3];
renamedMatch = fileStatusAndSummaryRenamedFilePathRegex.exec(
@ -510,7 +511,12 @@ export class GitLogParser {
[, status, file, renamed] = match;
if (originalFileName !== file) continue;
if (originalFileName !== file) {
status = undefined;
file = undefined;
renamed = undefined;
continue;
}
// Stops excessive memory usage -- https://bugs.chromium.org/p/v8/issues/detail?id=2869
file = ` ${renamed || file}`.substr(1);
@ -523,9 +529,9 @@ export class GitLogParser {
// Ensure the regex state is reset
logFileSimpleRenamedFilesRegex.lastIndex = 0;
// Stops excessive memory usage -- https://bugs.chromium.org/p/v8/issues/detail?id=2869
return [
ref == null || ref.length === 0 ? undefined : ` ${ref}`.substr(1),
// Stops excessive memory usage -- https://bugs.chromium.org/p/v8/issues/detail?id=2869
ref == null || ref.length === 0 || file == null ? undefined : ` ${ref}`.substr(1),
file,
status as GitFileStatus | undefined
];

+ 1
- 1
src/git/parsers/statusParser.ts Voir le fichier

@ -49,7 +49,7 @@ export class GitStatusParser {
} else {
const rawStatus = line.substring(0, 2);
const fileName = line.substring(3);
if (rawStatus.startsWith('R')) {
if (rawStatus.startsWith('R') || rawStatus.startsWith('C')) {
const [file1, file2] = fileName.replace(/"/g, emptyStr).split('->');
files.push(this.parseStatusFile(repoPath, rawStatus, file2.trim(), file1.trim()));
} else {

+ 4
- 0
src/git/shell.ts Voir le fichier

@ -186,3 +186,7 @@ export function run(
}
});
}
export function fsExists(path: string) {
return new Promise<boolean>(resolve => fs.exists(path, exists => resolve(exists)));
}

+ 1
- 1
src/views/nodes/resultsFileNode.ts Voir le fichier

@ -111,7 +111,7 @@ export class ResultsFileNode extends ViewRefFileNode {
rhs: {
sha: this.ref2,
uri:
this.file.status === 'R'
this.file.status === 'R' || this.file.status === 'C'
? GitUri.fromFile(this.file, this.uri.repoPath!, this.ref2, true)
: this.uri
},

+ 1
- 1
src/views/viewCommands.ts Voir le fichier

@ -900,7 +900,7 @@ export class ViewCommands {
if (file.status === 'A') continue;
const uri1 = GitUri.fromFile(file, repoPath);
const uri2 = file.status === 'R' ? GitUri.fromFile(file, repoPath, ref2, true) : uri1;
const uri2 = file.status === 'R' || file.status === 'C' ? GitUri.fromFile(file, repoPath, ref2, true) : uri1;
diffArgs = {
repoPath: repoPath,

Chargement…
Annuler
Enregistrer