Treci la conținut

Îmbinare

Pitmaster îmbină ramuri printr-o îmbinare în trei direcții, la fel ca git merge. Detectează posibilitățile de fast-forward, calculează bazele de îmbinare, îmbină arbori și conținut și generează marcaje de conflict standard când fișierele intră în conflict.

Îmbinare de bază

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

Clasa MergeResult este un obiect valoare doar pentru citire:

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)

Când îmbinarea se oprește din cauza conflictelor, starea conflictului este scrisă în index și în arborele de lucru. Rezolvați conflictele din fișiere, adăugați-le la staging cu add() și apelați $repo->mergeContinue() sau apelați $repo->mergeAbort() pentru a restaura starea dinaintea îmbinării.

Înainte de îmbinare, faceți commit sau stash pentru modificările locale: îmbinarea reconstruiește arborele de lucru pornind de la rezultat și suprascrie modificările necomise din fișierele urmărite (#2).

Îmbinări fast-forward

Când HEAD-ul actual este un strămoș al ramurii țintă, Pitmaster efectuează o îmbinare fast-forward: mută pur și simplu HEAD la commitul țintă, fără să creeze un commit de îmbinare.

Cod
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

Cazul invers nu este detectat încă: îmbinarea unei ramuri pe care HEAD o conține deja creează un commit de îmbinare cu un arbore neschimbat, în loc să raporteze „Already up to date” (#6).

Îmbinare în trei direcții

Când ramurile au diverjat, Pitmaster efectuează o îmbinare în trei direcții:

  1. Găsește baza de îmbinare (cel mai apropiat strămoș comun)
  2. Compară baza cu versiunea noastră și cu a lor, la nivel de arbore
  3. Pentru fiecare fișier modificat în ambele ramuri, efectuează o îmbinare la nivel de conținut
  4. Creează un commit de îmbinare cu doi părinți
Cod
Before:              After merge:

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

Baza de îmbinare

Baza de îmbinare este cel mai recent strămoș comun al două commituri. Pitmaster o găsește parcurgând graful commiturilor. Când există mai multe baze de îmbinare (istoric încrucișat), merge() le îmbină mai întâi și folosește rezultatul ca bază.

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

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

Acces direct la algoritmul MergeBase:

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

Verificarea îmbinării ramurilor

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
}

Marcaje de conflict

Când un fișier este modificat în ambele ramuri și modificările se suprapun, Pitmaster generează marcaje de conflict standard. merge() etichetează versiunile ca HEAD și cu numele ramurii îmbinate:

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

Clasa ConflictMarker încadrează două versiuni în marcaje:

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

Îmbinare în trei direcții la nivel de conținut

Clasa ThreeWayMerge îmbină conținutul bloburilor (nu fișierele):

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

Primește trei șiruri (versiunea de bază, versiunea noastră și versiunea lor), etichete opționale și un stil de conflict ('merge' în mod implicit sau 'diff3' pentru a include secțiunea de bază).

Strategii de îmbinare

Îmbinare recursivă

Strategia implicită ($repo->merge($branch)) gestionează istoricul încrucișat cu mai multe baze de îmbinare. CLI acceptă și -s ort și -s recursive pentru aceasta.

Îmbinare Ours

Înregistrează un commit de îmbinare care păstrează arborele HEAD neschimbat. Utilă pentru menținerea istoricului ramurilor fără a integra modificări.

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

Îmbinare Octopus

Îmbină două sau mai multe ramuri într-un singur commit de îmbinare. Dacă vreo ramură intră în conflict, returnează un rezultat necurat fără să scrie starea îmbinării sau să creeze un commit.

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

Clasele RecursiveMerge, OursMerge și OctopusMerge din Pitmaster\Merge sunt ajutoare pentru îmbinarea la nivel de conținut; Repository implementează singură strategiile de mai sus.

Rerere (reutilizarea rezolvării înregistrate)

Pitmaster include o clasă Rerere autonomă, care stochează rezolvările în structura rr-cache a lui 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() nu o apelează: rezolvările sunt înregistrate și reluate numai când codul dumneavoastră face acest lucru. isEnabled() caută textul literal rerere.enabled = true în .git/config și nu recunoaște secțiunea [rerere] scrisă de git config rerere.enabled true (#20).

Fluxul îmbinării la nivel de arbore

Procesul complet de îmbinare:

  1. MergeBase::findAll() găsește strămoșii comuni
  2. Arborii de bază, al nostru și al lor sunt comparați cale cu cale
  3. Pentru fișierele modificate doar într-o ramură: se preia acea modificare
  4. Pentru fișierele modificate în ambele ramuri:
    • Dacă au fost modificate la fel: se preia oricare (sunt identice)
    • Dacă modificările diferă: se execută ThreeWayMerge::merge() asupra conținutului
  5. Dacă toate îmbinările sunt curate: se construiește arborele îmbinat și se creează un commit de îmbinare
  6. Dacă există conflicte: se returnează MergeResult cu clean = false și căile care intră în conflict

Utilizarea CLI

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

Rezultat în caz de reușită:

Cod
Merge successful.

Rezultatul în caz de conflict:

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