fix: clarify sync button and result toasts

Show sync label when no remote changes are known
Report pulled file counts in sync success toasts
Count pulled files correctly in VS Code sync
This commit is contained in:
Bohdan Triapitsyn
2026-05-05 23:53:21 +03:00
parent 93267927ff
commit 7b66bca2c5
9 changed files with 61 additions and 8 deletions
+22 -2
View File
@@ -948,6 +948,8 @@ export const GitView: React.FC = () => {
if (!remote) {
throw new Error('No remote available for sync');
}
let pulledFileCount = 0;
let pushedChanges = false;
await git.gitFetch(currentDirectory, { remote: remote.name });
const afterFetch = await git.getGitStatus(currentDirectory);
@@ -956,14 +958,32 @@ export const GitView: React.FC = () => {
toast.error(t('gitView.toast.commitOrStashBeforeSync'));
return;
}
await git.gitPull(currentDirectory, getPullOptions(remote));
const pullResult = await git.gitPull(currentDirectory, getPullOptions(remote));
pulledFileCount = pullResult.files.length;
}
const afterPull = await git.getGitStatus(currentDirectory);
if ((afterPull.ahead ?? 0) > 0) {
await git.gitPush(currentDirectory);
pushedChanges = true;
}
if (pulledFileCount > 0 && pushedChanges) {
toast.success(
pulledFileCount === 1
? t('gitView.toast.syncedPulledSingleAndPushed', { count: pulledFileCount, name: remote.name })
: t('gitView.toast.syncedPulledPluralAndPushed', { count: pulledFileCount, name: remote.name })
);
} else if (pulledFileCount > 0) {
toast.success(
pulledFileCount === 1
? t('gitView.toast.pulledFilesSingle', { count: pulledFileCount, name: remote.name })
: t('gitView.toast.pulledFilesPlural', { count: pulledFileCount, name: remote.name })
);
} else if (pushedChanges) {
toast.success(t('gitView.toast.pushedToUpstream'));
} else {
toast.success(t('gitView.toast.syncedChanges'));
}
toast.success(t('gitView.toast.syncedChanges'));
}
await refreshStatusAndBranches(false);
@@ -54,11 +54,16 @@ export const SyncActions: React.FC<SyncActionsProps> = ({
const blocksRebaseSync = behindCount > 0 && hasUncommittedChanges;
const isPrimaryDisabled = disabled || syncAction !== null || isRemovingRemote || !trackingRemote || blocksRebaseSync;
const isDropdownDisabled = disabled || syncAction !== null || isRemovingRemote || remotes.length === 0;
const countsLabel = t('gitView.sync.syncCounts', { ahead: aheadCount, behind: behindCount });
const hasKnownSyncWork = aheadCount > 0 || behindCount > 0;
const primaryLabel = hasKnownSyncWork
? t('gitView.sync.syncCounts', { ahead: aheadCount, behind: behindCount })
: t('gitView.sync.sync');
const tooltipLabel = blocksRebaseSync
? t('gitView.sync.commitOrStashTooltip')
: trackingRemote
? t('gitView.sync.syncChangesTooltip', { ahead: aheadCount, behind: behindCount })
? hasKnownSyncWork
? t('gitView.sync.syncChangesTooltip', { ahead: aheadCount, behind: behindCount })
: t('gitView.sync.syncChanges')
: t('gitView.sync.noRemoteTooltip');
const handleSync = () => {
@@ -87,7 +92,7 @@ export const SyncActions: React.FC<SyncActionsProps> = ({
) : (
<RiRefreshLine className="size-4" />
)}
<span className="whitespace-nowrap tabular-nums">{countsLabel}</span>
<span className="whitespace-nowrap tabular-nums">{primaryLabel}</span>
</button>
</TooltipTrigger>
<TooltipContent sideOffset={8}>{tooltipLabel}</TooltipContent>
+3
View File
@@ -557,6 +557,7 @@ export const dict = {
'gitView.sync.push': 'Push',
'gitView.sync.pushTooltip': 'Push changes',
'gitView.sync.pushTooltipAhead': 'Push changes ({count} ahead)',
'gitView.sync.sync': 'sync',
'gitView.sync.syncChanges': 'Sync Changes',
'gitView.sync.syncChangesTooltip': 'Sync Changes ({behind} down, {ahead} up)',
'gitView.sync.syncChangesWithCounts': 'Sync Changes {behind}↓ {ahead}↑',
@@ -705,6 +706,8 @@ export const dict = {
'gitView.toast.pulledFilesSingle': 'Pulled {count} file from {name}',
'gitView.toast.pushedToUpstream': 'Pushed to upstream',
'gitView.toast.commitOrStashBeforeSync': 'Commit or stash your changes before syncing',
'gitView.toast.syncedPulledPluralAndPushed': 'Pulled {count} files from {name} and pushed to upstream',
'gitView.toast.syncedPulledSingleAndPushed': 'Pulled {count} file from {name} and pushed to upstream',
'gitView.toast.syncedChanges': 'Synced changes',
'gitView.toast.rebaseAborted': 'Rebase aborted',
'gitView.toast.rebaseConflictsDetected': 'Rebase conflicts detected',
+3
View File
@@ -558,6 +558,7 @@ export const dict: Record<I18nKey, string> = {
"gitView.sync.push": "Push",
"gitView.sync.pushTooltip": "Hacer push",
"gitView.sync.pushTooltipAhead": "Hacer push ({count} por delante)",
"gitView.sync.sync": "sync",
"gitView.sync.syncChanges": "Sync Changes",
"gitView.sync.syncChangesTooltip": "Sync Changes ({behind} abajo, {ahead} arriba)",
"gitView.sync.syncChangesWithCounts": "Sync Changes {behind}↓ {ahead}↑",
@@ -706,6 +707,8 @@ export const dict: Record<I18nKey, string> = {
"gitView.toast.pulledFilesSingle": "Se trajo {count} archivo de {name}",
"gitView.toast.pushedToUpstream": "Enviado al upstream",
"gitView.toast.commitOrStashBeforeSync": "Haz commit o stash de tus cambios antes de sincronizar",
"gitView.toast.syncedPulledPluralAndPushed": "Se trajeron {count} archivos de {name} y se envió al upstream",
"gitView.toast.syncedPulledSingleAndPushed": "Se trajo {count} archivo de {name} y se envió al upstream",
"gitView.toast.syncedChanges": "Cambios sincronizados",
"gitView.toast.rebaseAborted": "Rebase abortado",
"gitView.toast.rebaseConflictsDetected": "Se detectaron conflictos al hacer rebase",
+3
View File
@@ -558,6 +558,7 @@ export const dict: Record<I18nKey, string> = {
'gitView.sync.push': '푸시',
'gitView.sync.pushTooltip': '변경 사항 푸시',
'gitView.sync.pushTooltipAhead': '변경 사항 푸시({count}개 앞섬)',
'gitView.sync.sync': 'sync',
'gitView.sync.syncChanges': 'Sync Changes',
'gitView.sync.syncChangesTooltip': 'Sync Changes({behind}개 내려받기, {ahead}개 올리기)',
'gitView.sync.syncChangesWithCounts': 'Sync Changes {behind}↓ {ahead}↑',
@@ -706,6 +707,8 @@ export const dict: Record<I18nKey, string> = {
'gitView.toast.pulledFilesSingle': '{name}에서 파일 {count}개를 풀했습니다',
'gitView.toast.pushedToUpstream': '업스트림에 푸시했습니다',
'gitView.toast.commitOrStashBeforeSync': '동기화하기 전에 변경 사항을 커밋하거나 stash하세요',
'gitView.toast.syncedPulledPluralAndPushed': '{name}에서 파일 {count}개를 풀하고 업스트림에 푸시했습니다',
'gitView.toast.syncedPulledSingleAndPushed': '{name}에서 파일 {count}개를 풀하고 업스트림에 푸시했습니다',
'gitView.toast.syncedChanges': '변경 사항을 동기화했습니다',
'gitView.toast.rebaseAborted': 'rebase 중단됨',
'gitView.toast.rebaseConflictsDetected': '리베이스 충돌이 감지되었습니다',
@@ -558,6 +558,7 @@ export const dict: Record<I18nKey, string> = {
"gitView.sync.push": "Push",
"gitView.sync.pushTooltip": "Fazer push",
"gitView.sync.pushTooltipAhead": "Fazer push ({count} por delante)",
"gitView.sync.sync": "sync",
"gitView.sync.syncChanges": "Sync Changes",
"gitView.sync.syncChangesTooltip": "Sync Changes ({behind} abaixo, {ahead} acima)",
"gitView.sync.syncChangesWithCounts": "Sync Changes {behind}↓ {ahead}↑",
@@ -706,6 +707,8 @@ export const dict: Record<I18nKey, string> = {
"gitView.toast.pulledFilesSingle": "Se trajo {count} arquivo de {name}",
"gitView.toast.pushedToUpstream": "Enviado ao upstream",
"gitView.toast.commitOrStashBeforeSync": "Faça commit ou stash das alterações antes de sincronizar",
"gitView.toast.syncedPulledPluralAndPushed": "Foram trazidos {count} arquivos de {name} e enviados ao upstream",
"gitView.toast.syncedPulledSingleAndPushed": "Foi trazido {count} arquivo de {name} e enviado ao upstream",
"gitView.toast.syncedChanges": "Alterações sincronizadas",
"gitView.toast.rebaseAborted": "Rebase abortado",
"gitView.toast.rebaseConflictsDetected": "Se detectaron conflitos ao hacer rebase",
+3
View File
@@ -558,6 +558,7 @@ export const dict: Record<I18nKey, string> = {
"gitView.sync.push": "Push",
"gitView.sync.pushTooltip": "Push змін",
"gitView.sync.pushTooltipAhead": "Push змін ({count} попереду)",
"gitView.sync.sync": "sync",
"gitView.sync.syncChanges": "Sync Changes",
"gitView.sync.syncChangesTooltip": "Sync Changes ({behind} вниз, {ahead} вгору)",
"gitView.sync.syncChangesWithCounts": "Sync Changes {behind}↓ {ahead}↑",
@@ -706,6 +707,8 @@ export const dict: Record<I18nKey, string> = {
"gitView.toast.pulledFilesSingle": "Отримано файл: {count} з {name}",
"gitView.toast.pushedToUpstream": "Надіслано в upstream",
"gitView.toast.commitOrStashBeforeSync": "Закомітьте або сховайте зміни перед синхронізацією",
"gitView.toast.syncedPulledPluralAndPushed": "Отримано файлів: {count} з {name} і надіслано в upstream",
"gitView.toast.syncedPulledSingleAndPushed": "Отримано файл: {count} з {name} і надіслано в upstream",
"gitView.toast.syncedChanges": "Зміни синхронізовано",
"gitView.toast.rebaseAborted": "Перебазування перервано",
"gitView.toast.rebaseConflictsDetected": "Виявлено конфлікти перебазування",
@@ -558,6 +558,7 @@ export const dict: Record<I18nKey, string> = {
'gitView.sync.push': '推送',
'gitView.sync.pushTooltip': '推送更改',
'gitView.sync.pushTooltipAhead': '推送更改(领先 {count}',
'gitView.sync.sync': 'sync',
'gitView.sync.syncChanges': 'Sync Changes',
'gitView.sync.syncChangesTooltip': 'Sync Changes{behind} 下,{ahead} 上)',
'gitView.sync.syncChangesWithCounts': 'Sync Changes {behind}↓ {ahead}↑',
@@ -706,6 +707,8 @@ export const dict: Record<I18nKey, string> = {
'gitView.toast.pulledFilesSingle': '已从 {name} 拉取 {count} 个文件',
'gitView.toast.pushedToUpstream': '已推送到上游',
'gitView.toast.commitOrStashBeforeSync': '同步前请先提交或储藏你的更改',
'gitView.toast.syncedPulledPluralAndPushed': '已从 {name} 拉取 {count} 个文件并推送到上游',
'gitView.toast.syncedPulledSingleAndPushed': '已从 {name} 拉取 {count} 个文件并推送到上游',
'gitView.toast.syncedChanges': '已同步更改',
'gitView.toast.rebaseAborted': '变基已中止',
'gitView.toast.rebaseConflictsDetected': '检测到变基冲突',
+13 -3
View File
@@ -2380,6 +2380,7 @@ export async function gitPull(
}
// Fallback to raw git
const beforeHead = await execGit(['rev-parse', 'HEAD'], directory);
const args = ['pull'];
if (options?.rebase === true) args.push('--rebase');
if (options?.remote) args.push(options.remote);
@@ -2389,11 +2390,20 @@ export async function gitPull(
if (result.exitCode !== 0) {
throw new Error(result.stderr.trim() || result.stdout.trim() || 'Failed to pull from remote');
}
const afterHead = await execGit(['rev-parse', 'HEAD'], directory);
const before = beforeHead.exitCode === 0 ? beforeHead.stdout.trim() : '';
const after = afterHead.exitCode === 0 ? afterHead.stdout.trim() : '';
const changedFiles = before && after && before !== after
? await execGit(['diff', '--name-only', before, after], directory)
: { stdout: '', stderr: '', exitCode: 0 };
const files = changedFiles.exitCode === 0
? changedFiles.stdout.split('\n').map((line) => line.trim()).filter(Boolean)
: [];
return {
success: result.exitCode === 0,
summary: { changes: 0, insertions: 0, deletions: 0 },
files: [],
summary: { changes: files.length, insertions: 0, deletions: 0 },
files,
insertions: 0,
deletions: 0,
};