Skip to content

Commit 2458636

Browse files
committed
mhutchie#479 New Repository Dropdown Order option "Workspace Full Path", that sorts repositories according to the workspace folder order, then alphabetically by the full path of the repository. This is the new default order.
1 parent 556e494 commit 2458636

File tree

15 files changed

+513
-176
lines changed

15 files changed

+513
-176
lines changed

package.json

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1068,13 +1068,15 @@
10681068
"type": "string",
10691069
"enum": [
10701070
"Full Path",
1071-
"Name"
1071+
"Name",
1072+
"Workspace Full Path"
10721073
],
10731074
"enumDescriptions": [
10741075
"Sort repositories alphabetically by the full path of the repository.",
1075-
"Sort repositories alphabetically by the name of the repository."
1076+
"Sort repositories alphabetically by the name of the repository.",
1077+
"Sort repositories according to the workspace folder order, then alphabetically by the full path of the repository."
10761078
],
1077-
"default": "Full Path",
1079+
"default": "Workspace Full Path",
10781080
"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)."
10791081
},
10801082
"git-graph.retainContextWhenHidden": {

src/commands.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { CodeReviewData, CodeReviews, ExtensionState } from './extensionState';
88
import { GitGraphView } from './gitGraphView';
99
import { Logger } from './logger';
1010
import { RepoManager } from './repoManager';
11-
import { GitExecutable, UNABLE_TO_FIND_GIT_MSG, abbrevCommit, abbrevText, copyToClipboard, doesVersionMeetRequirement, getExtensionVersion, getPathFromUri, getRelativeTimeDiff, getRepoName, isPathInWorkspace, openFile, resolveToSymbolicPath, showErrorMessage, showInformationMessage } from './utils';
11+
import { GitExecutable, UNABLE_TO_FIND_GIT_MSG, abbrevCommit, abbrevText, copyToClipboard, doesVersionMeetRequirement, getExtensionVersion, getPathFromUri, getRelativeTimeDiff, getRepoName, getSortedRepositoryPaths, isPathInWorkspace, openFile, resolveToSymbolicPath, showErrorMessage, showInformationMessage } from './utils';
1212
import { Disposable } from './utils/disposable';
1313
import { Event } from './utils/event';
1414

@@ -157,7 +157,7 @@ export class CommandManager extends Disposable {
157157
}
158158

159159
const repos = this.repoManager.getRepos();
160-
const items: vscode.QuickPickItem[] = Object.keys(repos).map((path) => ({
160+
const items: vscode.QuickPickItem[] = getSortedRepositoryPaths(repos, getConfig().repoDropdownOrder).map((path) => ({
161161
label: repos[path].name || getRepoName(path),
162162
description: path
163163
}));
@@ -188,7 +188,7 @@ export class CommandManager extends Disposable {
188188
*/
189189
private fetch() {
190190
const repos = this.repoManager.getRepos();
191-
const repoPaths = Object.keys(repos);
191+
const repoPaths = getSortedRepositoryPaths(repos, getConfig().repoDropdownOrder);
192192

193193
if (repoPaths.length > 1) {
194194
const items: vscode.QuickPickItem[] = repoPaths.map((path) => ({

src/config.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -523,9 +523,12 @@ class Config {
523523
* Get the value of the `git-graph.repositoryDropdownOrder` Extension Setting.
524524
*/
525525
get repoDropdownOrder(): RepoDropdownOrder {
526-
return this.config.get<string>('repositoryDropdownOrder', 'Full Path') === 'Name'
527-
? RepoDropdownOrder.Name
528-
: RepoDropdownOrder.FullPath;
526+
const order = this.config.get<string>('repositoryDropdownOrder', 'Workspace Full Path');
527+
return order === 'Full Path'
528+
? RepoDropdownOrder.FullPath
529+
: order === 'Name'
530+
? RepoDropdownOrder.Name
531+
: RepoDropdownOrder.WorkspaceFullPath;
529532
}
530533

531534
/**

src/extensionState.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ export const DEFAULT_REPO_STATE: GitRepoState = {
3535
showRemoteBranches: true,
3636
showRemoteBranchesV2: BooleanOverride.Default,
3737
showStashes: BooleanOverride.Default,
38-
showTags: BooleanOverride.Default
38+
showTags: BooleanOverride.Default,
39+
workspaceFolderIndex: null
3940
};
4041

4142
const DEFAULT_GIT_GRAPH_VIEW_GLOBAL_STATE: GitGraphViewGlobalState = {

src/repoManager.ts

Lines changed: 63 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,10 @@ export class RepoManager extends Disposable {
6666
this.startupTasks();
6767

6868
this.registerDisposables(
69-
// Monitor changes to the workspace folders to: search added folders for repositories, remove repositories within deleted folders
69+
// Monitor changes to the workspace folders to:
70+
// - search added folders for repositories
71+
// - remove repositories within deleted folders
72+
// - apply changes to the order of workspace folders
7073
vscode.workspace.onDidChangeWorkspaceFolders(async (e) => {
7174
let changes = false, path;
7275
if (e.added.length > 0) {
@@ -83,7 +86,11 @@ export class RepoManager extends Disposable {
8386
this.stopWatchingFolder(path);
8487
}
8588
}
86-
if (changes) this.sendRepos();
89+
changes = this.updateReposWorkspaceFolderIndex() || changes;
90+
91+
if (changes) {
92+
this.sendRepos();
93+
}
8794
}),
8895

8996
// Monitor changes to the maxDepthOfRepoSearch Extension Setting, and trigger a new search if needed
@@ -143,6 +150,7 @@ export class RepoManager extends Disposable {
143150
*/
144151
private async startupTasks() {
145152
this.removeReposNotInWorkspace();
153+
this.updateReposWorkspaceFolderIndex();
146154
if (!await this.checkReposExist()) this.sendRepos();
147155
this.checkReposForNewConfig();
148156
await this.checkReposForNewSubmodules();
@@ -154,16 +162,10 @@ export class RepoManager extends Disposable {
154162
* Remove any repositories that are no longer in the current workspace.
155163
*/
156164
private removeReposNotInWorkspace() {
157-
let rootsExact = [], rootsFolder = [], workspaceFolders = vscode.workspace.workspaceFolders, repoPaths = Object.keys(this.repos), path;
158-
if (typeof workspaceFolders !== 'undefined') {
159-
for (let i = 0; i < workspaceFolders.length; i++) {
160-
path = getPathFromUri(workspaceFolders[i].uri);
161-
rootsExact.push(path);
162-
rootsFolder.push(pathWithTrailingSlash(path));
163-
}
164-
}
165+
const workspaceFolderInfo = getWorkspaceFolderInfoForRepoInclusionMapping();
166+
const rootsExact = workspaceFolderInfo.rootsExact, rootsFolder = workspaceFolderInfo.rootsFolder, repoPaths = Object.keys(this.repos);
165167
for (let i = 0; i < repoPaths.length; i++) {
166-
let repoPathFolder = pathWithTrailingSlash(repoPaths[i]);
168+
const repoPathFolder = pathWithTrailingSlash(repoPaths[i]);
167169
if (rootsExact.indexOf(repoPaths[i]) === -1 && !rootsFolder.find(root => repoPaths[i].startsWith(root)) && !rootsExact.find(root => root.startsWith(repoPathFolder))) {
168170
this.removeRepo(repoPaths[i]);
169171
}
@@ -219,11 +221,7 @@ export class RepoManager extends Disposable {
219221
* @returns The set of repositories.
220222
*/
221223
public getRepos() {
222-
let repoPaths = Object.keys(this.repos).sort((a, b) => a.localeCompare(b)), repos: GitRepoSet = {};
223-
for (let i = 0; i < repoPaths.length; i++) {
224-
repos[repoPaths[i]] = this.repos[repoPaths[i]];
225-
}
226-
return repos;
224+
return Object.assign({}, this.repos);
227225
}
228226

229227
/**
@@ -303,6 +301,7 @@ export class RepoManager extends Disposable {
303301
return false;
304302
} else {
305303
this.repos[repo] = Object.assign({}, DEFAULT_REPO_STATE);
304+
this.updateReposWorkspaceFolderIndex(repo);
306305
this.extensionState.saveRepos(this.repos);
307306
this.logger.log('Added new repo: ' + repo);
308307
await this.checkRepoForNewConfig(repo, true);
@@ -382,6 +381,36 @@ export class RepoManager extends Disposable {
382381
});
383382
}
384383

384+
/**
385+
* Update each repositories workspaceFolderIndex based on the current workspace.
386+
* @param repo If provided, only update this specific repository.
387+
* @returns TRUE => At least one repository was changed, FALSE => No repositories were changed.
388+
*/
389+
private updateReposWorkspaceFolderIndex(repo: string | null = null) {
390+
const workspaceFolderInfo = getWorkspaceFolderInfoForRepoInclusionMapping();
391+
const rootsExact = workspaceFolderInfo.rootsExact, rootsFolder = workspaceFolderInfo.rootsFolder, workspaceFolders = workspaceFolderInfo.workspaceFolders;
392+
const repoPaths = repo !== null && this.isKnownRepo(repo) ? [repo] : Object.keys(this.repos);
393+
let changes = false, rootIndex: number, workspaceFolderIndex: number | null;
394+
for (let i = 0; i < repoPaths.length; i++) {
395+
rootIndex = rootsExact.indexOf(repoPaths[i]);
396+
if (rootIndex === -1) {
397+
// Find a workspace folder that contains the repository
398+
rootIndex = rootsFolder.findIndex((root) => repoPaths[i].startsWith(root));
399+
}
400+
if (rootIndex === -1) {
401+
// Find a workspace folder that is contained within the repository
402+
const repoPathFolder = pathWithTrailingSlash(repoPaths[i]);
403+
rootIndex = rootsExact.findIndex((root) => root.startsWith(repoPathFolder));
404+
}
405+
workspaceFolderIndex = rootIndex > -1 ? workspaceFolders[rootIndex].index : null;
406+
if (this.repos[repoPaths[i]].workspaceFolderIndex !== workspaceFolderIndex) {
407+
this.repos[repoPaths[i]].workspaceFolderIndex = workspaceFolderIndex;
408+
changes = true;
409+
}
410+
}
411+
return changes;
412+
}
413+
385414
/**
386415
* Set the state of a known repository.
387416
* @param repo The repository the state belongs to.
@@ -659,6 +688,24 @@ export class RepoManager extends Disposable {
659688
}
660689
}
661690

691+
/**
692+
* Gets the current workspace folders, and generates information required to identify whether a repository is within any of the workspace folders.
693+
* @returns The Workspace Folder Information.
694+
*/
695+
function getWorkspaceFolderInfoForRepoInclusionMapping() {
696+
let rootsExact = [], rootsFolder = [], workspaceFolders = vscode.workspace.workspaceFolders || [], path;
697+
for (let i = 0; i < workspaceFolders.length; i++) {
698+
path = getPathFromUri(workspaceFolders[i].uri);
699+
rootsExact.push(path);
700+
rootsFolder.push(pathWithTrailingSlash(path));
701+
}
702+
return {
703+
workspaceFolders: workspaceFolders,
704+
rootsExact: rootsExact,
705+
rootsFolder: rootsFolder
706+
};
707+
}
708+
662709
/**
663710
* Check if the specified path is a directory.
664711
* @param path The path to check.

src/types.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,7 @@ export interface GitRepoState {
207207
showRemoteBranchesV2: BooleanOverride;
208208
showStashes: BooleanOverride;
209209
showTags: BooleanOverride;
210+
workspaceFolderIndex: number | null;
210211
}
211212

212213

@@ -515,7 +516,8 @@ export const enum RepoCommitOrdering {
515516

516517
export const enum RepoDropdownOrder {
517518
FullPath,
518-
Name
519+
Name,
520+
WorkspaceFullPath
519521
}
520522

521523
export const enum SquashMessageFormat {

src/utils.ts

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { getConfig } from './config';
66
import { DataSource } from './dataSource';
77
import { DiffSide, encodeDiffDocUri } from './diffDocProvider';
88
import { ExtensionState } from './extensionState';
9-
import { ErrorInfo, GitFileStatus, PullRequestConfig, PullRequestProvider } from './types';
9+
import { ErrorInfo, GitFileStatus, GitRepoSet, PullRequestConfig, PullRequestProvider, RepoDropdownOrder } from './types';
1010

1111
export const UNCOMMITTED = '*';
1212
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.';
@@ -210,15 +210,41 @@ export function getNonce() {
210210
* @returns The short name.
211211
*/
212212
export function getRepoName(path: string) {
213-
let firstSep = path.indexOf('/');
213+
const firstSep = path.indexOf('/');
214214
if (firstSep === path.length - 1 || firstSep === -1) {
215215
return path; // Path has no slashes, or a single trailing slash ==> use the path
216216
} else {
217-
let p = path.endsWith('/') ? path.substring(0, path.length - 1) : path; // Remove trailing slash if it exists
217+
const p = path.endsWith('/') ? path.substring(0, path.length - 1) : path; // Remove trailing slash if it exists
218218
return p.substring(p.lastIndexOf('/') + 1);
219219
}
220220
}
221221

222+
/**
223+
* Get a sorted list of repository paths from a given GitRepoSet.
224+
* @param repos The set of repositories.
225+
* @param order The order to sort the repositories.
226+
* @returns An array of ordered repository paths.
227+
*/
228+
export function getSortedRepositoryPaths(repos: GitRepoSet, order: RepoDropdownOrder): ReadonlyArray<string> {
229+
const repoPaths = Object.keys(repos);
230+
if (order === RepoDropdownOrder.WorkspaceFullPath) {
231+
return repoPaths.sort((a, b) => repos[a].workspaceFolderIndex === repos[b].workspaceFolderIndex
232+
? a.localeCompare(b)
233+
: repos[a].workspaceFolderIndex === null
234+
? 1
235+
: repos[b].workspaceFolderIndex === null
236+
? -1
237+
: repos[a].workspaceFolderIndex! - repos[b].workspaceFolderIndex!
238+
);
239+
} else if (order === RepoDropdownOrder.FullPath) {
240+
return repoPaths.sort((a, b) => a.localeCompare(b));
241+
} else {
242+
return repoPaths.map((path) => ({ name: repos[path].name || getRepoName(path), path: path }))
243+
.sort((a, b) => a.name !== b.name ? a.name.localeCompare(b.name) : a.path.localeCompare(b.path))
244+
.map((x) => x.path);
245+
}
246+
}
247+
222248

223249
/* Visual Studio Code Command Wrappers */
224250

0 commit comments

Comments
 (0)