feat(worktrees): création intelligente (auto) + commit/push/promotion + grille agrandissable
- addWorktree résout la branche par dépôt (branchExists) : checkout si présente, suivi de origin/<b> si seulement remote, sinon création (-b) depuis la branche par défaut. Corrige le 'fatal: invalid reference' sur les groupes hétérogènes ; newBranch -> mode (déprécié, mappé en route).
- Nouvelles opérations git : commit (add -A), push (upstream auto), promotion « en principal » (la branche du worktree devient le checkout principal, sans merge ni conflit, ancienne branche conservée). Routes /worktrees/{commit,push,promote} auditées + GET /repos/:id/branches.
- Web : actions Commit/Push/Promouvoir sur WorktreeCard, sélecteur de branche de base (datalist) dans RepoSection et GroupSessionModal, agrandissement d'un terminal au choix dans TerminalGrid.
- Court-circuite la session de groupe quand aucun worktree n'a pu être créé (fini le masquage par « no resolvable worktree »).
- Tests : git.ts (branchExists/listBranches/commitAll/auto), worktree-manager (commit/promote/branches) ; acceptance P5 étend le scénario worktree de groupe sur branche neuve + commit + promotion.
This commit is contained in:
@@ -21,21 +21,20 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- mode feature : branche commune créée dans chaque repo -->
|
||||
<!-- mode feature : branche commune créée (ou réutilisée si déjà présente) dans chaque repo -->
|
||||
<template v-if="mode === 'feature'">
|
||||
<label class="flex flex-col gap-1 text-xs text-zinc-400">
|
||||
{{ t('crossRepo.branchLabel') }}
|
||||
<input v-model="branch" class="input font-mono" :placeholder="t('crossRepo.branchPlaceholder')" :required="mode === 'feature'" />
|
||||
<span class="text-[11px] text-zinc-500">{{ t('crossRepo.branchHint') }}</span>
|
||||
</label>
|
||||
<label class="flex flex-col gap-1 text-xs text-zinc-400">
|
||||
{{ t('crossRepo.baseRefLabel') }}
|
||||
<input v-model="baseRef" class="input font-mono" list="group-base-branches" :placeholder="t('crossRepo.baseRefPlaceholder')" />
|
||||
<datalist id="group-base-branches">
|
||||
<option v-for="b in baseBranchOptions" :key="b" :value="b" />
|
||||
</datalist>
|
||||
</label>
|
||||
<div class="flex flex-wrap items-end gap-3">
|
||||
<label class="flex items-center gap-1.5 text-xs text-zinc-400">
|
||||
<input v-model="newBranch" type="checkbox" /> {{ t('crossRepo.newBranch') }}
|
||||
</label>
|
||||
<label class="flex flex-col gap-1 text-xs text-zinc-400">
|
||||
{{ t('crossRepo.baseRefLabel') }}
|
||||
<input v-model="baseRef" class="input font-mono" placeholder="HEAD" />
|
||||
</label>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<label class="flex flex-col gap-1 text-xs text-zinc-400">
|
||||
@@ -78,11 +77,12 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue';
|
||||
import { computed, onMounted, ref } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import type { RepoSummary } from '@arboretum/shared';
|
||||
import type { RepoBranchesResponse, RepoSummary } from '@arboretum/shared';
|
||||
import { useGroupsStore, type CrossRepoResult, type GroupFeatureOutcome } from '../stores/groups';
|
||||
import { useWorktreesStore } from '../stores/worktrees';
|
||||
import { useToastsStore } from '../stores/toasts';
|
||||
|
||||
const props = defineProps<{ groupId: string; repos: RepoSummary[] }>();
|
||||
@@ -91,12 +91,29 @@ const emit = defineEmits<{ close: [] }>();
|
||||
const { t } = useI18n();
|
||||
const router = useRouter();
|
||||
const groups = useGroupsStore();
|
||||
const worktrees = useWorktreesStore();
|
||||
const toasts = useToastsStore();
|
||||
|
||||
const mode = ref<'feature' | 'main'>('feature');
|
||||
const branch = ref('');
|
||||
const newBranch = ref(true);
|
||||
const baseRef = ref('');
|
||||
|
||||
// Sélecteur de branche de base : branches du 1er repo du groupe (indicatif ; vide → défaut serveur par repo).
|
||||
const branches = ref<RepoBranchesResponse | null>(null);
|
||||
const baseBranchOptions = computed<string[]>(() => {
|
||||
const b = branches.value;
|
||||
if (!b) return [];
|
||||
return Array.from(new Set([...(b.default ? [b.default] : []), ...b.local, ...b.remote]));
|
||||
});
|
||||
onMounted(async () => {
|
||||
const first = props.repos[0];
|
||||
if (!first) return;
|
||||
try {
|
||||
branches.value = await worktrees.fetchBranches(first.id);
|
||||
} catch {
|
||||
/* le sélecteur de base est facultatif */
|
||||
}
|
||||
});
|
||||
const command = ref<'claude' | 'bash'>('claude');
|
||||
const wtResults = ref<Record<string, CrossRepoResult>>({});
|
||||
const outcome = ref<GroupFeatureOutcome | null>(null);
|
||||
@@ -136,7 +153,7 @@ async function onSubmit(): Promise<void> {
|
||||
{
|
||||
command: command.value,
|
||||
...(mode.value === 'feature'
|
||||
? { branch: branch.value.trim(), newBranch: newBranch.value, ...(baseRef.value.trim() ? { baseRef: baseRef.value.trim() } : {}) }
|
||||
? { branch: branch.value.trim(), ...(baseRef.value.trim() ? { baseRef: baseRef.value.trim() } : {}) }
|
||||
: {}),
|
||||
},
|
||||
(result) => {
|
||||
|
||||
Reference in New Issue
Block a user