
# Diff

Pitmaster menghitung diff dalam format unified milik git. Mesin diff mendukung beberapa algoritma, pemformatan unified diff, deteksi file biner, dan perbandingan rekursif tingkat tree.

Untuk file teks yang dimodifikasi dengan mode `100644`, `format()` menghasilkan byte yang sama dengan `git diff`. Kasus lainnya saat ini diformat secara berbeda ([#3](https://github.com/inline0/pitmaster/issues/3)):

- File baru dan yang dihapus tidak memiliki baris `new file mode` / `deleted file mode`, baris `index`, ataupun `/dev/null`: kedua sisi diberi label `a/<path>` dan `b/<path>`, dan rentang hunk sisi kosong ditulis sebagai `-1,0` atau `+1,0`, sementara git menulis `-0,0` atau `+0,0`.
- Baris `index` selalu diakhiri dengan `100644`, apa pun mode filenya.
- Perubahan mode saja (misalnya `chmod +x`) tidak menghasilkan apa pun.
- File biner menampilkan `Binary files differ` tanpa baris `index`, sedangkan git menampilkan `Binary files a/<path> and b/<path> differ`.
- Penggantian nama ditampilkan sebagai satu entri `diff --git a/<old> b/<new>` dengan diff konten, tanpa baris `similarity index`, `rename from`, atau `rename to`.

## Operasi diff

### Perubahan yang belum di-stage (worktree vs index)

```php
$diffs = $repo->diff();

foreach ($diffs as $diff) {
    echo $diff->format();
}
```

Membandingkan setiap file dalam index dengan kondisinya saat ini di working tree. Mendeteksi modifikasi, penghapusan, dan file biner.

### Perubahan yang sudah di-stage (index vs HEAD)

```php
$diffs = $repo->diffStaged();

foreach ($diffs as $diff) {
    echo $diff->format();
}
```

Membandingkan index saat ini dengan tree commit HEAD. Menampilkan apa yang akan disertakan dalam commit berikutnya. File yang dihapus dan ditambahkan dengan konten serupa dilaporkan sebagai satu entri yang diganti namanya. Sebelum commit pertama, HEAD belum ada dan hasilnya kosong ([#5](https://github.com/inline0/pitmaster/issues/5)).

Pemasangan pasangan nama saat ini juga mencocokkan file satu baris yang tidak berkaitan, karena baris kosong di akhir dihitung sebagai konten yang sama ([#4](https://github.com/inline0/pitmaster/issues/4)).

### Diff antar-tree

```php
$a = $repo->readObject($repo->resolve('HEAD~1')->hex)->tree;
$b = $repo->head()->tree;
$diffs = $repo->diffTree($a, $b);
```

Membandingkan dua tree secara rekursif. Berikan ID tree: ID commit tidak diurai menjadi tree-nya dan akan menghasilkan hasil kosong. Mesin `TreeDiff` yang sama digunakan oleh `diffStaged()`, `show()`, dan `logPath()`.

### Filter berdasarkan jalur

```php
$diffs = $repo->diff('src/specific-file.php');
```

Spesifikasi jalur adalah satu jalur file yang persis. `diffStaged()` menerima argumen yang sama tetapi untuk saat ini mengabaikannya, jadi selalu mengembalikan setiap file yang sudah di-stage ([#5](https://github.com/inline0/pitmaster/issues/5)).

### Pilih algoritma

```php
$diffs = $repo->diff(null, 'patience');
$diffs = $repo->diffStaged(null, 'histogram');
$diffs = $repo->diffTree($a, $b, 'minimal');
```

Nama yang valid adalah `myers` (default), `patience`, `histogram`, dan `minimal`. Nama lainnya akan melempar `InvalidArgumentException`.

## Struktur DiffResult

Setiap `DiffResult` merepresentasikan diff untuk satu file.

```php
use Pitmaster\Diff\DiffResult;

foreach ($diffs as $diff) {
    echo $diff->oldPath;    // Path in the old tree
    echo $diff->newPath;    // Path in the new tree
    echo $diff->binary;     // true for binary files
    echo $diff->oldHash;    // Hex hash of the old blob (null for added files)
    echo $diff->newHash;    // Hex hash of the new blob (null for deleted files)

    foreach ($diff->hunks as $hunk) {
        echo $hunk->header();  // @@ -1,5 +1,7 @@
        foreach ($hunk->lines as $line) {
            echo $line . "\n"; // ' context', '+added', '-removed'
        }
    }
}
```

### Output berformat

```php
echo $diff->format();
```

Menghasilkan output unified diff. Untuk file teks yang dimodifikasi, hasilnya cocok dengan `git diff`:

```
diff --git a/src/File.php b/src/File.php
index abc1234..def5678 100644
--- a/src/File.php
+++ b/src/File.php
@@ -10,6 +10,8 @@ class File
     private string $name;

+    private int $count;
+
     public function __construct()
     {
```

### Periksa perubahan

```php
if ($diff->hasChanges()) {
    // File was modified (or is binary with changes)
}
```

## Struktur hunk

Setiap `Hunk` merepresentasikan bagian perubahan yang berurutan beserta baris konteks.

```php
use Pitmaster\Diff\Hunk;

$hunk->oldStart;   // Starting line in old file
$hunk->oldCount;   // Number of lines from old file
$hunk->newStart;   // Starting line in new file
$hunk->newCount;   // Number of lines in new file
$hunk->lines;      // Array of diff lines (prefixed with ' ', '+', or '-')
$hunk->header();   // "@@ -10,6 +10,8 @@"
```

## Algoritma diff

Pitmaster menerapkan empat algoritma diff. Default-nya adalah Myers.

### Diff Myers

Algoritma O(ND) standar, sama seperti default git. Menghasilkan skrip edit minimal.

```php
use Pitmaster\Diff\MyersDiff;

$hunks = MyersDiff::diff($oldContent, $newContent);
```

Metode `diff()` menerima dua string dan mengembalikan array berisi objek `Hunk`. Metode ini membagi input berdasarkan baris baru, menghitung skrip edit terpendek, dan mengelompokkan perubahan menjadi hunk dengan 3 baris konteks (sesuai default git).

### Diff Patience

Diff struktural yang lebih baik untuk kode. Mencocokkan baris unik terlebih dahulu, lalu mengisi bagian di antaranya.

```php
use Pitmaster\Diff\PatienceDiff;

$hunks = PatienceDiff::diff($oldContent, $newContent);
```

### Diff Histogram

Varian diff patience yang lebih baik dalam menangani baris berulang. Digunakan oleh JGit dan Eclipse.

```php
use Pitmaster\Diff\HistogramDiff;

$hunks = HistogramDiff::diff($oldContent, $newContent);
```

### Diff Minimal

Menghasilkan diff sekecil mungkin (jumlah baris yang berubah paling sedikit), dengan konsekuensi komputasi yang lebih banyak.

```php
use Pitmaster\Diff\MinimalDiff;

$hunks = MinimalDiff::diff($oldContent, $newContent);
```

## Deteksi biner

Pitmaster memperlakukan konten sebagai biner jika byte NUL terdapat di antara 8192 byte pertama (git memeriksa 8000 byte pertama).

```php
if (MyersDiff::isBinary($content)) {
    // Binary file, skip line-level diff
}
```

File biner menghasilkan `DiffResult` dengan `$binary = true` dan array `$hunks` kosong. Output berformatnya adalah:

```
diff --git a/image.png b/image.png
Binary files differ
```

## Output berwarna

CLI menggunakan `ColorDiff` untuk output terminal dengan warna ANSI.

```php
use Pitmaster\Diff\ColorDiff;

$colorized = ColorDiff::colorize($diff->format());
echo $colorized;
```

- Baris yang ditambahkan (`+`) ditampilkan dalam warna hijau
- Baris yang dihapus (`-`) ditampilkan dalam warna merah
- Header hunk (`@@`) ditampilkan dalam warna sian
- Header file (`diff --git`, `index`, `---`, `+++`) ditampilkan dengan huruf tebal

`pitmaster diff` selalu memberi warna, termasuk saat output-nya dialirkan melalui pipe. `pitmaster show` hanya memberi warna saat stdout berupa terminal.

## Perbedaan tree (internal)

Kelas `TreeDiff` membandingkan dua objek tree secara rekursif dan menghasilkan `DiffResult` untuk setiap file yang berubah.

```php
use Pitmaster\Diff\TreeDiff;

$treeDiff = new TreeDiff($repo->objectDatabase());
$diffs = $treeDiff->diff($oldTreeId, $newTreeId);
```

Perbedaan tree menangani:
- File yang ditambahkan (hanya ada di tree baru)
- File yang dihapus (hanya ada di tree lama)
- File yang diubah (path sama, hash blob berbeda)
- Perbandingan subdirektori secara rekursif
- Pemasangan pasangan file yang diubah namanya antara file yang dihapus dan ditambahkan (hash persis sama, atau setidaknya 50% baris yang sama)

Entri dengan hash yang tidak berubah akan dilewati, jadi perubahan mode saja tidak menghasilkan hasil.

## Perbedaan kata

Untuk perubahan sebaris pada tingkat kata di dalam baris.

```php
use Pitmaster\Diff\WordDiff;

$result = WordDiff::diff('the quick fox', 'the slow fox');
// 'the [-quick-]{+slow+} fox'
```
