İçeriğe geç

Birleştirme

Pitmaster, git merge gibi dalları üç yönlü birleştirme yöntemiyle birleştirir. İleri sarma fırsatlarını algılar, birleştirme tabanlarını hesaplar, ağaç ve içerik düzeyinde birleştirmeler yapar ve dosyalarda çakışma olduğunda standart çakışma işaretçileri oluşturur.

Temel birleştirme

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

MergeResult sınıfı, salt okunur bir değer nesnesidir:

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)

Birleştirme çakışmalar nedeniyle durduğunda çakışma durumu dizine ve çalışma ağacına yazılır. Dosyaları çözün, add() komutunu çalıştırın ve $repo->mergeContinue() çağrısını yapın ya da birleştirme öncesi duruma dönmek için $repo->mergeAbort() çağrısını yapın.

Birleştirmeden önce yerel değişiklikleri kaydedin veya saklayın: birleştirme, çalışma ağacını sonuçtan yeniden oluşturur ve izlenen dosyalardaki kaydedilmemiş değişikliklerin üzerine yazar (#2).

İleri sarma birleştirmeleri

Geçerli HEAD, hedef dalın atasıysa Pitmaster ileri sarma birleştirmesi yapar: birleştirme kaydı oluşturmadan HEAD'i hedef kayda taşır.

Kod
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

Ters durum henüz algılanmıyor: HEAD'in zaten içerdiği bir dalı birleştirmek, "Zaten güncel" bildirmek yerine ağacı değişmemiş yeni bir birleştirme kaydı oluşturur (#6).

Üç yönlü birleştirme

Dallar birbirinden ayrıştığında Pitmaster üç yönlü birleştirme yapar:

  1. Birleştirme tabanını bulun (en yakın ortak ata)
  2. Tabanı bizimkine ve tabanı onlarınkine ağaç düzeyinde karşılaştırın
  3. Her iki dalda da değiştirilen her dosya için içerik düzeyinde birleştirme yapın
  4. İki ebeveynli bir birleştirme kaydı oluşturun
Kod
Before:              After merge:

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

Birleştirme tabanı

Birleştirme tabanı, iki kaydın en yakın ortak atasıdır. Pitmaster bunu kayıt grafiğinde gezinerek bulur. Birden fazla birleştirme tabanı olduğunda (çapraz geçmiş), merge() bunları önce birleştirir ve sonucu taban olarak kullanır.

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

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

MergeBase algoritmasına doğrudan erişim için:

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);

Dal birleştirme denetimi

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
}

Çakışma işaretçileri

Bir dosya her iki dalda değiştirilmişse ve değişiklikler çakışıyorsa Pitmaster standart çakışma işaretçileri oluşturur. merge() tarafları HEAD ve birleştirilen dalın adıyla etiketler:

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

ConflictMarker sınıfı, iki sürümü işaretçilerin içine alır:

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');

İçerik düzeyinde üç yönlü birleştirme

ThreeWayMerge sınıfı blob içeriğini (dosyaları değil) birleştirir:

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

Üç dizeyi (taban sürümü, bizim sürümümüz ve onların sürümü), isteğe bağlı etiketleri ve bir çakışma stilini alır (varsayılan olarak 'merge', taban bölümünü eklemek için 'diff3').

Birleştirme stratejileri

Özyinelemeli birleştirme

Varsayılan strateji olan $repo->merge($branch), birden fazla birleştirme tabanı içeren çapraz geçmişleri işler. CLI, bu strateji için -s ort ve -s recursive değerlerini de kabul eder.

Bizimki birleştirmesi

HEAD'in ağacını değiştirmeden tutan bir birleştirme kaydı oluşturur. Değişiklikleri dahil etmeden dal geçmişini korumak için kullanışlıdır.

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

Octopus birleştirmesi

İki veya daha fazla dalı tek bir birleştirme kaydında birleştirir. Herhangi bir dalda çakışma varsa, birleştirme durumu yazmadan veya kayıt oluşturmadan temiz olmayan bir sonuç döndürür.

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

Pitmaster\Merge içindeki RecursiveMerge, OursMerge ve OctopusMerge sınıfları içerik düzeyinde yardımcı araçlardır; Repository yukarıdaki stratejileri kendisi uygular.

Rerere (kaydedilmiş çözümü yeniden kullanma)

Pitmaster, çözümleri git'nin rr-cache düzeninde saklayan bağımsız bir Rerere sınıfı içerir:

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() bu sınıfı çağırmaz: çözümler yalnızca kodunuz bunu yaptığında kaydedilir ve yeniden uygulanır. isEnabled(), .git/config içinde rerere.enabled = true düz metnini arar ve git config rerere.enabled true tarafından yazılan [rerere] bölümünü tanımaz (#20).

Ağaç düzeyinde birleştirme akışı

Birleştirme işleminin tamamı:

  1. MergeBase::findAll() ortak ataları bulur
  2. Taban, bizim ve onların ağaçları yol yol karşılaştırılır
  3. Yalnızca bir dalda değiştirilen dosyalar için: bu değişiklik alınır
  4. Her iki dalda da değiştirilen dosyalar için:
    • İkisi de aynı şekilde değiştirilmişse: ikisi de aynı sonucu verdiğinden herhangi biri alınır
    • Değişiklikler farklıysa: içerikte ThreeWayMerge::merge() çalıştırılır
  5. Tüm birleştirmeler temizse: birleştirilmiş ağacı oluşturun ve birleştirme kaydı yaratın
  6. Herhangi bir çakışma varsa: MergeResult ile çakışma yollarını içeren clean = false döndürülür

CLI kullanımı

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

Başarılı olduğunda çıktı:

Kod
Merge successful.

Çakışma çıktısı:

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