Skip to content

Commit

Permalink
#158 Improved repository management for various Visual Studio Code an…
Browse files Browse the repository at this point in the history
…d Git repository workflows.
  • Loading branch information
mhutchie committed Aug 14, 2019
1 parent 6eba4a7 commit 02faa23
Show file tree
Hide file tree
Showing 10 changed files with 75 additions and 83 deletions.
11 changes: 5 additions & 6 deletions src/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,16 @@ export function addGitRepository(gitExecutable: GitExecutable | null, repoManage
vscode.window.showOpenDialog({ canSelectFiles: false, canSelectFolders: true, canSelectMany: false }).then(uris => {
if (uris && uris.length > 0) {
let path = getPathFromUri(uris[0]);
let folderName = path.substring(path.lastIndexOf('/') + 1);
if (isPathInWorkspace(path)) {
repoManager.registerRepo(path, false, false).then(valid => {
if (valid) {
vscode.window.showInformationMessage('The repository "' + folderName + '" was added to Git Graph.');
repoManager.registerRepo(path, false, false).then(status => {
if (status.error === null) {
vscode.window.showInformationMessage('The repository "' + status.root! + '" was added to Git Graph.');
} else {
vscode.window.showErrorMessage('The folder "' + folderName + '" is not a Git repository, and therefore could not be added to Git Graph.');
vscode.window.showErrorMessage(status.error + ' Therefore it could not be added to Git Graph.');
}
});
} else {
vscode.window.showErrorMessage('The folder "' + folderName + '" is not within the opened Visual Studio Code workspace, and therefore could not be added to Git Graph.');
vscode.window.showErrorMessage('The folder "' + path + '" is not within the opened Visual Studio Code workspace, and therefore could not be added to Git Graph.');
}
}
}, () => { });
Expand Down
33 changes: 13 additions & 20 deletions src/dataSource.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import * as cp from 'child_process';
import { decode, encodingExists } from 'iconv-lite';
import * as path from 'path';
import { Uri } from 'vscode';
import { AskpassEnvironment, AskpassManager } from './askpass/askpassManager';
import { getConfig } from './config';
import { Logger } from './logger';
import { BranchOrCommit, CommitOrdering, DiffSide, GitBranchData, GitCommandError, GitCommit, GitCommitComparisonData, GitCommitData, GitCommitDetails, GitCommitNode, GitFileChange, GitFileChangeType, GitRefData, GitRepoSettingsData, GitResetMode, GitTagDetailsData, GitUnsavedChanges } from './types';
import { abbrevCommit, getPathFromStr, GitExecutable, runGitCommandInNewTerminal, UNABLE_TO_FIND_GIT_MSG, UNCOMMITTED } from './utils';
import { abbrevCommit, getPathFromStr, getPathFromUri, GitExecutable, runGitCommandInNewTerminal, UNABLE_TO_FIND_GIT_MSG, UNCOMMITTED } from './utils';

const EOL_REGEX = /\r\n|\r|\n/g;
const INVALID_BRANCH_REGEX = /^\(.* .*\)$/;
Expand Down Expand Up @@ -160,30 +162,26 @@ export class DataSource {
date: parseInt(commitInfo[4]),
committer: commitInfo[5],
body: lines.slice(1, lastLine + 1).join('\n'),
repoRoot: '', fileChanges: [], error: null
fileChanges: [], error: null
};
}),
this.getDiffTreeNameStatus(repo, commitHash, commitHash),
this.getDiffTreeNumStat(repo, commitHash, commitHash),
this.topLevel(repo)
this.getDiffTreeNumStat(repo, commitHash, commitHash)
]).then((results) => {
results[0].repoRoot = results[3];
results[0].fileChanges = generateFileChanges(results[1], results[2], []);
resolve(results[0]);
}).catch((errorMessage) => resolve({ hash: '', parents: [], author: '', email: '', date: 0, committer: '', body: '', repoRoot: '', fileChanges: [], error: errorMessage }));
}).catch((errorMessage) => resolve({ hash: '', parents: [], author: '', email: '', date: 0, committer: '', body: '', fileChanges: [], error: errorMessage }));
});
}

public getUncommittedDetails(repo: string) {
return new Promise<GitCommitDetails>(resolve => {
let details: GitCommitDetails = { hash: UNCOMMITTED, parents: [], author: '', email: '', date: 0, committer: '', body: '', repoRoot: '', fileChanges: [], error: null };
let details: GitCommitDetails = { hash: UNCOMMITTED, parents: [], author: '', email: '', date: 0, committer: '', body: '', fileChanges: [], error: null };
Promise.all([
this.getDiffTreeNameStatus(repo, 'HEAD', ''),
this.getDiffTreeNumStat(repo, 'HEAD', ''),
this.getUntrackedFiles(repo),
this.topLevel(repo)
this.getUntrackedFiles(repo)
]).then((results) => {
details.repoRoot = results[3];
details.fileChanges = generateFileChanges(results[0], results[1], results[2]);
resolve(details);
}).catch((errorMessage) => {
Expand All @@ -198,16 +196,14 @@ export class DataSource {
Promise.all([
this.getDiffTreeNameStatus(repo, fromHash, toHash === UNCOMMITTED ? '' : toHash),
this.getDiffTreeNumStat(repo, fromHash, toHash === UNCOMMITTED ? '' : toHash),
toHash === UNCOMMITTED ? this.getUntrackedFiles(repo) : Promise.resolve<string[]>([]),
this.topLevel(repo)
toHash === UNCOMMITTED ? this.getUntrackedFiles(repo) : Promise.resolve<string[]>([])
]).then((results) => {
resolve({
repoRoot: results[3],
fileChanges: generateFileChanges(results[0], results[1], results[2]),
error: null
});
}).catch((errorMessage) => {
resolve({ repoRoot: '', fileChanges: [], error: errorMessage });
resolve({ fileChanges: [], error: errorMessage });
});
});
}
Expand Down Expand Up @@ -288,12 +284,9 @@ export class DataSource {
return this.spawnGit(['diff-index', 'HEAD'], repo, (stdout) => stdout !== '').then(changes => changes, () => false);
}

public isGitRepository(path: string) {
return this.spawnGit(['rev-parse', '--git-dir'], path, (stdout) => stdout).then(() => true, () => false);
}

public topLevel(repo: string) {
return this.spawnGit(['rev-parse', '--show-toplevel'], repo, (stdout) => getPathFromStr(stdout.trim()));
public repoRoot(repoPath: string) {
return this.spawnGit(['rev-parse', '--show-toplevel'], repoPath, (stdout) => getPathFromUri(Uri.file(path.normalize(stdout.trim())))).then((root) => root, () => null);
// null => path is not in a repo
}


Expand Down
4 changes: 2 additions & 2 deletions src/diffDocProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,9 @@ class DiffDocument {
}
}

export function encodeDiffDocUri(repo: string, repoRoot: string, filePath: string, commit: string, type: GitFileChangeType, diffSide: DiffSide): vscode.Uri {
export function encodeDiffDocUri(repo: string, filePath: string, commit: string, type: GitFileChangeType, diffSide: DiffSide): vscode.Uri {
return commit === UNCOMMITTED && type !== 'D'
? vscode.Uri.file(path.join(repoRoot, filePath))
? vscode.Uri.file(path.join(repo, filePath))
: vscode.Uri.parse(DiffDocProvider.scheme + ':' + getPathFromStr(filePath) + '?commit=' + encodeURIComponent(commit) + '&type=' + type + '&diffSide=' + diffSide + '&repo=' + encodeURIComponent(repo));
}

Expand Down
5 changes: 2 additions & 3 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,8 @@ export async function activate(context: vscode.ExtensionContext) {
// If command is run from the SCP menu, load the specific repo
loadRepo = getPathFromUri(args.rootUri);
if (!repoManager.isKnownRepo(loadRepo)) {
repoManager.registerRepo(loadRepo, true, true).then(valid => {
if (!valid) loadRepo = null;
GitGraphView.createOrShow(context.extensionPath, dataSource, extensionState, avatarManager, repoManager, logger, loadRepo);
repoManager.registerRepo(loadRepo, true, true).then(status => {
GitGraphView.createOrShow(context.extensionPath, dataSource, extensionState, avatarManager, repoManager, logger, status.root);
});
return;
}
Expand Down
8 changes: 4 additions & 4 deletions src/gitGraphView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ export class GitGraphView {
case 'copyFilePath':
this.sendMessage({
command: 'copyFilePath',
success: await copyFilePathToClipboard(msg.repoRoot, msg.filePath)
success: await copyFilePathToClipboard(msg.repo, msg.filePath)
});
break;
case 'copyToClipboard':
Expand Down Expand Up @@ -219,7 +219,7 @@ export class GitGraphView {
let branchData = await this.dataSource.getBranches(msg.repo, msg.showRemoteBranches), isRepo = true;
if (branchData.error) {
// If an error occurred, check to make sure the repo still exists
isRepo = await this.dataSource.isGitRepository(msg.repo);
isRepo = (await this.dataSource.repoRoot(msg.repo)) !== null;
if (!isRepo) branchData.error = null; // If the error is caused by the repo no longer existing, clear the error message
}
this.sendMessage({
Expand Down Expand Up @@ -258,7 +258,7 @@ export class GitGraphView {
case 'openFile':
this.sendMessage({
command: 'openFile',
error: await openFile(msg.repoRoot, msg.filePath)
error: await openFile(msg.repo, msg.filePath)
});
break;
case 'pruneRemote':
Expand Down Expand Up @@ -328,7 +328,7 @@ export class GitGraphView {
case 'viewDiff':
this.sendMessage({
command: 'viewDiff',
success: await viewDiff(msg.repo, msg.repoRoot, msg.fromHash, msg.toHash, msg.oldFilePath, msg.newFilePath, msg.type)
success: await viewDiff(msg.repo, msg.fromHash, msg.toHash, msg.oldFilePath, msg.newFilePath, msg.type)
});
break;
case 'viewScm':
Expand Down
52 changes: 35 additions & 17 deletions src/repoManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,24 +111,27 @@ export class RepoManager {
// expandExistingRepos: if true, in the event that a known repo is within the repo being registered, remove it (excluding subrepos)
// loadRepo: if true and the Git Graph view is visible, force it to be loaded with the repo that is being registered
public registerRepo(path: string, expandExistingRepos: boolean, loadRepo: boolean) {
return new Promise<boolean>(async resolve => {
if (await this.dataSource.isGitRepository(path)) {
return new Promise<{ root: string | null, error: string | null }>(async resolve => {
let root = await this.dataSource.repoRoot(path);
if (root === null) {
resolve({ root: null, error: 'The folder "' + path + '" is not a Git repository.' });
} else if (typeof this.repos[root] !== 'undefined') {
resolve({ root: null, error: 'The folder "' + path + '" is contained within the known repository "' + root + '".' });
} else {
if (expandExistingRepos) {
let reposInFolder = this.getReposInFolder(path);
let reposInFolder = this.getReposInFolder(root);
for (let i = 0; i < reposInFolder.length; i++) {
// Remove repos within the repo being registered, unless they are a subrepo of a currently known repo
if (reposInFolder.findIndex(knownRepo => reposInFolder[i].startsWith(knownRepo + '/')) === -1) this.removeRepo(reposInFolder[i]);
}
}
if (this.ignoredRepos.includes(path)) {
this.ignoredRepos.splice(this.ignoredRepos.indexOf(path), 1);
if (this.ignoredRepos.includes(root)) {
this.ignoredRepos.splice(this.ignoredRepos.indexOf(root), 1);
this.extensionState.setIgnoredRepos(this.ignoredRepos);
}
this.addRepo(path);
this.sendRepos(loadRepo ? path : null);
resolve(true);
} else {
resolve(false);
this.addRepo(root);
this.sendRepos(loadRepo ? root : null);
resolve({ root: root, error: null });
}
});
}
Expand Down Expand Up @@ -214,11 +217,14 @@ export class RepoManager {
public checkReposExist() {
return new Promise<boolean>(resolve => {
let repoPaths = Object.keys(this.repos), changes = false;
evalPromises(repoPaths, 3, path => this.dataSource.isGitRepository(path)).then(results => {
evalPromises(repoPaths, 3, path => this.dataSource.repoRoot(path)).then(results => {
for (let i = 0; i < repoPaths.length; i++) {
if (!results[i]) {
if (results[i] === null) {
this.removeRepo(repoPaths[i]);
changes = true;
} else if (repoPaths[i] !== results[i]) {
this.transferRepoState(repoPaths[i], results[i]!);
changes = true;
}
}
if (changes) this.sendRepos();
Expand All @@ -232,6 +238,14 @@ export class RepoManager {
this.extensionState.saveRepos(this.repos);
}

private transferRepoState(oldRepo: string, newRepo: string) {
this.repos[newRepo] = this.repos[oldRepo];
delete this.repos[oldRepo];
this.extensionState.saveRepos(this.repos);
if (this.extensionState.getLastActiveRepo() === oldRepo) this.extensionState.setLastActiveRepo(newRepo);
this.logger.log('Transferred repo state: ' + oldRepo + ' -> ' + newRepo);
}


/* Repo Searching */

Expand All @@ -250,15 +264,19 @@ export class RepoManager {

private searchDirectoryForRepos(directory: string, maxDepth: number) { // Returns a promise resolving to a boolean, that indicates if new repositories were found.
return new Promise<boolean>(resolve => {
if (this.isDirectoryWithinRepos(directory) || this.ignoredRepos.includes(directory)) {
if (this.isDirectoryWithinRepos(directory)) {
resolve(false);
return;
}

this.dataSource.isGitRepository(directory).then(isRepo => {
if (isRepo) {
this.addRepo(directory);
resolve(true);
this.dataSource.repoRoot(directory).then(root => {
if (root !== null) {
if (this.ignoredRepos.includes(root)) {
resolve(false);
} else {
this.addRepo(root);
resolve(true);
}
} else if (maxDepth > 0) {
fs.readdir(directory, async (err, dirContents) => {
if (err) {
Expand Down
8 changes: 2 additions & 6 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ export interface GitCommitData {
}

export interface GitCommitComparisonData {
repoRoot: string;
fileChanges: GitFileChange[];
error: GitCommandError;
}
Expand Down Expand Up @@ -83,7 +82,6 @@ export interface GitCommitDetails {
date: number;
committer: string;
body: string;
repoRoot: string;
fileChanges: GitFileChange[];
error: GitCommandError;
}
Expand Down Expand Up @@ -298,15 +296,14 @@ export interface ResponseCompareCommits {
command: 'compareCommits';
commitHash: string;
compareWithHash: string;
repoRoot: string;
fileChanges: GitFileChange[];
refresh: boolean;
error: GitCommandError;
}

export interface RequestCopyFilePath {
command: 'copyFilePath';
repoRoot: string;
repo: string;
filePath: string;
}
export interface ResponseCopyFilePath {
Expand Down Expand Up @@ -489,7 +486,7 @@ export interface ResponseMerge {

export interface RequestOpenFile {
command: 'openFile';
repoRoot: string;
repo: string;
filePath: string;
}
export interface ResponseOpenFile {
Expand Down Expand Up @@ -626,7 +623,6 @@ export interface ResponseTagDetails {
export interface RequestViewDiff {
command: 'viewDiff';
repo: string;
repoRoot: string;
fromHash: string;
toHash: string;
oldFilePath: string;
Expand Down
14 changes: 7 additions & 7 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,17 +49,17 @@ export function getNonce() {

// Visual Studio Code Command Wrappers

export function copyFilePathToClipboard(repoRoot: string, filePath: string) {
return vscode.env.clipboard.writeText(path.join(repoRoot, filePath)).then(() => true, () => false);
export function copyFilePathToClipboard(repo: string, filePath: string) {
return vscode.env.clipboard.writeText(path.join(repo, filePath)).then(() => true, () => false);
}

export function copyToClipboard(text: string) {
return vscode.env.clipboard.writeText(text).then(() => true, () => false);
}

export function openFile(repoRoot: string, filePath: string) {
export function openFile(repo: string, filePath: string) {
return new Promise<GitCommandError>(resolve => {
let p = path.join(repoRoot, filePath);
let p = path.join(repo, filePath);
fs.exists(p, exists => {
if (exists) {
vscode.commands.executeCommand('vscode.open', vscode.Uri.file(p), { preview: true, viewColumn: getConfig().openDiffTabLocation() })
Expand All @@ -71,7 +71,7 @@ export function openFile(repoRoot: string, filePath: string) {
});
}

export function viewDiff(repo: string, repoRoot: string, fromHash: string, toHash: string, oldFilePath: string, newFilePath: string, type: GitFileChangeType) {
export function viewDiff(repo: string, fromHash: string, toHash: string, oldFilePath: string, newFilePath: string, type: GitFileChangeType) {
return new Promise<boolean>(resolve => {
let options = { preview: true, viewColumn: getConfig().openDiffTabLocation() };
if (type !== 'U') {
Expand All @@ -84,10 +84,10 @@ export function viewDiff(repo: string, repoRoot: string, fromHash: string, toHas
let title = pathComponents[pathComponents.length - 1] + ' (' + desc + ')';
if (fromHash === UNCOMMITTED) fromHash = 'HEAD';

vscode.commands.executeCommand('vscode.diff', encodeDiffDocUri(repo, repoRoot, oldFilePath, fromHash === toHash ? fromHash + '^' : fromHash, type, 'old'), encodeDiffDocUri(repo, repoRoot, newFilePath, toHash, type, 'new'), title, options)
vscode.commands.executeCommand('vscode.diff', encodeDiffDocUri(repo, oldFilePath, fromHash === toHash ? fromHash + '^' : fromHash, type, 'old'), encodeDiffDocUri(repo, newFilePath, toHash, type, 'new'), title, options)
.then(() => resolve(true), () => resolve(false));
} else {
vscode.commands.executeCommand('vscode.open', vscode.Uri.file(path.join(repoRoot, newFilePath)), options)
vscode.commands.executeCommand('vscode.open', vscode.Uri.file(path.join(repo, newFilePath)), options)
.then(() => resolve(true), () => resolve(false));
}
});
Expand Down
1 change: 0 additions & 1 deletion web/global.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,6 @@ declare global {
hash: string;
srcElem: HTMLElement | null;
commitDetails: GG.GitCommitDetails | null;
repoRoot: string;
fileChanges: GG.GitFileChange[] | null;
fileTree: GitFolder | null;
compareWithHash: string | null;
Expand Down

0 comments on commit 02faa23

Please sign in to comment.