fix: add Git installation check for checkpoints feature (#3109) (#5920)

Co-authored-by: Daniel Riccio <ricciodaniel98@gmail.com>
This commit is contained in:
Murilo Pires 2025-07-22 22:31:20 -03:00 committed by GitHub
parent 8dcc078d85
commit 0cb76d9ae9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
21 changed files with 159 additions and 23 deletions

View file

@ -6,6 +6,8 @@ import { TelemetryService } from "@roo-code/telemetry"
import { Task } from "../task/Task"
import { getWorkspacePath } from "../../utils/path"
import { checkGitInstalled } from "../../utils/git"
import { t } from "../../i18n"
import { ClineApiReqInfo } from "../../shared/ExtensionMessage"
import { getApiMetrics } from "../../shared/getApiMetrics"
@ -70,6 +72,47 @@ export function getCheckpointService(cline: Task) {
cline.checkpointServiceInitializing = true
// Check if Git is installed before initializing the service
// Note: This is intentionally fire-and-forget to match the original IIFE pattern
// The service is returned immediately while Git check happens asynchronously
checkGitInstallation(cline, service, log, provider)
return service
} catch (err) {
log(`[Task#getCheckpointService] ${err.message}`)
cline.enableCheckpoints = false
return undefined
}
}
async function checkGitInstallation(
cline: Task,
service: RepoPerTaskCheckpointService,
log: (message: string) => void,
provider: any,
) {
try {
const gitInstalled = await checkGitInstalled()
if (!gitInstalled) {
log("[Task#getCheckpointService] Git is not installed, disabling checkpoints")
cline.enableCheckpoints = false
cline.checkpointServiceInitializing = false
// Show user-friendly notification
const selection = await vscode.window.showWarningMessage(
t("common:errors.git_not_installed"),
t("common:buttons.learn_more"),
)
if (selection === t("common:buttons.learn_more")) {
await vscode.env.openExternal(vscode.Uri.parse("https://git-scm.com/downloads"))
}
return
}
// Git is installed, proceed with initialization
service.on("initialize", () => {
log("[Task#getCheckpointService] service initialized")
@ -115,12 +158,11 @@ export function getCheckpointService(cline: Task) {
log(`[Task#getCheckpointService] initShadowGit -> ${err.message}`)
cline.enableCheckpoints = false
})
return service
} catch (err) {
log(`[Task#getCheckpointService] ${err.message}`)
log(`[Task#getCheckpointService] Unexpected error during Git check: ${err.message}`)
console.error("Git check error:", err)
cline.enableCheckpoints = false
return undefined
cline.checkpointServiceInitializing = false
}
}

View file

@ -32,6 +32,7 @@
"could_not_open_file_generic": "No s'ha pogut obrir el fitxer!",
"checkpoint_timeout": "S'ha esgotat el temps en intentar restaurar el punt de control.",
"checkpoint_failed": "Ha fallat la restauració del punt de control.",
"git_not_installed": "Git és necessari per a la funció de punts de control. Si us plau, instal·la Git per activar els punts de control.",
"no_workspace": "Si us plau, obre primer una carpeta de projecte",
"update_support_prompt": "Ha fallat l'actualització del missatge de suport",
"reset_support_prompt": "Ha fallat el restabliment del missatge de suport",
@ -111,7 +112,8 @@
},
"buttons": {
"save": "Desar",
"edit": "Editar"
"edit": "Editar",
"learn_more": "Més informació"
},
"tasks": {
"canceled": "Error de tasca: Ha estat aturada i cancel·lada per l'usuari.",

View file

@ -28,6 +28,7 @@
"could_not_open_file_generic": "Datei konnte nicht geöffnet werden!",
"checkpoint_timeout": "Zeitüberschreitung beim Versuch, den Checkpoint wiederherzustellen.",
"checkpoint_failed": "Fehler beim Wiederherstellen des Checkpoints.",
"git_not_installed": "Git ist für die Checkpoint-Funktion erforderlich. Bitte installiere Git, um Checkpoints zu aktivieren.",
"no_workspace": "Bitte öffne zuerst einen Projektordner",
"update_support_prompt": "Fehler beim Aktualisieren der Support-Nachricht",
"reset_support_prompt": "Fehler beim Zurücksetzen der Support-Nachricht",
@ -107,7 +108,8 @@
},
"buttons": {
"save": "Speichern",
"edit": "Bearbeiten"
"edit": "Bearbeiten",
"learn_more": "Mehr erfahren"
},
"tasks": {
"canceled": "Aufgabenfehler: Die Aufgabe wurde vom Benutzer gestoppt und abgebrochen.",

View file

@ -28,6 +28,7 @@
"could_not_open_file_generic": "Could not open file!",
"checkpoint_timeout": "Timed out when attempting to restore checkpoint.",
"checkpoint_failed": "Failed to restore checkpoint.",
"git_not_installed": "Git is required for the checkpoints feature. Please install Git to enable checkpoints.",
"no_workspace": "Please open a project folder first",
"update_support_prompt": "Failed to update support prompt",
"reset_support_prompt": "Failed to reset support prompt",
@ -107,7 +108,8 @@
},
"buttons": {
"save": "Save",
"edit": "Edit"
"edit": "Edit",
"learn_more": "Learn More"
},
"tasks": {
"canceled": "Task error: It was stopped and canceled by the user.",

View file

@ -28,6 +28,7 @@
"could_not_open_file_generic": "¡No se pudo abrir el archivo!",
"checkpoint_timeout": "Se agotó el tiempo al intentar restaurar el punto de control.",
"checkpoint_failed": "Error al restaurar el punto de control.",
"git_not_installed": "Git es necesario para la función de puntos de control. Por favor, instala Git para activar los puntos de control.",
"no_workspace": "Por favor, abre primero una carpeta de proyecto",
"update_support_prompt": "Error al actualizar el mensaje de soporte",
"reset_support_prompt": "Error al restablecer el mensaje de soporte",
@ -107,7 +108,8 @@
},
"buttons": {
"save": "Guardar",
"edit": "Editar"
"edit": "Editar",
"learn_more": "Más información"
},
"tasks": {
"canceled": "Error de tarea: Fue detenida y cancelada por el usuario.",

View file

@ -28,6 +28,7 @@
"could_not_open_file_generic": "Impossible d'ouvrir le fichier !",
"checkpoint_timeout": "Expiration du délai lors de la tentative de rétablissement du checkpoint.",
"checkpoint_failed": "Échec du rétablissement du checkpoint.",
"git_not_installed": "Git est requis pour la fonctionnalité des points de contrôle. Veuillez installer Git pour activer les points de contrôle.",
"no_workspace": "Veuillez d'abord ouvrir un espace de travail",
"update_support_prompt": "Erreur lors de la mise à jour du prompt de support",
"reset_support_prompt": "Erreur lors de la réinitialisation du prompt de support",
@ -107,7 +108,8 @@
},
"buttons": {
"save": "Enregistrer",
"edit": "Modifier"
"edit": "Modifier",
"learn_more": "En savoir plus"
},
"tasks": {
"canceled": "Erreur de tâche : Elle a été arrêtée et annulée par l'utilisateur.",

View file

@ -28,6 +28,7 @@
"could_not_open_file_generic": "फ़ाइल नहीं खोली जा सकी!",
"checkpoint_timeout": "चेकपॉइंट को पुनर्स्थापित करने का प्रयास करते समय टाइमआउट हो गया।",
"checkpoint_failed": "चेकपॉइंट पुनर्स्थापित करने में विफल।",
"git_not_installed": "चेकपॉइंट सुविधा के लिए Git आवश्यक है। कृपया चेकपॉइंट সক্ষম करने के लिए Git इंस्टॉल करें।",
"no_workspace": "कृपया पहले प्रोजेक्ट फ़ोल्डर खोलें",
"update_support_prompt": "सपोर्ट प्रॉम्प्ट अपडेट करने में विफल",
"reset_support_prompt": "सपोर्ट प्रॉम्प्ट रीसेट करने में विफल",
@ -107,7 +108,8 @@
},
"buttons": {
"save": "सहेजें",
"edit": "संपादित करें"
"edit": "संपादित करें",
"learn_more": "और अधिक जानें"
},
"tasks": {
"canceled": "टास्क त्रुटि: इसे उपयोगकर्ता द्वारा रोका और रद्द किया गया था।",

View file

@ -28,6 +28,7 @@
"could_not_open_file_generic": "Tidak dapat membuka file!",
"checkpoint_timeout": "Timeout saat mencoba memulihkan checkpoint.",
"checkpoint_failed": "Gagal memulihkan checkpoint.",
"git_not_installed": "Git diperlukan untuk fitur checkpoint. Silakan instal Git untuk mengaktifkan checkpoint.",
"no_workspace": "Silakan buka folder proyek terlebih dahulu",
"update_support_prompt": "Gagal memperbarui support prompt",
"reset_support_prompt": "Gagal mereset support prompt",
@ -107,7 +108,8 @@
},
"buttons": {
"save": "Simpan",
"edit": "Edit"
"edit": "Edit",
"learn_more": "Pelajari Lebih Lanjut"
},
"tasks": {
"canceled": "Error tugas: Dihentikan dan dibatalkan oleh pengguna.",

View file

@ -28,6 +28,7 @@
"could_not_open_file_generic": "Impossibile aprire il file!",
"checkpoint_timeout": "Timeout durante il tentativo di ripristinare il checkpoint.",
"checkpoint_failed": "Impossibile ripristinare il checkpoint.",
"git_not_installed": "Git è richiesto per la funzione di checkpoint. Per favore, installa Git per abilitare i checkpoint.",
"no_workspace": "Per favore, apri prima una cartella di progetto",
"update_support_prompt": "Errore durante l'aggiornamento del messaggio di supporto",
"reset_support_prompt": "Errore durante il ripristino del messaggio di supporto",
@ -107,7 +108,8 @@
},
"buttons": {
"save": "Salva",
"edit": "Modifica"
"edit": "Modifica",
"learn_more": "Scopri di più"
},
"tasks": {
"canceled": "Errore attività: È stata interrotta e annullata dall'utente.",

View file

@ -28,6 +28,7 @@
"could_not_open_file_generic": "ファイルを開けませんでした!",
"checkpoint_timeout": "チェックポイントの復元を試みる際にタイムアウトしました。",
"checkpoint_failed": "チェックポイントの復元に失敗しました。",
"git_not_installed": "チェックポイント機能にはGitが必要です。チェックポイントを有効にするにはGitをインストールしてください。",
"no_workspace": "まずプロジェクトフォルダを開いてください",
"update_support_prompt": "サポートメッセージの更新に失敗しました",
"reset_support_prompt": "サポートメッセージのリセットに失敗しました",
@ -107,7 +108,8 @@
},
"buttons": {
"save": "保存",
"edit": "編集"
"edit": "編集",
"learn_more": "詳細"
},
"tasks": {
"canceled": "タスクエラー:ユーザーによって停止およびキャンセルされました。",

View file

@ -28,6 +28,7 @@
"could_not_open_file_generic": "파일을 열 수 없습니다!",
"checkpoint_timeout": "체크포인트 복원을 시도하는 중 시간 초과되었습니다.",
"checkpoint_failed": "체크포인트 복원에 실패했습니다.",
"git_not_installed": "체크포인트 기능을 사용하려면 Git이 필요합니다. 체크포인트를 활성화하려면 Git을 설치하세요.",
"no_workspace": "먼저 프로젝트 폴더를 열어주세요",
"update_support_prompt": "지원 프롬프트 업데이트에 실패했습니다",
"reset_support_prompt": "지원 프롬프트 재설정에 실패했습니다",
@ -107,7 +108,8 @@
},
"buttons": {
"save": "저장",
"edit": "편집"
"edit": "편집",
"learn_more": "더 알아보기"
},
"tasks": {
"canceled": "작업 오류: 사용자에 의해 중지 및 취소되었습니다.",

View file

@ -28,6 +28,7 @@
"could_not_open_file_generic": "Kon bestand niet openen!",
"checkpoint_timeout": "Time-out bij het herstellen van checkpoint.",
"checkpoint_failed": "Herstellen van checkpoint mislukt.",
"git_not_installed": "Git is vereist voor de checkpoint-functie. Installeer Git om checkpoints in te schakelen.",
"no_workspace": "Open eerst een projectmap",
"update_support_prompt": "Bijwerken van ondersteuningsprompt mislukt",
"reset_support_prompt": "Resetten van ondersteuningsprompt mislukt",
@ -107,7 +108,8 @@
},
"buttons": {
"save": "Opslaan",
"edit": "Bewerken"
"edit": "Bewerken",
"learn_more": "Meer informatie"
},
"tasks": {
"canceled": "Taakfout: gestopt en geannuleerd door gebruiker.",

View file

@ -28,6 +28,7 @@
"could_not_open_file_generic": "Nie można otworzyć pliku!",
"checkpoint_timeout": "Upłynął limit czasu podczas próby przywrócenia punktu kontrolnego.",
"checkpoint_failed": "Nie udało się przywrócić punktu kontrolnego.",
"git_not_installed": "Funkcja punktów kontrolnych wymaga oprogramowania Git. Zainstaluj Git, aby włączyć punkty kontrolne.",
"no_workspace": "Najpierw otwórz folder projektu",
"update_support_prompt": "Nie udało się zaktualizować komunikatu wsparcia",
"reset_support_prompt": "Nie udało się zresetować komunikatu wsparcia",
@ -107,7 +108,8 @@
},
"buttons": {
"save": "Zapisz",
"edit": "Edytuj"
"edit": "Edytuj",
"learn_more": "Dowiedz się więcej"
},
"tasks": {
"canceled": "Błąd zadania: Zostało zatrzymane i anulowane przez użytkownika.",

View file

@ -32,6 +32,7 @@
"could_not_open_file_generic": "Não foi possível abrir o arquivo!",
"checkpoint_timeout": "Tempo esgotado ao tentar restaurar o ponto de verificação.",
"checkpoint_failed": "Falha ao restaurar o ponto de verificação.",
"git_not_installed": "O Git é necessário para o recurso de checkpoints. Por favor, instale o Git para habilitar os checkpoints.",
"no_workspace": "Por favor, abra primeiro uma pasta de projeto",
"update_support_prompt": "Falha ao atualizar o prompt de suporte",
"reset_support_prompt": "Falha ao redefinir o prompt de suporte",
@ -111,7 +112,8 @@
},
"buttons": {
"save": "Salvar",
"edit": "Editar"
"edit": "Editar",
"learn_more": "Saiba Mais"
},
"tasks": {
"canceled": "Erro na tarefa: Foi interrompida e cancelada pelo usuário.",

View file

@ -28,6 +28,7 @@
"could_not_open_file_generic": "Не удалось открыть файл!",
"checkpoint_timeout": "Превышено время ожидания при попытке восстановления контрольной точки.",
"checkpoint_failed": "Не удалось восстановить контрольную точку.",
"git_not_installed": "Для функции контрольных точек требуется Git. Пожалуйста, установите Git, чтобы включить контрольные точки.",
"no_workspace": "Пожалуйста, сначала откройте папку проекта",
"update_support_prompt": "Не удалось обновить промпт поддержки",
"reset_support_prompt": "Не удалось сбросить промпт поддержки",
@ -107,7 +108,8 @@
},
"buttons": {
"save": "Сохранить",
"edit": "Редактировать"
"edit": "Редактировать",
"learn_more": "Узнать больше"
},
"tasks": {
"canceled": "Ошибка задачи: Она была остановлена и отменена пользователем.",

View file

@ -28,6 +28,7 @@
"could_not_open_file_generic": "Dosya açılamadı!",
"checkpoint_timeout": "Kontrol noktasını geri yüklemeye çalışırken zaman aşımına uğradı.",
"checkpoint_failed": "Kontrol noktası geri yüklenemedi.",
"git_not_installed": "Kontrol noktaları özelliği için Git gereklidir. Kontrol noktalarını etkinleştirmek için lütfen Git'i yükleyin.",
"no_workspace": "Lütfen önce bir proje klasörü açın",
"update_support_prompt": "Destek istemi güncellenemedi",
"reset_support_prompt": "Destek istemi sıfırlanamadı",
@ -107,7 +108,8 @@
},
"buttons": {
"save": "Kaydet",
"edit": "Düzenle"
"edit": "Düzenle",
"learn_more": "Daha Fazla Bilgi"
},
"tasks": {
"canceled": "Görev hatası: Kullanıcı tarafından durduruldu ve iptal edildi.",

View file

@ -28,6 +28,7 @@
"could_not_open_file_generic": "Không thể mở tệp!",
"checkpoint_timeout": "Đã hết thời gian khi cố gắng khôi phục điểm kiểm tra.",
"checkpoint_failed": "Không thể khôi phục điểm kiểm tra.",
"git_not_installed": "Yêu cầu Git cho tính năng điểm kiểm tra. Vui lòng cài đặt Git để bật điểm kiểm tra.",
"no_workspace": "Vui lòng mở thư mục dự án trước",
"update_support_prompt": "Không thể cập nhật lời nhắc hỗ trợ",
"reset_support_prompt": "Không thể đặt lại lời nhắc hỗ trợ",
@ -107,7 +108,8 @@
},
"buttons": {
"save": "Lưu",
"edit": "Chỉnh sửa"
"edit": "Chỉnh sửa",
"learn_more": "Tìm hiểu thêm"
},
"tasks": {
"canceled": "Lỗi nhiệm vụ: Nó đã bị dừng và hủy bởi người dùng.",

View file

@ -33,6 +33,7 @@
"could_not_open_file_generic": "无法打开文件!",
"checkpoint_timeout": "尝试恢复检查点时超时。",
"checkpoint_failed": "恢复检查点失败。",
"git_not_installed": "存档点功能需要 Git。请安装 Git 以启用存档点。",
"no_workspace": "请先打开项目文件夹",
"update_support_prompt": "更新支持消息失败",
"reset_support_prompt": "重置支持消息失败",
@ -112,7 +113,8 @@
},
"buttons": {
"save": "保存",
"edit": "编辑"
"edit": "编辑",
"learn_more": "了解更多"
},
"tasks": {
"canceled": "任务错误:它已被用户停止并取消。",

View file

@ -28,6 +28,7 @@
"could_not_open_file_generic": "無法開啟檔案!",
"checkpoint_timeout": "嘗試恢復檢查點時超時。",
"checkpoint_failed": "恢復檢查點失敗。",
"git_not_installed": "存檔點功能需要 Git。請安裝 Git 以啟用存檔點。",
"no_workspace": "請先開啟專案資料夾",
"update_support_prompt": "更新支援訊息失敗",
"reset_support_prompt": "重設支援訊息失敗",
@ -107,7 +108,8 @@
},
"buttons": {
"save": "儲存",
"edit": "編輯"
"edit": "編輯",
"learn_more": "了解更多"
},
"tasks": {
"canceled": "工作錯誤:它已被使用者停止並取消。",

View file

@ -4,6 +4,7 @@ import * as fs from "fs"
import * as path from "path"
import {
checkGitInstalled,
searchCommits,
getCommitInfo,
getWorkingState,
@ -83,6 +84,54 @@ describe("git utils", () => {
vitest.clearAllMocks()
})
describe("checkGitInstalled", () => {
it("should return true when git --version succeeds", async () => {
vitest.mocked(exec).mockImplementation((command: string, options: any, callback: any) => {
if (command === "git --version") {
callback(null, { stdout: "git version 2.39.2", stderr: "" })
return {} as any
}
callback(new Error("Unexpected command"))
return {} as any
})
const result = await checkGitInstalled()
expect(result).toBe(true)
expect(vitest.mocked(exec)).toHaveBeenCalledWith("git --version", {}, expect.any(Function))
})
it("should return false when git --version fails", async () => {
vitest.mocked(exec).mockImplementation((command: string, options: any, callback: any) => {
if (command === "git --version") {
callback(new Error("git not found"))
return {} as any
}
callback(new Error("Unexpected command"))
return {} as any
})
const result = await checkGitInstalled()
expect(result).toBe(false)
expect(vitest.mocked(exec)).toHaveBeenCalledWith("git --version", {}, expect.any(Function))
})
it("should handle unexpected errors gracefully", async () => {
vitest.mocked(exec).mockImplementation((command: string, options: any, callback: any) => {
if (command === "git --version") {
// Simulate an unexpected error
callback(new Error("Unexpected system error"))
return {} as any
}
callback(new Error("Unexpected command"))
return {} as any
})
const result = await checkGitInstalled()
expect(result).toBe(false)
expect(vitest.mocked(exec)).toHaveBeenCalledWith("git --version", {}, expect.any(Function))
})
})
describe("searchCommits", () => {
const mockCommitData = [
"abc123def456",

View file

@ -210,7 +210,16 @@ async function checkGitRepo(cwd: string): Promise<boolean> {
}
}
async function checkGitInstalled(): Promise<boolean> {
/**
* Checks if Git is installed on the system by attempting to run git --version
* @returns {Promise<boolean>} True if Git is installed and accessible, false otherwise
* @example
* const isGitInstalled = await checkGitInstalled();
* if (!isGitInstalled) {
* console.log("Git is not installed");
* }
*/
export async function checkGitInstalled(): Promise<boolean> {
try {
await execAsync("git --version")
return true