Skip to content

Commit

Permalink
#479 New Repository Dropdown Order option "Workspace Full Path", that…
Browse files Browse the repository at this point in the history
… sorts repositories according to the workspace folder order, then alphabetically by the full path of the repository. This is the new default order.
  • Loading branch information
mhutchie committed Mar 14, 2021
1 parent 556e494 commit 2458636
Show file tree
Hide file tree
Showing 15 changed files with 513 additions and 176 deletions.
8 changes: 5 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -1068,13 +1068,15 @@
"type": "string",
"enum": [
"Full Path",
"Name"
"Name",
"Workspace Full Path"
],
"enumDescriptions": [
"Sort repositories alphabetically by the full path of the repository.",
"Sort repositories alphabetically by the name of the repository."
"Sort repositories alphabetically by the name of the repository.",
"Sort repositories according to the workspace folder order, then alphabetically by the full path of the repository."
],
"default": "Full Path",
"default": "Workspace Full Path",
"description": "Specifies the order that repositories are sorted in the repository dropdown on the Git Graph View (only visible when more than one repository exists in the current Visual Studio Code Workspace)."
},
"git-graph.retainContextWhenHidden": {
Expand Down
6 changes: 3 additions & 3 deletions src/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { CodeReviewData, CodeReviews, ExtensionState } from './extensionState';
import { GitGraphView } from './gitGraphView';
import { Logger } from './logger';
import { RepoManager } from './repoManager';
import { GitExecutable, UNABLE_TO_FIND_GIT_MSG, abbrevCommit, abbrevText, copyToClipboard, doesVersionMeetRequirement, getExtensionVersion, getPathFromUri, getRelativeTimeDiff, getRepoName, isPathInWorkspace, openFile, resolveToSymbolicPath, showErrorMessage, showInformationMessage } from './utils';
import { GitExecutable, UNABLE_TO_FIND_GIT_MSG, abbrevCommit, abbrevText, copyToClipboard, doesVersionMeetRequirement, getExtensionVersion, getPathFromUri, getRelativeTimeDiff, getRepoName, getSortedRepositoryPaths, isPathInWorkspace, openFile, resolveToSymbolicPath, showErrorMessage, showInformationMessage } from './utils';
import { Disposable } from './utils/disposable';
import { Event } from './utils/event';

Expand Down Expand Up @@ -157,7 +157,7 @@ export class CommandManager extends Disposable {
}

const repos = this.repoManager.getRepos();
const items: vscode.QuickPickItem[] = Object.keys(repos).map((path) => ({
const items: vscode.QuickPickItem[] = getSortedRepositoryPaths(repos, getConfig().repoDropdownOrder).map((path) => ({
label: repos[path].name || getRepoName(path),
description: path
}));
Expand Down Expand Up @@ -188,7 +188,7 @@ export class CommandManager extends Disposable {
*/
private fetch() {
const repos = this.repoManager.getRepos();
const repoPaths = Object.keys(repos);
const repoPaths = getSortedRepositoryPaths(repos, getConfig().repoDropdownOrder);

if (repoPaths.length > 1) {
const items: vscode.QuickPickItem[] = repoPaths.map((path) => ({
Expand Down
9 changes: 6 additions & 3 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -523,9 +523,12 @@ class Config {
* Get the value of the `git-graph.repositoryDropdownOrder` Extension Setting.
*/
get repoDropdownOrder(): RepoDropdownOrder {
return this.config.get<string>('repositoryDropdownOrder', 'Full Path') === 'Name'
? RepoDropdownOrder.Name
: RepoDropdownOrder.FullPath;
const order = this.config.get<string>('repositoryDropdownOrder', 'Workspace Full Path');
return order === 'Full Path'
? RepoDropdownOrder.FullPath
: order === 'Name'
? RepoDropdownOrder.Name
: RepoDropdownOrder.WorkspaceFullPath;
}

/**
Expand Down
3 changes: 2 additions & 1 deletion src/extensionState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ export const DEFAULT_REPO_STATE: GitRepoState = {
showRemoteBranches: true,
showRemoteBranchesV2: BooleanOverride.Default,
showStashes: BooleanOverride.Default,
showTags: BooleanOverride.Default
showTags: BooleanOverride.Default,
workspaceFolderIndex: null
};

const DEFAULT_GIT_GRAPH_VIEW_GLOBAL_STATE: GitGraphViewGlobalState = {
Expand Down
79 changes: 63 additions & 16 deletions src/repoManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,10 @@ export class RepoManager extends Disposable {
this.startupTasks();

this.registerDisposables(
// Monitor changes to the workspace folders to: search added folders for repositories, remove repositories within deleted folders
// Monitor changes to the workspace folders to:
// - search added folders for repositories
// - remove repositories within deleted folders
// - apply changes to the order of workspace folders
vscode.workspace.onDidChangeWorkspaceFolders(async (e) => {
let changes = false, path;
if (e.added.length > 0) {
Expand All @@ -83,7 +86,11 @@ export class RepoManager extends Disposable {
this.stopWatchingFolder(path);
}
}
if (changes) this.sendRepos();
changes = this.updateReposWorkspaceFolderIndex() || changes;

if (changes) {
this.sendRepos();
}
}),

// Monitor changes to the maxDepthOfRepoSearch Extension Setting, and trigger a new search if needed
Expand Down Expand Up @@ -143,6 +150,7 @@ export class RepoManager extends Disposable {
*/
private async startupTasks() {
this.removeReposNotInWorkspace();
this.updateReposWorkspaceFolderIndex();
if (!await this.checkReposExist()) this.sendRepos();
this.checkReposForNewConfig();
await this.checkReposForNewSubmodules();
Expand All @@ -154,16 +162,10 @@ export class RepoManager extends Disposable {
* Remove any repositories that are no longer in the current workspace.
*/
private removeReposNotInWorkspace() {
let rootsExact = [], rootsFolder = [], workspaceFolders = vscode.workspace.workspaceFolders, repoPaths = Object.keys(this.repos), path;
if (typeof workspaceFolders !== 'undefined') {
for (let i = 0; i < workspaceFolders.length; i++) {
path = getPathFromUri(workspaceFolders[i].uri);
rootsExact.push(path);
rootsFolder.push(pathWithTrailingSlash(path));
}
}
const workspaceFolderInfo = getWorkspaceFolderInfoForRepoInclusionMapping();
const rootsExact = workspaceFolderInfo.rootsExact, rootsFolder = workspaceFolderInfo.rootsFolder, repoPaths = Object.keys(this.repos);
for (let i = 0; i < repoPaths.length; i++) {
let repoPathFolder = pathWithTrailingSlash(repoPaths[i]);
const repoPathFolder = pathWithTrailingSlash(repoPaths[i]);
if (rootsExact.indexOf(repoPaths[i]) === -1 && !rootsFolder.find(root => repoPaths[i].startsWith(root)) && !rootsExact.find(root => root.startsWith(repoPathFolder))) {
this.removeRepo(repoPaths[i]);
}
Expand Down Expand Up @@ -219,11 +221,7 @@ export class RepoManager extends Disposable {
* @returns The set of repositories.
*/
public getRepos() {
let repoPaths = Object.keys(this.repos).sort((a, b) => a.localeCompare(b)), repos: GitRepoSet = {};
for (let i = 0; i < repoPaths.length; i++) {
repos[repoPaths[i]] = this.repos[repoPaths[i]];
}
return repos;
return Object.assign({}, this.repos);
}

/**
Expand Down Expand Up @@ -303,6 +301,7 @@ export class RepoManager extends Disposable {
return false;
} else {
this.repos[repo] = Object.assign({}, DEFAULT_REPO_STATE);
this.updateReposWorkspaceFolderIndex(repo);
this.extensionState.saveRepos(this.repos);
this.logger.log('Added new repo: ' + repo);
await this.checkRepoForNewConfig(repo, true);
Expand Down Expand Up @@ -382,6 +381,36 @@ export class RepoManager extends Disposable {
});
}

/**
* Update each repositories workspaceFolderIndex based on the current workspace.
* @param repo If provided, only update this specific repository.
* @returns TRUE => At least one repository was changed, FALSE => No repositories were changed.
*/
private updateReposWorkspaceFolderIndex(repo: string | null = null) {
const workspaceFolderInfo = getWorkspaceFolderInfoForRepoInclusionMapping();
const rootsExact = workspaceFolderInfo.rootsExact, rootsFolder = workspaceFolderInfo.rootsFolder, workspaceFolders = workspaceFolderInfo.workspaceFolders;
const repoPaths = repo !== null && this.isKnownRepo(repo) ? [repo] : Object.keys(this.repos);
let changes = false, rootIndex: number, workspaceFolderIndex: number | null;
for (let i = 0; i < repoPaths.length; i++) {
rootIndex = rootsExact.indexOf(repoPaths[i]);
if (rootIndex === -1) {
// Find a workspace folder that contains the repository
rootIndex = rootsFolder.findIndex((root) => repoPaths[i].startsWith(root));
}
if (rootIndex === -1) {
// Find a workspace folder that is contained within the repository
const repoPathFolder = pathWithTrailingSlash(repoPaths[i]);
rootIndex = rootsExact.findIndex((root) => root.startsWith(repoPathFolder));
}
workspaceFolderIndex = rootIndex > -1 ? workspaceFolders[rootIndex].index : null;
if (this.repos[repoPaths[i]].workspaceFolderIndex !== workspaceFolderIndex) {
this.repos[repoPaths[i]].workspaceFolderIndex = workspaceFolderIndex;
changes = true;
}
}
return changes;
}

/**
* Set the state of a known repository.
* @param repo The repository the state belongs to.
Expand Down Expand Up @@ -659,6 +688,24 @@ export class RepoManager extends Disposable {
}
}

/**
* Gets the current workspace folders, and generates information required to identify whether a repository is within any of the workspace folders.
* @returns The Workspace Folder Information.
*/
function getWorkspaceFolderInfoForRepoInclusionMapping() {
let rootsExact = [], rootsFolder = [], workspaceFolders = vscode.workspace.workspaceFolders || [], path;
for (let i = 0; i < workspaceFolders.length; i++) {
path = getPathFromUri(workspaceFolders[i].uri);
rootsExact.push(path);
rootsFolder.push(pathWithTrailingSlash(path));
}
return {
workspaceFolders: workspaceFolders,
rootsExact: rootsExact,
rootsFolder: rootsFolder
};
}

/**
* Check if the specified path is a directory.
* @param path The path to check.
Expand Down
4 changes: 3 additions & 1 deletion src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,7 @@ export interface GitRepoState {
showRemoteBranchesV2: BooleanOverride;
showStashes: BooleanOverride;
showTags: BooleanOverride;
workspaceFolderIndex: number | null;
}


Expand Down Expand Up @@ -515,7 +516,8 @@ export const enum RepoCommitOrdering {

export const enum RepoDropdownOrder {
FullPath,
Name
Name,
WorkspaceFullPath
}

export const enum SquashMessageFormat {
Expand Down
32 changes: 29 additions & 3 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { getConfig } from './config';
import { DataSource } from './dataSource';
import { DiffSide, encodeDiffDocUri } from './diffDocProvider';
import { ExtensionState } from './extensionState';
import { ErrorInfo, GitFileStatus, PullRequestConfig, PullRequestProvider } from './types';
import { ErrorInfo, GitFileStatus, GitRepoSet, PullRequestConfig, PullRequestProvider, RepoDropdownOrder } from './types';

export const UNCOMMITTED = '*';
export const UNABLE_TO_FIND_GIT_MSG = 'Unable to find a Git executable. Either: Set the Visual Studio Code Setting "git.path" to the path and filename of an existing Git executable, or install Git and restart Visual Studio Code.';
Expand Down Expand Up @@ -210,15 +210,41 @@ export function getNonce() {
* @returns The short name.
*/
export function getRepoName(path: string) {
let firstSep = path.indexOf('/');
const firstSep = path.indexOf('/');
if (firstSep === path.length - 1 || firstSep === -1) {
return path; // Path has no slashes, or a single trailing slash ==> use the path
} else {
let p = path.endsWith('/') ? path.substring(0, path.length - 1) : path; // Remove trailing slash if it exists
const p = path.endsWith('/') ? path.substring(0, path.length - 1) : path; // Remove trailing slash if it exists
return p.substring(p.lastIndexOf('/') + 1);
}
}

/**
* Get a sorted list of repository paths from a given GitRepoSet.
* @param repos The set of repositories.
* @param order The order to sort the repositories.
* @returns An array of ordered repository paths.
*/
export function getSortedRepositoryPaths(repos: GitRepoSet, order: RepoDropdownOrder): ReadonlyArray<string> {
const repoPaths = Object.keys(repos);
if (order === RepoDropdownOrder.WorkspaceFullPath) {
return repoPaths.sort((a, b) => repos[a].workspaceFolderIndex === repos[b].workspaceFolderIndex
? a.localeCompare(b)
: repos[a].workspaceFolderIndex === null
? 1
: repos[b].workspaceFolderIndex === null
? -1
: repos[a].workspaceFolderIndex! - repos[b].workspaceFolderIndex!
);
} else if (order === RepoDropdownOrder.FullPath) {
return repoPaths.sort((a, b) => a.localeCompare(b));
} else {
return repoPaths.map((path) => ({ name: repos[path].name || getRepoName(path), path: path }))
.sort((a, b) => a.name !== b.name ? a.name.localeCompare(b.name) : a.path.localeCompare(b.path))
.map((x) => x.path);
}
}


/* Visual Studio Code Command Wrappers */

Expand Down

0 comments on commit 2458636

Please sign in to comment.