feat(mobile): a commit's files open in the diff screen (#37113)

Tapping a file on the commit page now opens the shared diff screen for
that file in that commit (openDiffForFile kind 'commit'), in today's
Commits tab and inside the hub's Commits page.
This commit is contained in:
Kelvin Amoaba
2026-11-01 19:41:06 +00:00
committed by GitHub
parent 9f416bb1db
commit bf48d6665f
4 changed files with 64 additions and 5 deletions
@@ -34,6 +34,8 @@ type Props = {
commit: MobileGitHistoryItem
bottomInset: number
onBack: () => void
// Opens a file in the shared diff screen; rows stay inert when the host offers none.
onOpenFile?: (filePath: string) => void
}
function splitPath(path: string): { name: string; dir: string } {
@@ -57,7 +59,8 @@ export function MobileCommitDetailPage({
worktreeId,
commit,
bottomInset,
onBack
onBack,
onOpenFile
}: Props) {
useMobileColorScheme()
const t = useMobileTranslation()
@@ -271,19 +274,33 @@ export function MobileCommitDetailPage({
{t('sourceControl.gitHistoryList.noFileChanges', 'No file changes')}
</Text>
) : (
loadedFiles.map((file) => <CommitFileRow key={file.path} file={file} />)
loadedFiles.map((file) => (
<CommitFileRow key={file.path} file={file} onOpen={onOpenFile} />
))
)}
</ScrollView>
</View>
)
}
function CommitFileRow({ file }: { file: MobileGitChangedFile }) {
function CommitFileRow({
file,
onOpen
}: {
file: MobileGitChangedFile
onOpen?: (filePath: string) => void
}) {
useMobileColorScheme()
const status = file.status ?? 'modified'
const { name, dir } = splitPath(file.path)
return (
<View style={styles.fileRow} accessibilityLabel={file.path}>
<Pressable
style={({ pressed }) => [styles.fileRow, pressed && onOpen && styles.fileRowPressed]}
disabled={!onOpen}
onPress={() => onOpen?.(file.path)}
accessibilityRole={onOpen ? 'button' : undefined}
accessibilityLabel={file.path}
>
<Text style={[styles.fileStatus, { color: statusColor(status) }]}>
{STATUS_LABELS[status]}
</Text>
@@ -301,7 +318,7 @@ function CommitFileRow({ file }: { file: MobileGitChangedFile }) {
) : null}
</View>
<CommitLineStat added={file.added ?? 0} removed={file.removed ?? 0} />
</View>
</Pressable>
)
}
@@ -358,6 +375,7 @@ const styles = themedStyleSheet(() => ({
fontFamily: typography.monoFamily,
fontWeight: '600'
},
fileRowPressed: { backgroundColor: colors.bgRaised },
fileMain: { flex: 1, minWidth: 0 },
fileName: { color: colors.textPrimary, fontSize: typography.bodySize },
fileNameDeleted: { textDecorationLine: 'line-through', color: colors.textMuted },
@@ -297,6 +297,35 @@ describe('MobileCommitsScreen', () => {
expect(list?.props.refreshControl.props.refreshing).toBe(false)
})
it("opens a commit's file in the shared diff screen against that commit", async () => {
const openDiffForFile = vi.fn()
const client = createFakeRpcClient()
client.sendRequest = routedSend(() => Promise.resolve(historyResponse('expandable')))
await act(async () => {
renderer = create(
createElement(MobileCommitsScreen, {
client,
connState: 'connected',
worktreeId: 'wt-1',
hostId: 'host-1',
bottomInset: 0,
openDiffForFile
})
)
await settle()
})
await openFirstCommit()
const fileRow = renderer?.root.find(
(node) => String(node.type) === 'Pressable' && node.props.accessibilityLabel === 'src/app.ts'
)
await act(async () => fileRow?.props.onPress())
expect(openDiffForFile).toHaveBeenCalledWith({
kind: 'commit',
filePath: 'src/app.ts',
commitOid: 'commit-1'
})
})
it('wipes commits when the worktree identity changes', async () => {
let historyCalls = 0
const sendRequest = routedSend(() => {
@@ -8,6 +8,7 @@ import type { RpcClient } from '../transport/rpc-client'
import type { ConnectionState } from '../transport/types'
import type { MobileGitHistoryItem } from './git-history-reply-schema'
import { MobileCommitDetailPage } from './MobileCommitDetailPage'
import type { MobileDiffFileTarget } from './use-mobile-source-control-openers'
import { MobileGitHistoryList, type MobileGitHistoryListProps } from './MobileGitHistoryList'
type OpenCommit = (commit: MobileGitHistoryItem) => void
@@ -17,12 +18,14 @@ type Props = Omit<MobileGitHistoryListProps, 'onOpenCommit'> & {
embedded?: boolean
// Set by a parent whose own commit page covers more than this list, e.g. the whole Source Control screen.
onOpenCommit?: OpenCommit
openDiffForFile?: (target: MobileDiffFileTarget) => void
}
// The branch's commits; a commit opens over them unless the parent hosts the commit page itself.
export const MobileCommitsScreen = memo(function MobileCommitsScreen({
embedded = false,
onOpenCommit,
openDiffForFile,
...props
}: Props) {
const list = (open: OpenCommit) => <MobileGitHistoryList {...props} onOpenCommit={open} />
@@ -37,6 +40,7 @@ export const MobileCommitsScreen = memo(function MobileCommitsScreen({
worktreeId={props.worktreeId}
bottomInset={props.bottomInset}
padTop={false}
openDiffForFile={openDiffForFile}
>
{list}
</MobileCommitPageHost>
@@ -58,6 +62,7 @@ export function MobileCommitPageHost({
worktreeId,
bottomInset,
padTop,
openDiffForFile,
children
}: {
client: RpcClient | null
@@ -66,6 +71,7 @@ export function MobileCommitPageHost({
bottomInset: number
// The page covers a screen-top header, so it pads under the status bar unless an ancestor already did.
padTop: boolean
openDiffForFile?: (target: MobileDiffFileTarget) => void
children: (openCommit: OpenCommit) => ReactNode
}) {
const pager = useOptionPager<'commit'>()
@@ -97,6 +103,11 @@ export function MobileCommitPageHost({
commit={commit}
bottomInset={bottomInset}
onBack={close}
onOpenFile={
openDiffForFile
? (filePath) => openDiffForFile({ kind: 'commit', filePath, commitOid: commit.id })
: undefined
}
/>
) : null
return (
@@ -317,6 +317,7 @@ export function MobileSourceControlPanel({
worktreeId={worktreeId}
bottomInset={insets.bottom}
padTop={!embedded}
openDiffForFile={state.openDiffForFile}
>
{(openCommit) => (
<View style={styles.container}>