Gå til indhold

Merge

Pitmaster udfører branch-merges med en trevejs-merge, ligesom git merge. Den registrerer muligheder for fast-forward, beregner merge-baser, udfører merges på tree- og indholdsniveau og genererer standardkonfliktmarkører, når filer er i konflikt.

Grundlæggende merge

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

Klassen MergeResult er et skrivebeskyttet værdiobjekt:

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)

Når merge-processen stopper på grund af konflikter, skrives konfliktstatussen til indekset og arbejdstræet. Løs konflikterne i filerne, add() dem, og kald $repo->mergeContinue(), eller kald $repo->mergeAbort() for at gendanne tilstanden fra før merge-processen.

Før du merger, skal du committe eller stash'e lokale ændringer: merge-processen genopbygger arbejdstræet ud fra resultatet og overskriver ikke-committede ændringer i sporede filer (#2).

Fast-forward-merges

Når den aktuelle HEAD er en forfader til target-branchen, udfører Pitmaster en fast-forward-merge: den flytter blot HEAD til target-committen uden at oprette en merge-commit.

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

Det omvendte tilfælde registreres endnu ikke: Hvis du merger en branch, som HEAD allerede indeholder, oprettes der en ny merge-commit med et uændret tree i stedet for at rapportere "Already up to date" (#6).

Trevejsfletning

Når brancher er divergeret, udfører Pitmaster en trevejs-merge:

  1. Find merge-basen (den laveste fælles forfader)
  2. Sammenlign base-to-ours og base-to-theirs på tree-niveau
  3. Udfør en merge på indholdsniveau for hver fil, der er ændret i begge brancher
  4. Opret en merge-commit med to forældre
Kode
Before:              After merge:

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

Merge-base

Merge-basen er den seneste fælles forfader til to commits. Pitmaster finder den ved at gennemgå commit-grafen. Hvis der er flere merge-baser (krydsende historik), merger merge() dem først og bruger resultatet som base.

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

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

Sådan får du direkte adgang til algoritmen 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);

Kontrol af branch-merge

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
}

Konfliktmarkører

Når en fil er ændret i begge brancher, og ændringerne overlapper, genererer Pitmaster standardkonfliktmarkører. merge() angiver siderne som HEAD og navnet på den mergede branch:

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

Klassen ConflictMarker indrammer to versioner med markører:

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

Trevejs-merge på indholdsniveau

Klassen ThreeWayMerge merger blob-indhold (ikke filer):

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

Den tager tre strenge (baseversionen, vores version og deres version) samt valgfrie etiketter og en konfliktstil ('merge' som standard eller 'diff3' for også at inkludere basissektionen).

Merge-strategier

Rekursiv merge

Standardstrategien ($repo->merge($branch)) håndterer krydsende merge-historik med flere merge-baser. CLI accepterer også -s ort og -s recursive til den.

Ours-merge

Registrerer en merge-commit, der bevarer HEAD's tree uændret. Nyttigt til at vedligeholde branchhistorik uden at indarbejde ændringer.

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

Octopus-merge

Merger to eller flere brancher i én merge-commit. Hvis en branch er i konflikt, returneres et urent resultat uden at skrive mergestatus eller oprette en commit.

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

Klasserne RecursiveMerge, OursMerge og OctopusMerge i Pitmaster\Merge er hjælpere til indholdsniveau; Repository implementerer selv strategierne ovenfor.

Rerere (genbrug af registreret konfliktløsning)

Pitmaster indeholder en selvstændig Rerere-klasse, der gemmer konfliktløsninger i git's rr-cache-layout:

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() kalder den ikke: Konfliktløsninger registreres og afspilles kun, når din kode gør det. isEnabled() leder efter den bogstavelige tekst rerere.enabled = true i .git/config og genkender ikke sektionen [rerere], som git config rerere.enabled true skriver (#20).

Merge-forløb på tree-niveau

Hele merge-processen:

  1. MergeBase::findAll() finder de fælles forfædre
  2. Base-, ours- og theirs-trees sammenlignes sti for sti
  3. For filer, der kun er ændret i én branch: Brug den ændring
  4. For filer, der er ændret i begge brancher:
    • Hvis begge er ændret på samme måde: Brug en af dem (de stemmer overens)
    • Hvis ændringerne er forskellige: Kør ThreeWayMerge::merge() på indholdet
  5. Hvis alle merges er uden konflikter: Byg det mergede tree, og opret en merge-commit
  6. Hvis der er konflikter: Returner MergeResult med clean = false og konfliktstierne

Brug af CLI

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

Output ved succes:

Kode
Merge successful.

Output ved konflikt:

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