feat(p9): commit/push avancé dans l'IDE (staging sélectif, discard, amend, fetch/pull)
- components/workspace/CommitPanel.vue : staging sélectif par fichier (stage/unstage/stage all/unstage all), discard avec confirmation (includeUntracked), zone de commit (message + amend), commit mode 'staged', push, fetch, pull --ff-only (rebase proposé en cas de divergence), gestion 409 ALREADY_PUSHED - components/workspace/FileRow.vue : ligne de fichier réutilisable (badge statut M/A/D/?/U + clic → diff + slot actions) - WorkspaceView : CommitPanel remplace ChangedFilesPanel ; @changed → reload des changements - stores/worktrees : applyWorktree (upsert depuis les mutations git → header live) - i18n EN+FR (commit.*) ; acceptance-p9.mjs (staging sélectif, amend OK/409, pull ff-only OK + divergence refusée)
This commit is contained in:
53
packages/web/src/components/workspace/FileRow.vue
Normal file
53
packages/web/src/components/workspace/FileRow.vue
Normal file
@@ -0,0 +1,53 @@
|
||||
<template>
|
||||
<div
|
||||
class="group flex items-center gap-2 rounded px-2 py-0.5 text-xs"
|
||||
:class="active ? 'bg-zinc-800 text-zinc-100' : 'text-zinc-300 hover:bg-zinc-800/60'"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class="flex min-w-0 flex-1 items-center gap-2 text-left"
|
||||
:title="c.renamedFrom ? `${c.renamedFrom} → ${c.path}` : c.path"
|
||||
@click="emit('select')"
|
||||
>
|
||||
<span class="w-3 shrink-0 text-center font-mono font-bold" :class="tone">{{ letter }}</span>
|
||||
<span class="min-w-0 flex-1 truncate font-mono">{{ basename }}<span class="text-zinc-600">{{ dir }}</span></span>
|
||||
<span v-if="c.insertions" class="shrink-0 text-emerald-500">+{{ c.insertions }}</span>
|
||||
<span v-if="c.deletions" class="shrink-0 text-rose-500">−{{ c.deletions }}</span>
|
||||
</button>
|
||||
<span class="flex shrink-0 items-center gap-0.5 opacity-60 group-hover:opacity-100">
|
||||
<slot />
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import type { FileChange } from '@arboretum/shared';
|
||||
|
||||
const props = defineProps<{ c: FileChange; active: boolean; staged?: boolean }>();
|
||||
const emit = defineEmits<{ select: [] }>();
|
||||
|
||||
const letter = computed(() => {
|
||||
if (props.c.conflicted) return 'U';
|
||||
if (props.c.untracked) return '?';
|
||||
const x = props.staged
|
||||
? props.c.indexStatus
|
||||
: props.c.worktreeStatus !== '.' && props.c.worktreeStatus !== ' ' && props.c.worktreeStatus
|
||||
? props.c.worktreeStatus
|
||||
: props.c.indexStatus;
|
||||
return (x || 'M').toUpperCase();
|
||||
});
|
||||
const tone = computed(() => {
|
||||
if (props.c.conflicted) return 'text-rose-400';
|
||||
if (props.c.untracked) return 'text-zinc-400';
|
||||
return letter.value === 'A' ? 'text-emerald-400' : letter.value === 'D' ? 'text-rose-400' : letter.value === 'R' ? 'text-sky-400' : 'text-amber-400';
|
||||
});
|
||||
const basename = computed(() => {
|
||||
const i = props.c.path.lastIndexOf('/');
|
||||
return i >= 0 ? props.c.path.slice(i + 1) : props.c.path;
|
||||
});
|
||||
const dir = computed(() => {
|
||||
const i = props.c.path.lastIndexOf('/');
|
||||
return i >= 0 ? ` ${props.c.path.slice(0, i)}` : '';
|
||||
});
|
||||
</script>
|
||||
Reference in New Issue
Block a user