mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-08-28 05:27:24 +00:00
Fix EXT-553: Remove percentage-based progress tracking for worktree file copying (#10905)
* Fix EXT-553: Remove percentage-based progress tracking for worktree file copying - Removed totalBytes from CopyProgress interface - Removed Math.min() clamping that caused stuck-at-100% issue - Changed UI from progress bar to spinner with activity indicator - Shows 'item — X MB copied' instead of percentage - Updated all 18 locale files - Uses native cp with polling (no new dependencies) * fix: translate copyingProgress text in all 17 non-English locale files --------- Co-authored-by: Roo Code <roomote@roocode.com>
This commit is contained in:
parent
9d65772d24
commit
3ab1d08159
22 changed files with 48 additions and 77 deletions
|
|
@ -265,7 +265,7 @@ describe("WorktreeIncludeService", () => {
|
|||
expect(result).toContain("node_modules")
|
||||
})
|
||||
|
||||
it("should call progress callback with size-based progress", async () => {
|
||||
it("should call progress callback with bytesCopied progress", async () => {
|
||||
// Set up files to copy
|
||||
await fs.writeFile(path.join(sourceDir, ".worktreeinclude"), "node_modules\n.env.local")
|
||||
await fs.writeFile(path.join(sourceDir, ".gitignore"), "node_modules\n.env.local")
|
||||
|
|
@ -273,22 +273,20 @@ describe("WorktreeIncludeService", () => {
|
|||
await fs.writeFile(path.join(sourceDir, "node_modules", "test.txt"), "test")
|
||||
await fs.writeFile(path.join(sourceDir, ".env.local"), "LOCAL_VAR=value")
|
||||
|
||||
const progressCalls: Array<{ bytesCopied: number; totalBytes: number; itemName: string }> = []
|
||||
const onProgress = vi.fn((progress: { bytesCopied: number; totalBytes: number; itemName: string }) => {
|
||||
const progressCalls: Array<{ bytesCopied: number; itemName: string }> = []
|
||||
const onProgress = vi.fn((progress: { bytesCopied: number; itemName: string }) => {
|
||||
progressCalls.push({ ...progress })
|
||||
})
|
||||
|
||||
await service.copyWorktreeIncludeFiles(sourceDir, targetDir, onProgress)
|
||||
|
||||
// Should be called multiple times (initial + after each copy + during polling)
|
||||
// Should be called multiple times (initial + after each copy)
|
||||
expect(onProgress).toHaveBeenCalled()
|
||||
|
||||
// All calls should have totalBytes > 0 (since we have files)
|
||||
expect(progressCalls.every((p) => p.totalBytes > 0)).toBe(true)
|
||||
|
||||
// Final call should have bytesCopied === totalBytes (complete)
|
||||
// bytesCopied should increase over time
|
||||
expect(progressCalls.length).toBeGreaterThan(0)
|
||||
const finalCall = progressCalls[progressCalls.length - 1]
|
||||
expect(finalCall?.bytesCopied).toBe(finalCall?.totalBytes)
|
||||
expect(finalCall?.bytesCopied).toBeGreaterThan(0)
|
||||
|
||||
// Each call should have an item name
|
||||
expect(progressCalls.every((p) => typeof p.itemName === "string")).toBe(true)
|
||||
|
|
|
|||
|
|
@ -15,13 +15,12 @@ import ignore, { type Ignore } from "ignore"
|
|||
import type { WorktreeIncludeStatus } from "./types.js"
|
||||
|
||||
/**
|
||||
* Progress info for size-based copy tracking.
|
||||
* Progress info for copy tracking.
|
||||
* Shows activity without trying to predict total size (which is inaccurate).
|
||||
*/
|
||||
export interface CopyProgress {
|
||||
/** Current bytes copied */
|
||||
bytesCopied: number
|
||||
/** Total bytes to copy */
|
||||
totalBytes: number
|
||||
/** Name of current item being copied */
|
||||
itemName: string
|
||||
}
|
||||
|
|
@ -164,26 +163,16 @@ export class WorktreeIncludeService {
|
|||
return []
|
||||
}
|
||||
|
||||
// Calculate total size of all items to copy (for accurate progress)
|
||||
const itemSizes = await Promise.all(
|
||||
itemsToCopy.map(async (item) => {
|
||||
const sourcePath = path.join(sourceDir, item)
|
||||
const size = await this.getPathSize(sourcePath)
|
||||
return { item, size }
|
||||
}),
|
||||
)
|
||||
|
||||
const totalBytes = itemSizes.reduce((sum, { size }) => sum + size, 0)
|
||||
let bytesCopied = 0
|
||||
|
||||
// Report initial progress
|
||||
if (onProgress && totalBytes > 0) {
|
||||
onProgress({ bytesCopied: 0, totalBytes, itemName: itemsToCopy[0]! })
|
||||
if (onProgress && itemsToCopy.length > 0) {
|
||||
onProgress({ bytesCopied: 0, itemName: itemsToCopy[0]! })
|
||||
}
|
||||
|
||||
// Copy the items with size-based progress tracking
|
||||
// Copy the items with progress tracking (no total size calculation)
|
||||
const copiedItems: string[] = []
|
||||
for (const { item, size } of itemSizes) {
|
||||
for (const item of itemsToCopy) {
|
||||
const sourcePath = path.join(sourceDir, item)
|
||||
const targetPath = path.join(targetDir, item)
|
||||
|
||||
|
|
@ -191,34 +180,33 @@ export class WorktreeIncludeService {
|
|||
const stats = await fs.stat(sourcePath)
|
||||
|
||||
if (stats.isDirectory()) {
|
||||
// Use native cp for directories with progress polling
|
||||
await this.copyDirectoryWithProgress(
|
||||
// Copy directory with progress tracking
|
||||
bytesCopied = await this.copyDirectoryWithProgress(
|
||||
sourcePath,
|
||||
targetPath,
|
||||
item,
|
||||
bytesCopied,
|
||||
totalBytes,
|
||||
onProgress,
|
||||
)
|
||||
} else {
|
||||
// Report progress before copying
|
||||
onProgress?.({ bytesCopied, totalBytes, itemName: item })
|
||||
onProgress?.({ bytesCopied, itemName: item })
|
||||
|
||||
// Ensure parent directory exists
|
||||
await fs.mkdir(path.dirname(targetPath), { recursive: true })
|
||||
await fs.copyFile(sourcePath, targetPath)
|
||||
|
||||
// Update bytes copied
|
||||
bytesCopied += this.getSizeOnDisk(stats)
|
||||
}
|
||||
|
||||
bytesCopied += size
|
||||
copiedItems.push(item)
|
||||
|
||||
// Report progress after copying
|
||||
onProgress?.({ bytesCopied, totalBytes, itemName: item })
|
||||
onProgress?.({ bytesCopied, itemName: item })
|
||||
} catch (error) {
|
||||
// Log but don't fail on individual copy errors
|
||||
console.error(`Failed to copy ${item}:`, error)
|
||||
// Still count the size as "processed" to avoid progress getting stuck
|
||||
bytesCopied += size
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -302,22 +290,21 @@ export class WorktreeIncludeService {
|
|||
}
|
||||
|
||||
/**
|
||||
* Copy directory with progress polling.
|
||||
* Copy directory with progress polling using native cp command.
|
||||
* Starts native copy and polls target directory size to report progress.
|
||||
* Returns the updated bytesCopied count.
|
||||
*/
|
||||
private async copyDirectoryWithProgress(
|
||||
source: string,
|
||||
target: string,
|
||||
itemName: string,
|
||||
bytesCopiedBefore: number,
|
||||
totalBytes: number,
|
||||
onProgress?: CopyProgressCallback,
|
||||
): Promise<void> {
|
||||
): Promise<number> {
|
||||
// Ensure parent directory exists
|
||||
await fs.mkdir(path.dirname(target), { recursive: true })
|
||||
|
||||
const isWindows = process.platform === "win32"
|
||||
const expectedSize = await this.getPathSize(source)
|
||||
|
||||
// Start the copy process
|
||||
const copyPromise = new Promise<void>((resolve, reject) => {
|
||||
|
|
@ -361,8 +348,7 @@ export class WorktreeIncludeService {
|
|||
const totalCopied = bytesCopiedBefore + currentSize
|
||||
|
||||
onProgress?.({
|
||||
bytesCopied: Math.min(totalCopied, bytesCopiedBefore + expectedSize),
|
||||
totalBytes,
|
||||
bytesCopied: totalCopied,
|
||||
itemName,
|
||||
})
|
||||
|
||||
|
|
@ -380,6 +366,10 @@ export class WorktreeIncludeService {
|
|||
// Wait for final poll iteration to complete
|
||||
await pollPromise.catch(() => {})
|
||||
}
|
||||
|
||||
// Get the final size of the copied directory
|
||||
const finalSize = await this.getPathSize(target)
|
||||
return bytesCopiedBefore + finalSize
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -3387,7 +3387,6 @@ export const webviewMessageHandler = async (
|
|||
provider.postMessageToWebview({
|
||||
type: "worktreeCopyProgress",
|
||||
copyProgressBytesCopied: progress.bytesCopied,
|
||||
copyProgressTotalBytes: progress.totalBytes,
|
||||
copyProgressItemName: progress.itemName,
|
||||
})
|
||||
},
|
||||
|
|
|
|||
|
|
@ -47,7 +47,6 @@ export const CreateWorktreeModal = ({
|
|||
const [error, setError] = useState<string | null>(null)
|
||||
const [copyProgress, setCopyProgress] = useState<{
|
||||
bytesCopied: number
|
||||
totalBytes: number
|
||||
itemName: string
|
||||
} | null>(null)
|
||||
|
||||
|
|
@ -85,7 +84,6 @@ export const CreateWorktreeModal = ({
|
|||
case "worktreeCopyProgress": {
|
||||
setCopyProgress({
|
||||
bytesCopied: message.copyProgressBytesCopied ?? 0,
|
||||
totalBytes: message.copyProgressTotalBytes ?? 0,
|
||||
itemName: message.copyProgressItemName ?? "",
|
||||
})
|
||||
break
|
||||
|
|
@ -226,30 +224,16 @@ export const CreateWorktreeModal = ({
|
|||
{/* Progress section - appears during file copying */}
|
||||
{copyProgress && (
|
||||
<div className="flex flex-col gap-2 px-3 py-3 rounded-lg bg-vscode-editor-background border border-vscode-panel-border">
|
||||
<div className="flex items-center justify-between text-sm">
|
||||
<div className="flex items-center gap-2 text-sm">
|
||||
<span className="codicon codicon-loading codicon-modifier-spin text-vscode-button-background" />
|
||||
<span className="text-vscode-foreground font-medium">
|
||||
{t("worktrees:copyingFiles")}
|
||||
</span>
|
||||
<span className="text-vscode-descriptionForeground">
|
||||
{copyProgress.totalBytes > 0
|
||||
? Math.round((copyProgress.bytesCopied / copyProgress.totalBytes) * 100)
|
||||
: 0}
|
||||
%
|
||||
</span>
|
||||
</div>
|
||||
<div className="w-full h-2 bg-vscode-input-background rounded-full overflow-hidden">
|
||||
<div
|
||||
className="h-full bg-vscode-button-background rounded-full transition-all duration-200"
|
||||
style={{
|
||||
width: `${copyProgress.totalBytes > 0 ? (copyProgress.bytesCopied / copyProgress.totalBytes) * 100 : 0}%`,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="text-xs text-vscode-descriptionForeground truncate">
|
||||
{t("worktrees:copyingProgress", {
|
||||
item: copyProgress.itemName,
|
||||
copied: prettyBytes(copyProgress.bytesCopied),
|
||||
total: prettyBytes(copyProgress.totalBytes),
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
2
webview-ui/src/i18n/locales/ca/worktrees.json
generated
2
webview-ui/src/i18n/locales/ca/worktrees.json
generated
|
|
@ -43,7 +43,7 @@
|
|||
"create": "Crea",
|
||||
"creating": "S'està creant...",
|
||||
"copyingFiles": "S'estan copiant fitxers...",
|
||||
"copyingProgress": "{{item}} — {{copied}} / {{total}}",
|
||||
"copyingProgress": "{{item}} — {{copied}} copiat",
|
||||
"cancel": "Cancel·la",
|
||||
|
||||
"deleteWorktree": "Suprimeix worktree",
|
||||
|
|
|
|||
2
webview-ui/src/i18n/locales/de/worktrees.json
generated
2
webview-ui/src/i18n/locales/de/worktrees.json
generated
|
|
@ -43,7 +43,7 @@
|
|||
"create": "Erstellen",
|
||||
"creating": "Wird erstellt...",
|
||||
"copyingFiles": "Dateien werden kopiert...",
|
||||
"copyingProgress": "{{item}} — {{copied}} / {{total}}",
|
||||
"copyingProgress": "{{item}} — {{copied}} kopiert",
|
||||
"cancel": "Abbrechen",
|
||||
|
||||
"deleteWorktree": "Worktree löschen",
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@
|
|||
"create": "Create",
|
||||
"creating": "Creating...",
|
||||
"copyingFiles": "Copying files...",
|
||||
"copyingProgress": "{{item}} — {{copied}} / {{total}}",
|
||||
"copyingProgress": "{{item}} — {{copied}} copied",
|
||||
"cancel": "Cancel",
|
||||
|
||||
"deleteWorktree": "Delete Worktree",
|
||||
|
|
|
|||
2
webview-ui/src/i18n/locales/es/worktrees.json
generated
2
webview-ui/src/i18n/locales/es/worktrees.json
generated
|
|
@ -43,7 +43,7 @@
|
|||
"create": "Crear",
|
||||
"creating": "Creando...",
|
||||
"copyingFiles": "Copiando archivos...",
|
||||
"copyingProgress": "{{item}} — {{copied}} / {{total}}",
|
||||
"copyingProgress": "{{item}} — {{copied}} copiado",
|
||||
"cancel": "Cancelar",
|
||||
|
||||
"deleteWorktree": "Eliminar Worktree",
|
||||
|
|
|
|||
2
webview-ui/src/i18n/locales/fr/worktrees.json
generated
2
webview-ui/src/i18n/locales/fr/worktrees.json
generated
|
|
@ -43,7 +43,7 @@
|
|||
"create": "Créer",
|
||||
"creating": "Création...",
|
||||
"copyingFiles": "Copie des fichiers...",
|
||||
"copyingProgress": "{{item}} — {{copied}} / {{total}}",
|
||||
"copyingProgress": "{{item}} — {{copied}} copié",
|
||||
"cancel": "Annuler",
|
||||
|
||||
"deleteWorktree": "Supprimer le worktree",
|
||||
|
|
|
|||
2
webview-ui/src/i18n/locales/hi/worktrees.json
generated
2
webview-ui/src/i18n/locales/hi/worktrees.json
generated
|
|
@ -43,7 +43,7 @@
|
|||
"create": "बनाएँ",
|
||||
"creating": "बनाया जा रहा है...",
|
||||
"copyingFiles": "फ़ाइलें कॉपी की जा रही हैं...",
|
||||
"copyingProgress": "{{item}} — {{copied}} / {{total}}",
|
||||
"copyingProgress": "{{item}} — {{copied}} कॉपी किया गया",
|
||||
"cancel": "रद्द करें",
|
||||
|
||||
"deleteWorktree": "Worktree हटाएँ",
|
||||
|
|
|
|||
2
webview-ui/src/i18n/locales/id/worktrees.json
generated
2
webview-ui/src/i18n/locales/id/worktrees.json
generated
|
|
@ -43,7 +43,7 @@
|
|||
"create": "Buat",
|
||||
"creating": "Membuat...",
|
||||
"copyingFiles": "Menyalin file...",
|
||||
"copyingProgress": "{{item}} — {{copied}} / {{total}}",
|
||||
"copyingProgress": "{{item}} — {{copied}} disalin",
|
||||
"cancel": "Batal",
|
||||
|
||||
"deleteWorktree": "Hapus Worktree",
|
||||
|
|
|
|||
2
webview-ui/src/i18n/locales/it/worktrees.json
generated
2
webview-ui/src/i18n/locales/it/worktrees.json
generated
|
|
@ -43,7 +43,7 @@
|
|||
"create": "Crea",
|
||||
"creating": "Creazione...",
|
||||
"copyingFiles": "Copia dei file...",
|
||||
"copyingProgress": "{{item}} — {{copied}} / {{total}}",
|
||||
"copyingProgress": "{{item}} — {{copied}} copiato",
|
||||
"cancel": "Annulla",
|
||||
|
||||
"deleteWorktree": "Elimina Worktree",
|
||||
|
|
|
|||
2
webview-ui/src/i18n/locales/ja/worktrees.json
generated
2
webview-ui/src/i18n/locales/ja/worktrees.json
generated
|
|
@ -43,7 +43,7 @@
|
|||
"create": "作成",
|
||||
"creating": "作成中...",
|
||||
"copyingFiles": "ファイルをコピー中...",
|
||||
"copyingProgress": "{{item}} — {{copied}} / {{total}}",
|
||||
"copyingProgress": "{{item}} — {{copied}} コピー済み",
|
||||
"cancel": "キャンセル",
|
||||
|
||||
"deleteWorktree": "Worktree を削除",
|
||||
|
|
|
|||
2
webview-ui/src/i18n/locales/ko/worktrees.json
generated
2
webview-ui/src/i18n/locales/ko/worktrees.json
generated
|
|
@ -43,7 +43,7 @@
|
|||
"create": "만들기",
|
||||
"creating": "만드는 중...",
|
||||
"copyingFiles": "파일 복사 중...",
|
||||
"copyingProgress": "{{item}} — {{copied}} / {{total}}",
|
||||
"copyingProgress": "{{item}} — {{copied}} 복사됨",
|
||||
"cancel": "취소",
|
||||
|
||||
"deleteWorktree": "Worktree 삭제",
|
||||
|
|
|
|||
2
webview-ui/src/i18n/locales/nl/worktrees.json
generated
2
webview-ui/src/i18n/locales/nl/worktrees.json
generated
|
|
@ -43,7 +43,7 @@
|
|||
"create": "Aanmaken",
|
||||
"creating": "Bezig met aanmaken...",
|
||||
"copyingFiles": "Bestanden kopiëren...",
|
||||
"copyingProgress": "{{item}} — {{copied}} / {{total}}",
|
||||
"copyingProgress": "{{item}} — {{copied}} gekopieerd",
|
||||
"cancel": "Annuleren",
|
||||
|
||||
"deleteWorktree": "Worktree verwijderen",
|
||||
|
|
|
|||
2
webview-ui/src/i18n/locales/pl/worktrees.json
generated
2
webview-ui/src/i18n/locales/pl/worktrees.json
generated
|
|
@ -43,7 +43,7 @@
|
|||
"create": "Utwórz",
|
||||
"creating": "Tworzenie...",
|
||||
"copyingFiles": "Kopiowanie plików...",
|
||||
"copyingProgress": "{{item}} — {{copied}} / {{total}}",
|
||||
"copyingProgress": "{{item}} — {{copied}} skopiowano",
|
||||
"cancel": "Anuluj",
|
||||
|
||||
"deleteWorktree": "Usuń Worktree",
|
||||
|
|
|
|||
2
webview-ui/src/i18n/locales/pt-BR/worktrees.json
generated
2
webview-ui/src/i18n/locales/pt-BR/worktrees.json
generated
|
|
@ -43,7 +43,7 @@
|
|||
"create": "Criar",
|
||||
"creating": "Criando...",
|
||||
"copyingFiles": "Copiando arquivos...",
|
||||
"copyingProgress": "{{item}} — {{copied}} / {{total}}",
|
||||
"copyingProgress": "{{item}} — {{copied}} copiado",
|
||||
"cancel": "Cancelar",
|
||||
|
||||
"deleteWorktree": "Excluir Worktree",
|
||||
|
|
|
|||
2
webview-ui/src/i18n/locales/ru/worktrees.json
generated
2
webview-ui/src/i18n/locales/ru/worktrees.json
generated
|
|
@ -43,7 +43,7 @@
|
|||
"create": "Создать",
|
||||
"creating": "Создание...",
|
||||
"copyingFiles": "Копирование файлов...",
|
||||
"copyingProgress": "{{item}} — {{copied}} / {{total}}",
|
||||
"copyingProgress": "{{item}} — {{copied}} скопировано",
|
||||
"cancel": "Отмена",
|
||||
|
||||
"deleteWorktree": "Удалить Worktree",
|
||||
|
|
|
|||
2
webview-ui/src/i18n/locales/tr/worktrees.json
generated
2
webview-ui/src/i18n/locales/tr/worktrees.json
generated
|
|
@ -43,7 +43,7 @@
|
|||
"create": "Oluştur",
|
||||
"creating": "Oluşturuluyor...",
|
||||
"copyingFiles": "Dosyalar kopyalanıyor...",
|
||||
"copyingProgress": "{{item}} — {{copied}} / {{total}}",
|
||||
"copyingProgress": "{{item}} — {{copied}} kopyalandı",
|
||||
"cancel": "İptal",
|
||||
|
||||
"deleteWorktree": "Worktree'yi sil",
|
||||
|
|
|
|||
2
webview-ui/src/i18n/locales/vi/worktrees.json
generated
2
webview-ui/src/i18n/locales/vi/worktrees.json
generated
|
|
@ -43,7 +43,7 @@
|
|||
"create": "Tạo",
|
||||
"creating": "Đang tạo...",
|
||||
"copyingFiles": "Đang sao chép tệp...",
|
||||
"copyingProgress": "{{item}} — {{copied}} / {{total}}",
|
||||
"copyingProgress": "{{item}} — {{copied}} đã sao chép",
|
||||
"cancel": "Hủy",
|
||||
|
||||
"deleteWorktree": "Xóa Worktree",
|
||||
|
|
|
|||
2
webview-ui/src/i18n/locales/zh-CN/worktrees.json
generated
2
webview-ui/src/i18n/locales/zh-CN/worktrees.json
generated
|
|
@ -43,7 +43,7 @@
|
|||
"create": "创建",
|
||||
"creating": "正在创建...",
|
||||
"copyingFiles": "正在复制文件...",
|
||||
"copyingProgress": "{{item}} — {{copied}} / {{total}}",
|
||||
"copyingProgress": "{{item}} — {{copied}} 已复制",
|
||||
"cancel": "取消",
|
||||
|
||||
"deleteWorktree": "删除 Worktree",
|
||||
|
|
|
|||
2
webview-ui/src/i18n/locales/zh-TW/worktrees.json
generated
2
webview-ui/src/i18n/locales/zh-TW/worktrees.json
generated
|
|
@ -43,7 +43,7 @@
|
|||
"create": "建立",
|
||||
"creating": "正在建立...",
|
||||
"copyingFiles": "正在複製檔案...",
|
||||
"copyingProgress": "{{item}} — {{copied}} / {{total}}",
|
||||
"copyingProgress": "{{item}} — {{copied}} 已複製",
|
||||
"cancel": "取消",
|
||||
|
||||
"deleteWorktree": "刪除 Worktree",
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue