diff --git a/app/src/lib/app-state.ts b/app/src/lib/app-state.ts index 48a2e4ee32f..5fd75226d90 100644 --- a/app/src/lib/app-state.ts +++ b/app/src/lib/app-state.ts @@ -394,6 +394,9 @@ export interface IAppState { /** Whether or not the user will see check marks indicating a line is included in the check in the diff */ readonly showDiffCheckMarks: boolean + /** Whether or not to show the current branch name next to each repository in the repository list */ + readonly showBranchNameInRepoList: boolean + /** * Cached repo rulesets. Used to prevent repeatedly querying the same * rulesets to check their bypass status. diff --git a/app/src/lib/stores/app-store.ts b/app/src/lib/stores/app-store.ts index ed2ceeeab5c..549edb2aafe 100644 --- a/app/src/lib/stores/app-store.ts +++ b/app/src/lib/stores/app-store.ts @@ -480,6 +480,9 @@ export const underlineLinksDefault = true export const showDiffCheckMarksDefault = true export const showDiffCheckMarksKey = 'diff-check-marks-visible' +export const showBranchNameInRepoListDefault = false +export const showBranchNameInRepoListKey = 'show-branch-name-in-repo-list' + const commitMessageGenerationDisclaimerLastSeenKey = 'commit-message-generation-disclaimer-last-seen' @@ -645,6 +648,8 @@ export class AppStore extends TypedBaseStore { private showDiffCheckMarks: boolean = showDiffCheckMarksDefault + private showBranchNameInRepoList: boolean = showBranchNameInRepoListDefault + private cachedRepoRulesets = new Map() private underlineLinks: boolean = underlineLinksDefault @@ -1192,6 +1197,7 @@ export class AppStore extends TypedBaseStore { cachedRepoRulesets: this.cachedRepoRulesets, underlineLinks: this.underlineLinks, showDiffCheckMarks: this.showDiffCheckMarks, + showBranchNameInRepoList: this.showBranchNameInRepoList, updateState: updateStore.state, commitMessageGenerationDisclaimerLastSeen: this.commitMessageGenerationDisclaimerLastSeen, @@ -2595,6 +2601,11 @@ export class AppStore extends TypedBaseStore { showDiffCheckMarksDefault ) + this.showBranchNameInRepoList = getBoolean( + showBranchNameInRepoListKey, + showBranchNameInRepoListDefault + ) + this.commitMessageGenerationDisclaimerLastSeen = getNumber(commitMessageGenerationDisclaimerLastSeenKey) ?? null @@ -3984,6 +3995,7 @@ export class AppStore extends TypedBaseStore { lookup.set(repository.id, { aheadBehind: status.branchAheadBehind || null, changedFilesCount: status.workingDirectory.files.length, + branchName: status.currentBranch, }) } /** @@ -4025,9 +4037,11 @@ export class AppStore extends TypedBaseStore { const existing = lookup.get(repository.id) lookup.set(repository.id, { aheadBehind: aheadBehind, - // We don't need to update changedFilesCount here since it was already - // set when calling `updateSidebarIndicator()` with the status object. + // We don't need to update changedFilesCount or branchName here since + // they were already set when calling `updateSidebarIndicator()` with + // the status object. changedFilesCount: existing?.changedFilesCount ?? 0, + branchName: existing?.branchName, }) this.emitUpdate() } @@ -9113,6 +9127,14 @@ export class AppStore extends TypedBaseStore { } } + public _updateShowBranchNameInRepoList(showBranchNameInRepoList: boolean) { + if (showBranchNameInRepoList !== this.showBranchNameInRepoList) { + this.showBranchNameInRepoList = showBranchNameInRepoList + setBoolean(showBranchNameInRepoListKey, showBranchNameInRepoList) + this.emitUpdate() + } + } + public _updateFileListFilter( repository: Repository, filterUpdate: Partial diff --git a/app/src/models/repository.ts b/app/src/models/repository.ts index 2899925701f..d04e0de8b2f 100644 --- a/app/src/models/repository.ts +++ b/app/src/models/repository.ts @@ -220,6 +220,11 @@ export interface ILocalRepositoryState { * The number of uncommitted changes currently in the repository. */ readonly changedFilesCount: number + /** + * The name of the currently checked out branch, or `undefined` if the + * branch name is not available (e.g. detached HEAD). + */ + readonly branchName?: string } /** diff --git a/app/src/ui/app.tsx b/app/src/ui/app.tsx index da5c21bcfbd..afe29c39354 100644 --- a/app/src/ui/app.tsx +++ b/app/src/ui/app.tsx @@ -1644,6 +1644,7 @@ export class App extends React.Component { onEditGlobalGitConfig={this.editGlobalGitConfig} underlineLinks={this.state.underlineLinks} showDiffCheckMarks={this.state.showDiffCheckMarks} + showBranchNameInRepoList={this.state.showBranchNameInRepoList} /> ) case PopupType.RepositorySettings: { @@ -3027,6 +3028,7 @@ export class App extends React.Component { externalEditorLabel={this.externalEditorLabel} shellLabel={useCustomShell ? undefined : selectedShell} dispatcher={this.props.dispatcher} + showBranchNameInRepoList={this.state.showBranchNameInRepoList} /> ) } diff --git a/app/src/ui/dispatcher/dispatcher.ts b/app/src/ui/dispatcher/dispatcher.ts index f85ff7ebb00..e73a33630b0 100644 --- a/app/src/ui/dispatcher/dispatcher.ts +++ b/app/src/ui/dispatcher/dispatcher.ts @@ -4191,6 +4191,12 @@ export class Dispatcher { return this.appStore._updateShowDiffCheckMarks(diffCheckMarks) } + public setShowBranchNameInRepoList(showBranchNameInRepoList: boolean) { + return this.appStore._updateShowBranchNameInRepoList( + showBranchNameInRepoList + ) + } + public testPruneBranches() { return this.appStore._testPruneBranches() } diff --git a/app/src/ui/preferences/appearance.tsx b/app/src/ui/preferences/appearance.tsx index dff3f590fad..5c1be7f9691 100644 --- a/app/src/ui/preferences/appearance.tsx +++ b/app/src/ui/preferences/appearance.tsx @@ -24,6 +24,8 @@ interface IAppearanceProps { readonly onShowRecentRepositoriesChanged: (show: boolean) => void readonly showWorktrees: boolean readonly onShowWorktreesChanged: (show: boolean) => void + readonly showBranchNameInRepoList: boolean + readonly onShowBranchNameInRepoListChanged: (value: boolean) => void } interface IAppearanceState { @@ -213,6 +215,12 @@ export class Appearance extends React.Component< ) } + private onShowBranchNameInRepoListChanged = ( + event: React.FormEvent + ) => { + this.props.onShowBranchNameInRepoListChanged(event.currentTarget.checked) + } + private renderRepositoryList() { return (
@@ -227,6 +235,15 @@ export class Appearance extends React.Component< } onChange={this.onShowRecentRepositoriesChanged} /> +
) } diff --git a/app/src/ui/preferences/preferences.tsx b/app/src/ui/preferences/preferences.tsx index 29ff91ad63a..14bf6157807 100644 --- a/app/src/ui/preferences/preferences.tsx +++ b/app/src/ui/preferences/preferences.tsx @@ -93,6 +93,7 @@ interface IPreferencesProps { readonly showRecentRepositories: boolean readonly showWorktrees: boolean readonly repositoryIndicatorsEnabled: boolean + readonly showBranchNameInRepoList: boolean readonly hideWindowOnQuit: boolean readonly onEditGlobalGitConfig: () => void readonly underlineLinks: boolean @@ -144,6 +145,7 @@ interface IPreferencesState { */ readonly existingLockFilePath?: string readonly repositoryIndicatorsEnabled: boolean + readonly showBranchNameInRepoList: boolean readonly hideWindowOnQuit: boolean readonly initiallySelectedTheme: ApplicationTheme @@ -219,6 +221,7 @@ export class Preferences extends React.Component< showRecentRepositories: this.props.showRecentRepositories, showWorktrees: this.props.showWorktrees, repositoryIndicatorsEnabled: this.props.repositoryIndicatorsEnabled, + showBranchNameInRepoList: this.props.showBranchNameInRepoList, hideWindowOnQuit: this.props.hideWindowOnQuit, initiallySelectedTheme: this.props.selectedTheme, initiallySelectedTabSize: this.props.selectedTabSize, @@ -552,6 +555,10 @@ export class Preferences extends React.Component< } showWorktrees={this.state.showWorktrees} onShowWorktreesChanged={this.onShowWorktreesChanged} + showBranchNameInRepoList={this.state.showBranchNameInRepoList} + onShowBranchNameInRepoListChanged={ + this.onShowBranchNameInRepoListChanged + } /> ) break @@ -797,6 +804,12 @@ export class Preferences extends React.Component< this.setState({ showDiffCheckMarks }) } + private onShowBranchNameInRepoListChanged = ( + showBranchNameInRepoList: boolean + ) => { + this.setState({ showBranchNameInRepoList }) + } + private onSelectedTabSizeChanged = (tabSize: number) => { this.props.dispatcher.setSelectedTabSize(tabSize) } @@ -1002,6 +1015,8 @@ export class Preferences extends React.Component< dispatcher.setDiffCheckMarksSetting(this.state.showDiffCheckMarks) + dispatcher.setShowBranchNameInRepoList(this.state.showBranchNameInRepoList) + this.props.onDismissed() } diff --git a/app/src/ui/repositories-list/group-repositories.ts b/app/src/ui/repositories-list/group-repositories.ts index 6dc7bfe2915..757f91b7f89 100644 --- a/app/src/ui/repositories-list/group-repositories.ts +++ b/app/src/ui/repositories-list/group-repositories.ts @@ -61,6 +61,7 @@ export interface IRepositoryListItem extends IFilterListItem { readonly needsDisambiguation: boolean readonly aheadBehind: IAheadBehind | null readonly changedFilesCount: number + readonly branchName?: string } const recentRepositoriesThreshold = 7 @@ -182,6 +183,7 @@ const toSortedListItems = ( ((allNames.get(title) ?? 0) > 1 && group.kind === 'recent'), aheadBehind: repoState?.aheadBehind ?? null, changedFilesCount: repoState?.changedFilesCount ?? 0, + branchName: repoState?.branchName, } }) .sort(({ repository: x }, { repository: y }) => diff --git a/app/src/ui/repositories-list/repositories-list.tsx b/app/src/ui/repositories-list/repositories-list.tsx index 9a5f1624e6b..038a9aa47b6 100644 --- a/app/src/ui/repositories-list/repositories-list.tsx +++ b/app/src/ui/repositories-list/repositories-list.tsx @@ -75,6 +75,9 @@ interface IRepositoriesListProps { readonly filterText: string readonly dispatcher: Dispatcher + + /** Whether to show the branch name next to each repository */ + readonly showBranchNameInRepoList: boolean } interface IRepositoriesListState { @@ -165,6 +168,9 @@ export class RepositoriesList extends React.Component< matches={matches} aheadBehind={item.aheadBehind} changedFilesCount={item.changedFilesCount} + branchName={ + this.props.showBranchNameInRepoList ? item.branchName : undefined + } /> ) } @@ -193,6 +199,9 @@ export class RepositoriesList extends React.Component< item: IRepositoryListItem ): JSX.Element | string | null => { const { repository, aheadBehind, changedFilesCount } = item + const branchName = this.props.showBranchNameInRepoList + ? item.branchName + : undefined const gitHubRepo = repository instanceof Repository ? repository.gitHubRepository : null const alias = repository instanceof Repository ? repository.alias : null @@ -217,6 +226,12 @@ export class RepositoriesList extends React.Component<
Path:
{repository.path} + {branchName && ( +
+
Branch:
+ {branchName} +
+ )} {aheadBehindTooltip && (
diff --git a/app/src/ui/repositories-list/repository-list-item.tsx b/app/src/ui/repositories-list/repository-list-item.tsx index 54cbe675bbc..19a17c3bbba 100644 --- a/app/src/ui/repositories-list/repository-list-item.tsx +++ b/app/src/ui/repositories-list/repository-list-item.tsx @@ -27,6 +27,9 @@ interface IRepositoryListItemProps { /** Number of uncommitted changes */ readonly changedFilesCount: number + + /** The name of the current branch, if available */ + readonly branchName?: string } /** A repository item. */ @@ -76,6 +79,13 @@ export class RepositoryListItem extends React.Component< />
+ {this.props.branchName && ( + + + {this.props.branchName} + + )} + {repository instanceof Repository && renderRepoIndicators({ aheadBehind: this.props.aheadBehind, @@ -98,6 +108,7 @@ export class RepositoryListItem extends React.Component< {alias && <> ({alias})}
{repo.path}
+ {this.props.branchName &&
Branch: {this.props.branchName}
} ) } @@ -109,7 +120,8 @@ export class RepositoryListItem extends React.Component< ) { return ( nextProps.repository.id !== this.props.repository.id || - nextProps.matches !== this.props.matches + nextProps.matches !== this.props.matches || + nextProps.branchName !== this.props.branchName ) } else { return true diff --git a/app/styles/_variables.scss b/app/styles/_variables.scss index f25bfe3f886..db530e6479b 100644 --- a/app/styles/_variables.scss +++ b/app/styles/_variables.scss @@ -338,6 +338,7 @@ $overlay-background-color: rgba(0, 0, 0, 0.4); */ --list-item-badge-color: #{$gray-800}; --list-item-badge-background-color: #{$gray-200}; + --branch-pill-background-color: #{$gray-100}; --list-item-selected-badge-color: #{$gray-900}; --list-item-selected-badge-background-color: #{$gray-300}; --list-item-selected-active-badge-color: #{$gray-900}; diff --git a/app/styles/themes/_dark.scss b/app/styles/themes/_dark.scss index aa31c752649..6a7c9b46a97 100644 --- a/app/styles/themes/_dark.scss +++ b/app/styles/themes/_dark.scss @@ -265,6 +265,7 @@ body.theme-dark { */ --list-item-badge-color: var(--text-color); --list-item-badge-background-color: #{$gray-600}; + --branch-pill-background-color: #{$gray-800}; --list-item-selected-badge-color: #{$white}; --list-item-selected-badge-background-color: #{$gray-500}; --list-item-selected-active-badge-color: #{$gray-900}; diff --git a/app/styles/ui/_repository-list.scss b/app/styles/ui/_repository-list.scss index 3b509b81165..5d45f8b938d 100644 --- a/app/styles/ui/_repository-list.scss +++ b/app/styles/ui/_repository-list.scss @@ -48,6 +48,8 @@ .name { // Long repository names truncate and ellipse @include ellipsis; + flex-shrink: 1; + min-width: 0; .prefix { color: var(--text-secondary-color); @@ -65,6 +67,26 @@ .alias { font-style: italic; } + + .branch-name { + color: var(--list-item-badge-color); + background: var(--branch-pill-background-color); + font-size: var(--font-size-sm); + margin-left: var(--spacing-half); + padding: 0 var(--spacing-half); + border-radius: var(--border-radius); + @include ellipsis; + flex-shrink: 1; + min-width: 0; + line-height: 1.6; + + .branch-icon { + height: 12px; + width: 12px; + margin-right: 2px; + vertical-align: text-bottom; + } + } } .filter-list-group-header { @@ -133,11 +155,12 @@ } .list-focus-container { - /** Ahead/behind badge colors when list item is selected but not focused */ + /** Badge colors when list item is selected but not focused */ .list-item.selected { .repository-list-item, .repository-list-item-tooltip { - .ahead-behind { + .ahead-behind, + .branch-name { background: var(--list-item-selected-badge-background-color); color: var(--list-item-selected-badge-color); } @@ -145,11 +168,12 @@ } &.focus-within { - /** Ahead/behind badge colors when list item is selected and focused */ + /** Badge colors when list item is selected and focused */ .list-item.selected { .repository-list-item, .repository-list-item-tooltip { - .ahead-behind { + .ahead-behind, + .branch-name { background: var(--list-item-selected-active-badge-background-color); color: var(--list-item-selected-active-badge-color); }