
# Stash

Git stash enregistre les modifications du répertoire de travail et de l’index pour vous permettre de passer à une autre tâche, puis de les restaurer plus tard. Pitmaster implémente le stash à l’aide de `refs/stash` et du reflog, selon la disposition de stockage de git. `Repository` ne possède pas de méthode stash ; utilisez la classe `Stash` ci-dessous.

Deux problèmes connus : lorsqu’il y a plusieurs entrées, `drop()` (et donc `pop()`) laisse l’entrée dans la liste ([#10](https://github.com/inline0/pitmaster/issues/10)), et `apply()` écrase les modifications non validées des fichiers que le stash ne touche pas ([#11](https://github.com/inline0/pitmaster/issues/11)). Appliquez les stash sur un répertoire de travail propre.

## Fonctionnement du stash

Chaque entrée de stash est un commit à deux parents :
- **Parent 0** : le commit HEAD au moment du stash (l’état de base)
- **Parent 1** : un commit qui capture l’état de l’index (modifications indexées)

L’arbre du commit stash lui-même capture l’état du répertoire de travail (y compris les modifications non indexées).

La « pile » stash est le reflog de `refs/stash`. Le stash le plus récent est `refs/stash` lui-même. Les entrées plus anciennes sont des entrées du reflog : `stash@{0}`, `stash@{1}`, etc.

```
refs/stash -> stash commit (worktree state)
                parent[0] -> HEAD at time of stash
                parent[1] -> index state commit
                               parent[0] -> HEAD at time of stash
```

## Créer l’objet Stash

```php
use Pitmaster\Stash\Stash;

$stash = new Stash(
    $repo->objectDatabase(),
    $repo->refDatabase(),
    $repo->gitDir(),
    $repo->workDir(),
);
```

## Push (enregistrer les modifications)

```php
// public function push(string $message = '', bool $includeUntracked = false): ObjectId
$stashId = $stash->push('Work in progress on login');
echo "Saved: {$stashId->hex}\n";

// Also stash untracked files
$stashId = $stash->push('With new files', includeUntracked: true);
```

Push effectue les opérations suivantes :
1. Lit le commit HEAD actuel
2. Construit un arbre à partir de l’index actuel (état indexé)
3. Crée un « commit d’index » dont HEAD est le parent
4. Construit un arbre à partir du répertoire de travail actuel (y compris les modifications non indexées)
5. Crée le commit stash (arbre du worktree, parents = HEAD + commit d’index)
6. Met à jour `refs/stash` pour qu’il pointe vers le commit stash
7. Ajoute une entrée du reflog à la pile
8. Réinitialise le répertoire de travail et l’index à HEAD

Après push, les fichiers suivis correspondent à HEAD. Les fichiers non suivis restent, sauf si `includeUntracked` est défini ; ils sont alors supprimés et stockés dans l’arbre du commit stash lui-même (git les stocke plutôt dans un troisième parent, afin qu’ils apparaissent comme des modifications suivies pour git). `push()` crée une entrée même en l’absence de modifications, alors que git indique « No local changes to save ».

### Avec le message par défaut

```php
$stashId = $stash->push();
// Message: "WIP on <branch>: <short hash> <HEAD subject>"
// With a message: "On <branch>: <message>"
```

Avec un HEAD détaché, `<branch>` vaut `HEAD`.

## Pop (appliquer et supprimer)

```php
$stash->pop();
```

Applique l’entrée stash la plus récente au répertoire de travail et la supprime de la pile. Équivalent à `apply()` suivi de `drop()`.

### Extraire une entrée précise

```php
$stash->pop(1);  // Pop stash@{1}
```

## Apply (restaurer sans supprimer)

```php
$stash->apply();      // Apply stash@{0}
$stash->apply(2);     // Apply stash@{2}
```

Apply fusionne le stash dans le HEAD actuel, fichier par fichier (base du stash, HEAD actuel, arbre du stash). L’entrée stash reste dans la pile et l’index n’est pas modifié. En cas de conflit, les fichiers contiennent des marqueurs libellés `Updated upstream` et `Stashed changes`, et `Pitmaster\Exceptions\MergeConflictException` est levée ; `pop()` conserve alors l’entrée. Apply ne vérifie pas au préalable la présence de modifications locales (voir la remarque en haut).

## Lister les entrées

```php
$entries = $stash->listEntries();

foreach ($entries as $entry) {
    echo "stash@{{$entry['index']}}: {$entry['message']}\n";
    // stash@{0}: On main: Work in progress on login
    // stash@{1}: WIP on main: f707601 Add login form
}
```

Chaque entrée contient :
- `index` : la position du stash (0 = le plus récent)
- `message` : le message du stash
- `hash` : le hash du commit stash

## Drop (supprimer sans appliquer)

```php
$stash->drop();       // Drop stash@{0}
$stash->drop(1);      // Drop stash@{1}
```

Si l’unique entrée est supprimée, `refs/stash` et son reflog sont supprimés. S’il y a plusieurs entrées, `drop()` ne fait actuellement que déplacer `refs/stash` sans modifier le reflog ; l’entrée reste donc dans `listEntries()` ([#10](https://github.com/inline0/pitmaster/issues/10)).

## Détails du stockage

### Structure du commit stash

```
commit <hash>
tree <worktree-tree-hash>
parent <HEAD-hash>
parent <index-commit-hash>
author <current identity>
committer <current identity>

<stash message>
```

### Structure du commit d’index

```
commit <hash>
tree <index-tree-hash>
parent <HEAD-hash>
author <current identity>
committer <current identity>

index on <branch>: <short hash> <HEAD subject>
```

L’identité provient de `GIT_COMMITTER_NAME`, puis de `GIT_AUTHOR_NAME`, puis de `user.name` dans la configuration du dépôt, puis de `Pitmaster` (même ordre pour l’adresse e-mail, en terminant par `pitmaster@example.invalid`). La date provient de `GIT_COMMITTER_DATE`, puis de `GIT_AUTHOR_DATE`, puis de l’heure actuelle. Contrairement aux commits, stash ne lit pas les constantes `PITMASTER_*`.

### Stockage Reflog

Les entrées stash sont stockées dans `.git/logs/refs/stash` :

```
0000000000000000000000000000000000000000 abc123... Name <email> 1700000000 +0000	WIP on main: f707601 Add login form
abc123... def456... Name <email> 1700000001 +0000	On main: Work in progress on login
```

Le reflog est lu par ordre chronologique. La liste des stash inverse cet ordre (du plus récent au plus ancien). Le reflog stash est écrit quelle que soit la valeur de `core.logallrefupdates`.

## Utilisation de CLI

```bash
# Save current changes
./vendor/bin/pitmaster stash
./vendor/bin/pitmaster stash push
./vendor/bin/pitmaster stash push "Work in progress on login"

# Apply and remove top entry
./vendor/bin/pitmaster stash pop

# Apply without removing
./vendor/bin/pitmaster stash apply

# List all entries
./vendor/bin/pitmaster stash list

# Remove top entry without applying
./vendor/bin/pitmaster stash drop
```

Le message est l’argument qui suit `push` (ou `save`) ; `-m` n’est pas pris en charge. `pop`, `apply` et `drop` fonctionnent toujours sur `stash@{0}` ; le CLI n’a pas de `--include-untracked`.

## Limites

- Uniquement le stash de tout le répertoire de travail (pas de `--keep-index`) ; fichiers non suivis uniquement via `push($message, includeUntracked: true)`
- Pas de mode `--patch` (sélection interactive des portions)
- Pop/apply restaure l’état du répertoire de travail, sans restaurer l’index. Les modifications indexées reviennent comme modifications non indexées, et les nouveaux fichiers indexés reviennent comme fichiers non suivis
- `stash@{n}` n’est pas accepté par `Repository::resolve()`
- Dans un worktree lié, stash utilise le mauvais emplacement de configuration et de reflog ([#14](https://github.com/inline0/pitmaster/issues/14))
