Keep new worktrees clean when setup changes are uncommitted (#3311)

* fix(worktrees): preserve selected base config

New worktrees were overwriting the selected ref's paseo.json with source-checkout bytes, which could create an accidental revert in an otherwise fresh workspace. Keep committed base config authoritative and warn when locally saved setup changes still need to be committed.

* fix(worktrees): seed config exclusively

* fix(projects): refresh config status on focus
This commit is contained in:
Mohamed Boudra
2026-08-13 12:02:15 +02:00
committed by GitHub
parent 9a7301d9a0
commit c190fed27c
23 changed files with 460 additions and 55 deletions
+3
View File
@@ -2405,6 +2405,9 @@ export const ar: TranslationResources = {
docsTooltip: "راجع المستندات لمزيد من التفاصيل ومتغيرات البيئة المتاحة لهذه الأوامر",
setup: "يثبت",
setupAccessibility: "أوامر إعداد شجرة العمل",
uncommittedTitle: "ثبّت تغييرات paseo.json",
uncommittedDescription:
"تستخدم أشجار العمل الجديدة نص الإعداد البرمجي من الفرع الأساسي الذي تحدده.",
teardown: "هدم",
teardownAccessibility: "أوامر هدم شجرة العمل",
},
+3
View File
@@ -2419,6 +2419,9 @@ export const en = {
"See docs for more details and the environment variables available to these commands",
setup: "Setup",
setupAccessibility: "Worktree setup commands",
uncommittedTitle: "Commit paseo.json changes",
uncommittedDescription:
"New worktrees use the setup script from the base branch you select.",
teardown: "Teardown",
teardownAccessibility: "Worktree teardown commands",
},
+3
View File
@@ -2460,6 +2460,9 @@ export const es: TranslationResources = {
"Consulte los documentos para obtener más detalles y las variables de entorno disponibles para estos comandos.",
setup: "Configuración",
setupAccessibility: "Comandos de configuración del árbol de trabajo",
uncommittedTitle: "Confirma los cambios de paseo.json",
uncommittedDescription:
"Los árboles de trabajo nuevos usan el script de configuración de la rama base que selecciones.",
teardown: "Demoler",
teardownAccessibility: "Comandos de desmontaje del árbol de trabajo",
},
+3
View File
@@ -2466,6 +2466,9 @@ export const fr: TranslationResources = {
"Voir la documentation pour plus de détails et les variables d'environnement disponibles pour ces commandes",
setup: "Installation",
setupAccessibility: "Commandes de configuration de Worktree",
uncommittedTitle: "Validez les modifications de paseo.json",
uncommittedDescription:
"Les nouveaux worktrees utilisent le script de configuration de la branche de base sélectionnée.",
teardown: "Démolir",
teardownAccessibility: "Commandes de démontage de Worktree",
},
+3
View File
@@ -2430,6 +2430,9 @@ export const ja: TranslationResources = {
"これらのコマンドで使用可能な詳細と環境変数についてはドキュメントを参照してください",
setup: "セットアップ",
setupAccessibility: "ワークツリーセットアップコマンド",
uncommittedTitle: "paseo.json の変更をコミットしてください",
uncommittedDescription:
"新しいワークツリーでは、選択したベースブランチのセットアップスクリプトが使われます。",
teardown: "削除時",
teardownAccessibility: "ワークツリー削除時のコマンド",
},
+2
View File
@@ -2422,6 +2422,8 @@ export const ko: TranslationResources = {
docsTooltip: "자세한 내용과 이 명령에 사용할 수 있는 환경 변수는 문서를 참조하세요",
setup: "설정",
setupAccessibility: "워크트리 설정 명령",
uncommittedTitle: "paseo.json 변경 사항을 커밋하세요",
uncommittedDescription: "새 워크트리는 선택한 기본 브랜치의 설정 스크립트를 사용합니다.",
teardown: "정리",
teardownAccessibility: "워크트리 정리 명령",
},
+3
View File
@@ -2445,6 +2445,9 @@ export const ptBR: TranslationResources = {
"Veja a documentação para mais detalhes e as variáveis de ambiente disponíveis para estes comandos",
setup: "Configuração",
setupAccessibility: "Comandos de configuração do worktree",
uncommittedTitle: "Faça commit das alterações no paseo.json",
uncommittedDescription:
"Novos worktrees usam o script de configuração do branch base selecionado.",
teardown: "Desmontagem",
teardownAccessibility: "Comandos de desmontagem do worktree",
},
+3
View File
@@ -2448,6 +2448,9 @@ export const ru: TranslationResources = {
"Дополнительную информацию и переменные среды, доступные для этих команд, см. в документации.",
setup: "Настраивать",
setupAccessibility: "Команды настройки рабочего дерева",
uncommittedTitle: "Закоммитьте изменения paseo.json",
uncommittedDescription:
"Новые рабочие деревья используют сценарий настройки из выбранной базовой ветки.",
teardown: "Срывать",
teardownAccessibility: "Команды разрушения рабочего дерева",
},
+2
View File
@@ -2377,6 +2377,8 @@ export const zhCN: TranslationResources = {
docsTooltip: "查看命令可用的环境变量和更多细节",
setup: "Setup",
setupAccessibility: "Worktree setup 命令",
uncommittedTitle: "提交 paseo.json 更改",
uncommittedDescription: "新工作树使用所选基础分支中的设置脚本。",
teardown: "Teardown",
teardownAccessibility: "Worktree teardown 命令",
},
@@ -3,6 +3,7 @@ import type { TFunction } from "i18next";
import { useTranslation } from "react-i18next";
import { Pressable, Text, TextInput, View } from "react-native";
import { router } from "expo-router";
import { useFocusEffect } from "@react-navigation/native";
import { StyleSheet } from "react-native-unistyles";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { ArrowLeft, MoreVertical, Pencil, Plus } from "lucide-react-native";
@@ -200,6 +201,12 @@ function ProjectSettingsBody({
queryFn: () => client.readProjectConfig(selectedHost.repoRoot),
retry: false,
});
const refetchProjectConfig = readQuery.refetch;
useFocusEffect(
useCallback(() => {
void refetchProjectConfig();
}, [refetchProjectConfig]),
);
const data = readQuery.data;
const supportsCustomIcon = useHostFeature(selectedHost.serverId, "projectCustomIcon");
@@ -240,6 +247,8 @@ function ProjectSettingsBody({
);
const loadedConfig: PaseoConfigRaw | null = data?.ok ? (data.config ?? {}) : null;
const loadedRevision: PaseoConfigRevision | null = data?.ok ? data.revision : null;
const hasUncommittedWorktreeSetupChanges =
data?.ok === true && data.hasUncommittedWorktreeSetupChanges === true;
const readError: ProjectConfigRpcError | null = data && !data.ok ? data.error : null;
const handleReload = useCallback(() => {
@@ -290,6 +299,7 @@ function ProjectSettingsBody({
readQuery,
loadedConfig,
loadedRevision,
hasUncommittedWorktreeSetupChanges,
readError,
selectedHost,
queryKey,
@@ -305,6 +315,7 @@ interface RenderContentInput {
readQuery: ReturnType<typeof useQuery<ReadProjectConfigData>>;
loadedConfig: PaseoConfigRaw | null;
loadedRevision: PaseoConfigRevision | null;
hasUncommittedWorktreeSetupChanges: boolean;
readError: ProjectConfigRpcError | null;
selectedHost: ProjectHostEntry;
queryKey: readonly [string, string, string];
@@ -317,6 +328,7 @@ function renderContent({
readQuery,
loadedConfig,
loadedRevision,
hasUncommittedWorktreeSetupChanges,
readError,
selectedHost,
queryKey,
@@ -358,6 +370,7 @@ function renderContent({
key={formKey}
baseConfig={loadedConfig}
revision={loadedRevision}
hasUncommittedWorktreeSetupChanges={hasUncommittedWorktreeSetupChanges}
repoRoot={selectedHost.repoRoot}
queryKey={queryKey}
client={client}
@@ -438,6 +451,7 @@ function errorToDetail(error: unknown): string | null {
interface ProjectConfigFormProps {
baseConfig: PaseoConfigRaw;
revision: PaseoConfigRevision | null;
hasUncommittedWorktreeSetupChanges: boolean;
repoRoot: string;
queryKey: readonly [string, string, string];
client: DaemonClient;
@@ -447,6 +461,7 @@ interface ProjectConfigFormProps {
function ProjectConfigForm({
baseConfig,
revision,
hasUncommittedWorktreeSetupChanges,
repoRoot,
queryKey,
client,
@@ -479,6 +494,11 @@ function ProjectConfigForm({
revision: result.revision,
requestId: "local-cache",
repoRoot,
...(result.hasUncommittedWorktreeSetupChanges === undefined
? {}
: {
hasUncommittedWorktreeSetupChanges: result.hasUncommittedWorktreeSetupChanges,
}),
});
setWriteError(null);
queryClient.invalidateQueries({ queryKey: ["projects"] });
@@ -660,6 +680,13 @@ function ProjectConfigForm({
testID="worktree-setup-section"
trailing={setupDocsLink}
>
{hasUncommittedWorktreeSetupChanges ? (
<Alert
variant="warning"
title={t("settings.project.worktree.uncommittedTitle")}
description={t("settings.project.worktree.uncommittedDescription")}
/>
) : null}
<SettingsTextAreaCard
testID="worktree-setup-input"
accessibilityLabel={t("settings.project.worktree.setupAccessibility")}