Lewati ke konten

Penggabungan

Pitmaster melakukan penggabungan branch dengan penggabungan tiga arah, seperti git merge. Kelas ini mendeteksi peluang fast-forward, menghitung basis penggabungan, melakukan penggabungan pada tingkat tree dan konten, serta menghasilkan penanda konflik standar saat file mengalami konflik.

Penggabungan dasar

PHP
$result = $repo->merge('feature/login');

if ($result->clean) {
    echo "Merged successfully: {$result->commitId->hex}\n";
} else {
    echo "Conflicts:\n";
    foreach ($result->conflictPaths as $path) {
        echo "  {$path}\n";
    }
}

MergeResult

Kelas MergeResult adalah objek nilai readonly:

PHP
use Pitmaster\Merge\MergeResult;

$result->clean;           // bool: true if merge completed without conflicts
$result->commitId;        // ?ObjectId: the merge commit (null if conflicts)
$result->conflictPaths;   // array: list of file paths with conflicts
$result->mergedContents;  // array: path => content with conflict markers (if conflicts)

Saat penggabungan berhenti karena konflik, status konflik ditulis ke index dan working tree. Selesaikan file, add() file tersebut, lalu panggil $repo->mergeContinue(), atau panggil $repo->mergeAbort() untuk memulihkan keadaan sebelum penggabungan.

Sebelum menggabungkan, commit atau stash perubahan lokal: proses penggabungan membangun ulang working tree dari hasilnya dan menimpa perubahan yang belum di-commit pada file yang dilacak (#2).

Penggabungan fast-forward

Jika HEAD saat ini merupakan leluhur branch target, Pitmaster melakukan penggabungan fast-forward: HEAD cukup dipindahkan ke commit target tanpa membuat commit penggabungan.

Kode
Before:         After fast-forward:

A---B (main)    A---B---C---D (main)
     \
      C---D (feature)
PHP
$result = $repo->merge('feature');
// $result->clean === true
// $result->commitId points to D
// No merge commit created

Kasus sebaliknya belum terdeteksi: menggabungkan branch yang sudah tercakup dalam HEAD akan membuat commit penggabungan baru dengan tree yang tidak berubah, alih-alih melaporkan "Already up to date" (#6).

Penggabungan tiga arah

Saat branch telah menyimpang, Pitmaster melakukan penggabungan tiga arah:

  1. Temukan basis penggabungan (leluhur bersama terdekat)
  2. Bandingkan basis dengan milik kita dan milik mereka pada tingkat tree
  3. Untuk setiap file yang berubah di kedua branch, lakukan penggabungan pada tingkat konten
  4. Buat commit penggabungan dengan dua induk
Kode
Before:              After merge:

      C---D (feature)         C---D
     /                       /     \
A---B---E---F (main)   A---B---E---F---G (main)

Basis penggabungan

Basis penggabungan adalah leluhur bersama paling baru dari dua commit. Pitmaster menemukannya dengan menelusuri graf commit. Jika ada beberapa basis penggabungan (riwayat criss-cross), merge() menggabungkannya terlebih dahulu dan menggunakan hasilnya sebagai basis.

PHP
$base = $repo->mergeBase(
    $repo->resolve('main'),
    $repo->resolve('feature/login'),
);

if ($base !== null) {
    echo "Common ancestor: {$base->hex}\n";
}

Untuk mengakses algoritme MergeBase secara langsung:

PHP
use Pitmaster\Merge\MergeBase;

$finder = new MergeBase($repo->objectDatabase());
$base = $finder->find($commitA, $commitB);

// All merge bases (criss-cross histories can have more than one)
$bases = $finder->findAll($commitA, $commitB);

// Check ancestry
$isAncestor = $finder->isAncestor($oldCommit, $newCommit);

Pemeriksaan penggabungan branch

PHP
if ($repo->isBranchMerged('feature/done')) {
    // All commits in feature/done are reachable from default branch
    $repo->deleteBranch('feature/done');
}

// Check against a specific target
if ($repo->isBranchMerged('feature/done', 'develop')) {
    // Merged into develop
}

Penanda konflik

Jika sebuah file diubah di kedua branch dan perubahan saling bertumpang tindih, Pitmaster menghasilkan penanda konflik standar. merge() memberi label pada sisi-sisinya sebagai HEAD dan nama branch hasil penggabungan:

Kode
<<<<<<< HEAD
    return $this->value + 1;
=======
    return $this->value * 2;
>>>>>>> feature

Kelas ConflictMarker membungkus dua versi dengan penanda:

PHP
use Pitmaster\Merge\ConflictMarker;

$marked = ConflictMarker::mark($oursContent, $theirsContent, 'HEAD', 'feature');

// diff3 style, with the base section
$marked = ConflictMarker::mark($oursContent, $theirsContent, 'HEAD', 'feature', $baseContent, 'base');

Penggabungan tiga arah pada tingkat konten

Kelas ThreeWayMerge menggabungkan konten blob (bukan file):

PHP
use Pitmaster\Merge\ThreeWayMerge;

$result = ThreeWayMerge::merge($baseContent, $oursContent, $theirsContent, 'HEAD', 'feature');

$result['content'];   // merged text, with conflict markers if needed
$result['clean'];     // true when nothing conflicted
$result['conflicts']; // number of conflict regions

Kelas ini menerima tiga string (versi basis, versi kita, dan versi mereka) beserta label opsional, serta gaya konflik ('merge' sebagai default, atau 'diff3' untuk menyertakan bagian basis).

Strategi penggabungan

Penggabungan rekursif

Strategi default ($repo->merge($branch)) menangani riwayat penggabungan criss-cross dengan beberapa basis penggabungan. CLI juga menerima -s ort dan -s recursive untuk strategi ini.

Penggabungan Ours

Mencatat commit penggabungan yang mempertahankan tree HEAD tanpa perubahan. Berguna untuk menjaga riwayat branch tanpa menyertakan perubahan.

PHP
$result = $repo->merge('feature/old', 'ours');

Penggabungan Octopus

Menggabungkan dua branch atau lebih ke dalam satu commit penggabungan. Jika ada branch yang mengalami konflik, hasilnya dinyatakan tidak bersih tanpa menulis status penggabungan atau membuat commit.

PHP
$result = $repo->mergeOctopus(['feature/a', 'feature/b', 'feature/c']);

Kelas RecursiveMerge, OursMerge, dan OctopusMerge di Pitmaster\Merge adalah helper tingkat konten; Repository mengimplementasikan sendiri strategi di atas.

Rerere (gunakan kembali resolusi yang tercatat)

Pitmaster menyertakan kelas mandiri Rerere yang menyimpan resolusi dalam tata letak rr-cache milik git:

PHP
use Pitmaster\Merge\Rerere;

$rerere = new Rerere($repo->gitDir());

if ($rerere->isEnabled()) {                     // rerere.enabled = true in .git/config
    $rerere->record($conflicted, $resolved);    // remember a resolution
    $resolved = $rerere->resolve($conflicted);  // replay it, or null
}

merge() tidak memanggilnya: resolusi dicatat dan diterapkan kembali hanya jika kode Anda melakukannya. isEnabled() mencari teks literal rerere.enabled = true di .git/config dan tidak mengenali bagian [rerere] yang ditulis oleh git config rerere.enabled true (#20).

Alur penggabungan pada tingkat tree

Proses penggabungan lengkap:

  1. MergeBase::findAll() mencari leluhur bersama
  2. Tree basis, milik kita, dan milik mereka dibandingkan path demi path
  3. Untuk file yang hanya diubah di satu branch: gunakan perubahan tersebut
  4. Untuk file yang diubah di kedua branch:
    • Jika keduanya berubah dengan cara yang sama: gunakan salah satunya (hasilnya sama)
    • Jika perubahannya berbeda: jalankan ThreeWayMerge::merge() pada konten
  5. Jika semua penggabungan bersih: bangun tree hasil penggabungan dan buat commit penggabungan
  6. Jika ada konflik: kembalikan MergeResult dengan clean = false dan path yang berkonflik

Penggunaan CLI

Shell
./vendor/bin/pitmaster merge feature/login

Output jika berhasil:

Kode
Merge successful.

Output saat terjadi konflik:

Kode
CONFLICT in: src/File.php, src/Other.php