@@ -32,7 +32,11 @@ struct BookmarkManagerView: View {
3232 }
3333
3434 private var localOnlyCount : Int {
35- bookmarks. filter { !$0. isTrackingRemote && !$0. isDeleted } . count
35+ bookmarks. filter { $0. hasLocalTarget && !$0. isTrackingRemote && !$0. isDeleted } . count
36+ }
37+
38+ private var remoteOnlyCount : Int {
39+ bookmarks. filter { !$0. hasLocalTarget } . count
3640 }
3741
3842 var body : some View {
@@ -81,6 +85,9 @@ struct BookmarkManagerView: View {
8185 if localOnlyCount > 0 {
8286 statBadge ( " \( localOnlyCount) local-only " , color: . secondary)
8387 }
88+ if remoteOnlyCount > 0 {
89+ statBadge ( " \( remoteOnlyCount) remote-only " , color: . blue)
90+ }
8491 Spacer ( )
8592 Toggle ( " Show deleted " , isOn: $showDeleted)
8693 . jayjayFont ( 11 )
@@ -102,6 +109,14 @@ struct BookmarkManagerView: View {
102109 . background ( Color . primary. opacity ( 0.02 ) )
103110 }
104111
112+ /// Untracked remotes only resolve as `name@remote` in revsets.
113+ private func revsetSymbol( for bookmark: BookmarkInfo ) -> String {
114+ if !bookmark. hasLocalTarget, let remote = bookmark. availableRemotes. first {
115+ return " \( bookmark. name) @ \( remote) "
116+ }
117+ return bookmark. name
118+ }
119+
105120 private func statBadge( _ text: String , color: Color ) -> some View {
106121 Text ( text)
107122 . jayjayFont ( 10 , weight: . semibold)
@@ -118,15 +133,18 @@ struct BookmarkManagerView: View {
118133 ForEach ( filteredBookmarks, id: \. name) { bookmark in
119134 BookmarkManagerRow (
120135 bookmark: bookmark,
121- onFilter: { onFilter ( bookmark. name ) } ,
136+ onFilter: { onFilter ( revsetSymbol ( for : bookmark) ) } ,
122137 onDelete: { actions? . deleteBookmark ( name: bookmark. name) } ,
123138 onForget: { actions? . deleteBookmark ( name: bookmark. name) } ,
124139 onPush: { actions? . gitPush ( bookmark: bookmark. name) } ,
125140 onResolve: {
126141 try ? repo? . moveBookmark ( name: bookmark. name, toRev: " @- " )
127142 actions? . gitFetch ( ) // refresh
128143 } ,
129- onOpenPR: { actions? . openPR ( bookmark: bookmark. name) }
144+ onOpenPR: { actions? . openPR ( bookmark: bookmark. name) } ,
145+ onTrack: { remote in
146+ actions? . trackBookmark ( name: bookmark. name, remote: remote)
147+ }
130148 )
131149 }
132150 }
@@ -144,17 +162,25 @@ private struct BookmarkManagerRow: View {
144162 let onPush : ( ) -> Void
145163 let onResolve : ( ) -> Void
146164 let onOpenPR : ( ) -> Void
165+ let onTrack : ( String ) -> Void
147166
148167 private var canOpenPR : Bool {
149168 bookmark. isTrackingRemote && !bookmark. isDeleted && !isTrunkBookmark( bookmark. name)
150169 }
151170
171+ private var remoteSuffix : String {
172+ guard !bookmark. hasLocalTarget, let first = bookmark. availableRemotes. first else {
173+ return " "
174+ }
175+ return " @ \( first) "
176+ }
177+
152178 var body : some View {
153179 HStack ( spacing: 10 ) {
154180 statusIcon
155181 VStack ( alignment: . leading, spacing: 2 ) {
156182 HStack ( spacing: 6 ) {
157- Text ( bookmark. name)
183+ Text ( bookmark. name + remoteSuffix )
158184 . jayjayFont ( 13 , weight: . medium, design: . monospaced)
159185 . lineLimit ( 1 )
160186 if bookmark. isDeleted {
@@ -163,7 +189,9 @@ private struct BookmarkManagerRow: View {
163189 if bookmark. isConflicted {
164190 badge ( " conflicted " , color: . orange)
165191 }
166- if !bookmark. isTrackingRemote, !bookmark. isDeleted {
192+ if !bookmark. hasLocalTarget {
193+ badge ( " remote-only " , color: . blue)
194+ } else if !bookmark. isTrackingRemote, !bookmark. isDeleted {
167195 badge ( " local " , color: . secondary)
168196 }
169197 }
@@ -200,7 +228,13 @@ private struct BookmarkManagerRow: View {
200228 Label ( " Resolve conflict (set to @) " , systemImage: " arrow.triangle.merge " )
201229 }
202230 }
203- if bookmark. isTrackingRemote, !bookmark. isDeleted {
231+ if !bookmark. hasLocalTarget {
232+ ForEach ( bookmark. availableRemotes, id: \. self) { remote in
233+ Button { onTrack ( remote) } label: {
234+ Label ( " Track \( bookmark. name) @ \( remote) " , systemImage: " arrow.down.circle " )
235+ }
236+ }
237+ } else if bookmark. isTrackingRemote, !bookmark. isDeleted {
204238 Button { onPush ( ) } label: {
205239 Label ( " Push " , systemImage: " arrow.up.circle " )
206240 }
@@ -210,14 +244,16 @@ private struct BookmarkManagerRow: View {
210244 Label ( " Pull Request on GitHub " , systemImage: " arrow.up.right.square " )
211245 }
212246 }
213- Divider ( )
214- if bookmark. isDeleted {
215- Button { onForget ( ) } label: {
216- Label ( " Forget (remove from jj) " , systemImage: " bookmark.slash " )
217- }
218- } else {
219- Button ( role: . destructive) { onDelete ( ) } label: {
220- Label ( " Delete " , systemImage: " trash " )
247+ if bookmark. hasLocalTarget {
248+ Divider ( )
249+ if bookmark. isDeleted {
250+ Button { onForget ( ) } label: {
251+ Label ( " Forget (remove from jj) " , systemImage: " bookmark.slash " )
252+ }
253+ } else {
254+ Button ( role: . destructive) { onDelete ( ) } label: {
255+ Label ( " Delete " , systemImage: " trash " )
256+ }
221257 }
222258 }
223259 }
@@ -231,6 +267,9 @@ private struct BookmarkManagerRow: View {
231267 } else if bookmark. isConflicted {
232268 Image ( systemName: " exclamationmark.triangle.fill " )
233269 . foregroundStyle ( . orange)
270+ } else if !bookmark. hasLocalTarget {
271+ Image ( systemName: " cloud " )
272+ . foregroundStyle ( . blue)
234273 } else if bookmark. isTrackingRemote {
235274 Image ( systemName: " cloud.fill " )
236275 . foregroundStyle ( . blue)
0 commit comments