
# Stash

Git stash menyimpan perubahan direktori kerja dan index agar Anda dapat beralih ke tugas lain, lalu memulihkannya nanti. Pitmaster mengimplementasikan stash menggunakan `refs/stash` dan reflog, sesuai tata letak penyimpanan git. `Repository` tidak memiliki metode stash; gunakan class `Stash` di bawah.

Dua masalah yang diketahui: jika ada lebih dari satu entri, `drop()` (dan karena itu `pop()`) membiarkan entri tersebut tetap dalam daftar ([#10](https://github.com/inline0/pitmaster/issues/10)), dan `apply()` menimpa perubahan yang belum di-commit pada file yang tidak disentuh stash ([#11](https://github.com/inline0/pitmaster/issues/11)). Terapkan stash pada working tree yang bersih.

## Cara kerja stash

Setiap entri stash adalah commit dengan dua parent:
- **Parent 0**: commit HEAD saat stash dibuat (keadaan dasar)
- **Parent 1**: commit yang merekam keadaan index (perubahan yang sudah di-stage)

Tree milik commit stash merekam keadaan working tree (termasuk perubahan yang belum di-stage).

"Stack" stash adalah reflog dari `refs/stash`. Stash terbaru adalah `refs/stash` itu sendiri. Entri yang lebih lama adalah entri reflog: `stash@{0}`, `stash@{1}`, dan seterusnya.

```
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
```

## Membuat objek Stash

```php
use Pitmaster\Stash\Stash;

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

## Push (simpan perubahan)

```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 melakukan hal berikut:
1. Membaca commit HEAD saat ini
2. Membuat tree dari indeks saat ini (status staged)
3. Membuat "commit indeks" dengan HEAD sebagai induk
4. Membuat tree dari direktori kerja saat ini (termasuk perubahan yang belum di-stage)
5. Membuat commit stash (tree worktree, induk = HEAD + commit indeks)
6. Memperbarui `refs/stash` agar menunjuk ke commit stash
7. Menambahkan entri reflog untuk stack
8. Mengatur ulang tree kerja dan indeks ke HEAD

Setelah push, file yang dilacak cocok dengan HEAD. File yang tidak dilacak tetap ada kecuali `includeUntracked` disetel; jika demikian, file tersebut dihapus dan disimpan di tree milik commit stash (git menyimpannya sebagai induk ketiga, sehingga git melihatnya sebagai perubahan yang dilacak). `push()` membuat entri meskipun tidak ada perubahan, saat git melaporkan "No local changes to save".

### Dengan pesan default

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

Pada HEAD yang terlepas, `<branch>` adalah `HEAD`.

## Pop (terapkan dan hapus)

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

Menerapkan entri stash teratas ke direktori kerja dan menghapusnya dari stack. Setara dengan `apply()` diikuti `drop()`.

### Pop entri tertentu

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

## Apply (pulihkan tanpa menghapus)

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

Apply menggabungkan stash ke file HEAD saat ini satu per satu (basis stash, HEAD saat ini, tree stash). Entri stash tetap berada di stack, dan indeks tidak disentuh. Konflik meninggalkan penanda berlabel `Updated upstream` dan `Stashed changes` di dalam file serta melempar `Pitmaster\Exceptions\MergeConflictException`; `pop()` kemudian mempertahankan entri tersebut. Apply tidak memeriksa perubahan lokal terlebih dahulu (lihat catatan di bagian atas).

## Daftar entri

```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
}
```

Setiap entri berisi:
- `index`: posisi stash (0 = terbaru)
- `message`: pesan stash
- `hash`: hash commit stash

## Drop (hapus tanpa menerapkan)

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

Jika satu-satunya entri dihapus, `refs/stash` dan reflog-nya juga dihapus. Jika ada lebih banyak entri, `drop()` saat ini hanya memindahkan `refs/stash` dan membiarkan reflog tetap sama, sehingga entri tetap berada di `listEntries()` ([#10](https://github.com/inline0/pitmaster/issues/10)).

## Detail penyimpanan

### Struktur commit stash

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

<stash message>
```

### Struktur commit indeks

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

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

Identitas diambil dari `GIT_COMMITTER_NAME`, lalu `GIT_AUTHOR_NAME`, kemudian `user.name` dalam konfigurasi repositori, lalu `Pitmaster` (urutan yang sama berlaku untuk email, yang diakhiri dengan `pitmaster@example.invalid`). Tanggal diambil dari `GIT_COMMITTER_DATE`, lalu `GIT_AUTHOR_DATE`, kemudian waktu saat ini. Tidak seperti commit, stash tidak membaca konstanta `PITMASTER_*`.

### Penyimpanan Reflog

Entri stash disimpan di `.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
```

Reflog dibaca dalam urutan kronologis. Daftar stash membalik urutannya (terbaru lebih dulu). Reflog stash ditulis terlepas dari `core.logallrefupdates`.

## Penggunaan 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
```

Pesan adalah argumen setelah `push` (atau `save`); `-m` tidak didukung. `pop`, `apply`, dan `drop` selalu bekerja pada `stash@{0}`; CLI tidak memiliki `--include-untracked`.

## Batasan

- Hanya stash seluruh direktori kerja (tanpa `--keep-index`); file yang tidak dilacak hanya melalui `push($message, includeUntracked: true)`
- Tidak ada mode `--patch` (pemilihan hunk interaktif)
- Pop/apply memulihkan status tree kerja; indeks tidak dipulihkan. Edit yang di-stage kembali sebagai edit yang belum di-stage, dan file yang baru di-stage kembali sebagai file yang tidak dilacak
- `stash@{n}` tidak diterima oleh `Repository::resolve()`
- Stash di worktree tertaut menggunakan konfigurasi dan lokasi reflog yang salah ([#14](https://github.com/inline0/pitmaster/issues/14))
