Skip to content

Commit 23ce8ac

Browse files
authored
Merge pull request #106 from coocoo1112/cooperj/storeLastSelectedWorktree
store last selected worktree, and open that when reselecting repo
2 parents 9e02270 + 62cf767 commit 23ce8ac

4 files changed

Lines changed: 143 additions & 3 deletions

File tree

app/src/lib/stores/app-store.ts

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,11 @@ import {
204204
} from '../../ui/lib/diff-mode'
205205
import { pathExists } from '../../ui/lib/path-exists'
206206
import { updateStore } from '../../ui/lib/update-store'
207+
import {
208+
getPreferredWorktreePath,
209+
clearPreferredWorktreePath,
210+
} from '../worktree-preferences'
211+
import { normalizePath } from '../helpers/path'
207212
import { resizableComponentClass } from '../../ui/resizable'
208213
import { BypassReasonType } from '../../ui/secret-scanning/bypass-push-protection-dialog'
209214
import { findContributionTargetDefaultBranch } from '../branch'
@@ -2160,20 +2165,57 @@ export class AppStore extends TypedBaseStore<IAppState> {
21602165

21612166
this.selectedRepository = repository
21622167

2163-
this.emitUpdate()
21642168
this.stopBackgroundFetching()
21652169
this.stopPullRequestUpdater()
21662170
this._clearBanner()
21672171
this.stopBackgroundPruner()
21682172

21692173
if (repository == null) {
2174+
this.emitUpdate()
21702175
return Promise.resolve(null)
21712176
}
21722177

21732178
if (!(repository instanceof Repository)) {
2179+
this.emitUpdate()
21742180
return Promise.resolve(null)
21752181
}
21762182

2183+
// When returning to a repository that has worktrees, restore the
2184+
// previously active linked worktree so the user doesn't always land
2185+
// on the main worktree after switching repos.
2186+
if (!repository.isLinkedWorktree) {
2187+
const repoPath = normalizePath(repository.path)
2188+
const preferredPath = getPreferredWorktreePath(repoPath)
2189+
2190+
if (preferredPath && preferredPath !== repoPath) {
2191+
const linkedRepo = this.repositories.find(
2192+
r =>
2193+
r instanceof Repository && normalizePath(r.path) === preferredPath
2194+
)
2195+
2196+
if (linkedRepo instanceof Repository) {
2197+
repository = linkedRepo
2198+
this.selectedRepository = repository
2199+
} else {
2200+
const exists = await pathExists(preferredPath)
2201+
if (exists) {
2202+
const addedRepos = await this._addRepositories(
2203+
[preferredPath],
2204+
repository.login
2205+
)
2206+
if (addedRepos.length > 0) {
2207+
repository = addedRepos[0]
2208+
this.selectedRepository = repository
2209+
}
2210+
} else {
2211+
clearPreferredWorktreePath(repoPath)
2212+
}
2213+
}
2214+
}
2215+
}
2216+
2217+
this.emitUpdate()
2218+
21772219
if (persistSelection) {
21782220
setNumber(LastSelectedRepositoryIDKey, repository.id)
21792221
}
@@ -7269,6 +7311,23 @@ export class AppStore extends TypedBaseStore<IAppState> {
72697311
return
72707312
}
72717313

7314+
if (repository instanceof Repository) {
7315+
if (repository.isLinkedWorktree) {
7316+
const repoPath = normalizePath(repository.path)
7317+
const mainRepo = this.repositories.find(
7318+
r =>
7319+
r instanceof Repository &&
7320+
!r.isLinkedWorktree &&
7321+
getPreferredWorktreePath(normalizePath(r.path)) === repoPath
7322+
)
7323+
if (mainRepo instanceof Repository) {
7324+
clearPreferredWorktreePath(normalizePath(mainRepo.path))
7325+
}
7326+
} else {
7327+
clearPreferredWorktreePath(normalizePath(repository.path))
7328+
}
7329+
}
7330+
72727331
const allRepositories = await this.repositoriesStore.getAll()
72737332
if (allRepositories.length === 0) {
72747333
this._closeFoldout(FoldoutType.Repository)
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import { normalizePath } from './helpers/path'
2+
3+
const StorageKey = 'worktree-active-paths'
4+
5+
function getPreferences(): Record<string, string> {
6+
try {
7+
const raw = localStorage.getItem(StorageKey)
8+
return raw ? JSON.parse(raw) : {}
9+
} catch {
10+
return {}
11+
}
12+
}
13+
14+
function savePreferences(prefs: Record<string, string>) {
15+
localStorage.setItem(StorageKey, JSON.stringify(prefs))
16+
}
17+
18+
/**
19+
* Get the preferred worktree path for a given main worktree path.
20+
* Returns null if no preference is stored (defaults to main worktree).
21+
*/
22+
export function getPreferredWorktreePath(
23+
mainWorktreePath: string
24+
): string | null {
25+
const prefs = getPreferences()
26+
return prefs[normalizePath(mainWorktreePath)] ?? null
27+
}
28+
29+
/**
30+
* Store the user's active worktree choice for a repository.
31+
* If the active path is the main worktree itself, the preference is cleared
32+
* so the repo defaults to its main worktree on next visit.
33+
*/
34+
export function setPreferredWorktreePath(
35+
mainWorktreePath: string,
36+
activeWorktreePath: string
37+
) {
38+
const prefs = getPreferences()
39+
const normalizedMain = normalizePath(mainWorktreePath)
40+
const normalizedActive = normalizePath(activeWorktreePath)
41+
42+
if (normalizedMain === normalizedActive) {
43+
delete prefs[normalizedMain]
44+
} else {
45+
prefs[normalizedMain] = normalizedActive
46+
}
47+
48+
savePreferences(prefs)
49+
}
50+
51+
/**
52+
* Clear any stored worktree preference for a repository so it
53+
* defaults to the main worktree on next visit.
54+
*/
55+
export function clearPreferredWorktreePath(mainWorktreePath: string) {
56+
const prefs = getPreferences()
57+
delete prefs[normalizePath(mainWorktreePath)]
58+
savePreferences(prefs)
59+
}

app/src/ui/toolbar/worktree-dropdown.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { PopupType } from '../../models/popup'
1818
import { Resizable } from '../resizable'
1919
import { enableResizingToolbarButtons } from '../../lib/feature-flag'
2020
import { normalizePath } from '../../lib/helpers/path'
21+
import { setPreferredWorktreePath } from '../../lib/worktree-preferences'
2122

2223
interface IWorktreeDropdownProps {
2324
readonly dispatcher: Dispatcher
@@ -54,6 +55,13 @@ export class WorktreeDropdown extends React.Component<
5455

5556
dispatcher.closeFoldout(FoldoutType.Worktree)
5657

58+
const { allWorktrees } = this.props.repositoryState.worktreesState
59+
const mainWorktree = allWorktrees.find(wt => wt.type === 'main')
60+
61+
if (mainWorktree) {
62+
setPreferredWorktreePath(mainWorktree.path, worktree.path)
63+
}
64+
5765
const existingRepo = repositories.find(
5866
r => r instanceof Repository && normalizePath(r.path) === worktreePath
5967
)

app/src/ui/worktrees/delete-worktree-dialog.tsx

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ import { Ref } from '../lib/ref'
88
import { OkCancelButtonGroup } from '../dialog/ok-cancel-button-group'
99
import { removeWorktree, getMainWorktreePath } from '../../lib/git/worktree'
1010
import { normalizePath } from '../../lib/helpers/path'
11+
import {
12+
getPreferredWorktreePath,
13+
clearPreferredWorktreePath,
14+
} from '../../lib/worktree-preferences'
1115

1216
interface IDeleteWorktreeDialogProps {
1317
readonly repository: Repository
@@ -66,16 +70,19 @@ export class DeleteWorktreeDialog extends React.Component<
6670
const isDeletingCurrentWorktree =
6771
normalizePath(repository.path) === normalizePath(worktreePath)
6872

73+
const mainPathForCleanup = await getMainWorktreePath(repository)
74+
6975
try {
7076
if (isDeletingCurrentWorktree) {
7177
// When deleting the currently selected worktree, we must switch away
7278
// first. Otherwise git runs from the directory being deleted and the
7379
// app is left pointing at a non-existent path.
74-
const mainPath = await getMainWorktreePath(repository)
75-
if (mainPath === null) {
80+
if (mainPathForCleanup === null) {
7681
throw new Error('Could not find main worktree')
7782
}
7883

84+
const mainPath = mainPathForCleanup
85+
7986
const addedRepos = await dispatcher.addRepositories(
8087
[mainPath],
8188
repository.login
@@ -90,13 +97,20 @@ export class DeleteWorktreeDialog extends React.Component<
9097
await dispatcher.removeRepository(repository, false)
9198
} else {
9299
await removeWorktree(repository, worktreePath)
100+
await dispatcher.refreshRepository(repository)
93101
}
94102
} catch (e) {
95103
dispatcher.postError(e)
96104
this.setState({ isDeleting: false })
97105
return
98106
}
99107

108+
const resolvedMainPath = mainPathForCleanup ?? repository.path
109+
const preferred = getPreferredWorktreePath(resolvedMainPath)
110+
if (preferred && normalizePath(preferred) === normalizePath(worktreePath)) {
111+
clearPreferredWorktreePath(resolvedMainPath)
112+
}
113+
100114
this.props.onDismissed()
101115
}
102116
}

0 commit comments

Comments
 (0)