Pular para o conteúdo

Mesclagem

Pitmaster realiza mesclagens de branches com uma mesclagem de três vias, como git merge. Detecta oportunidades de avanço rápido, calcula bases de mesclagem, realiza mesclagens no nível da árvore e do conteúdo e gera marcadores de conflito padrão quando há conflitos nos arquivos.

Mesclagem básica

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

A classe MergeResult é um objeto de valor somente leitura:

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)

Quando a mesclagem para por causa de conflitos, o estado do conflito é gravado no índice e na árvore de trabalho. Resolva os arquivos, add() e chame $repo->mergeContinue(), ou chame $repo->mergeAbort() para restaurar o estado anterior à mesclagem.

Antes de mesclar, faça commit ou guarde as alterações locais: a mesclagem recria a árvore de trabalho a partir do resultado e sobrescreve alterações não commitadas em arquivos rastreados (#2).

Mesclagens por avanço rápido

Quando o HEAD atual é um ancestral da branch de destino, Pitmaster realiza uma mesclagem por avanço rápido: simplesmente move o HEAD para o commit de destino sem criar um commit de mesclagem.

Código
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

O caso inverso ainda não é detectado: mesclar uma branch que o HEAD já contém cria um novo commit de mesclagem com uma árvore inalterada, em vez de informar "Already up to date" (#6).

Mesclagem de três vias

Quando as branches divergiram, Pitmaster realiza uma mesclagem de três vias:

  1. Encontrar a base de mesclagem (ancestral comum mais próximo)
  2. Fazer diff da base até a nossa versão e da base até a deles no nível da árvore
  3. Para cada arquivo alterado em ambas as branches, realizar a mesclagem no nível do conteúdo
  4. Criar um commit de mesclagem com dois pais
Código
Before:              After merge:

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

Base de mesclagem

A base de mesclagem é o ancestral comum mais recente de dois commits. Pitmaster a encontra percorrendo o grafo de commits. Quando há várias bases de mesclagem (histórico cruzado), merge() as mescla primeiro e usa o resultado como base.

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

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

Para acessar diretamente o algoritmo de 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);

Verificação de mesclagem de branches

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
}

Marcadores de conflito

Quando um arquivo é modificado em ambas as branches e as alterações se sobrepõem, Pitmaster gera marcadores de conflito padrão. merge() identifica os lados como HEAD e o nome da branch mesclada:

Código
<<<<<<< HEAD
    return $this->value + 1;
=======
    return $this->value * 2;
>>>>>>> feature

A classe ConflictMarker envolve duas versões em marcadores:

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

Mesclagem de três vias no nível do conteúdo

A classe ThreeWayMerge mescla o conteúdo de blobs (não arquivos):

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

Ela recebe três strings (a versão base, a nossa versão e a versão deles), além de rótulos opcionais e um estilo de conflito ('merge' por padrão ou 'diff3' para incluir a seção da base).

Estratégias de mesclagem

Mesclagem recursiva

A estratégia padrão ($repo->merge($branch)) trata históricos de mesclagem cruzada com várias bases de mesclagem. CLI também aceita -s ort e -s recursive para essa estratégia.

Mesclagem nossa

Registra um commit de mesclagem que mantém inalterada a árvore do HEAD. Útil para manter o histórico da branch sem incorporar alterações.

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

Mesclagem octopus

Mescla duas ou mais branches em um único commit de mesclagem. Se houver conflito em qualquer branch, retorna um resultado não limpo sem gravar o estado da mesclagem nem criar um commit.

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

As classes RecursiveMerge, OursMerge e OctopusMerge em Pitmaster\Merge são auxiliares para mesclagem no nível do conteúdo; Repository implementa as estratégias acima por conta própria.

Rerere (reutilizar resolução registrada)

Pitmaster inclui uma classe Rerere independente que armazena resoluções no layout de rr-cache de 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() não a chama: as resoluções só são registradas e reaplicadas quando seu código faz isso. isEnabled() procura o texto literal rerere.enabled = true em .git/config e não reconhece a seção [rerere] que git config rerere.enabled true grava (#20).

Fluxo de mesclagem no nível da árvore

O processo completo de mesclagem:

  1. MergeBase::findAll() localiza os ancestrais comuns
  2. As árvores da base, nossa e deles são comparadas caminho por caminho
  3. Para arquivos alterados em apenas uma branch: aplicar essa alteração
  4. Para arquivos alterados em ambas as branches:
    • Se ambas alteraram da mesma forma: aplicar qualquer uma (são iguais)
    • Se as alterações forem diferentes: executar ThreeWayMerge::merge() no conteúdo
  5. Se todas as mesclagens forem limpas: criar a árvore mesclada e criar um commit de mesclagem
  6. Se houver conflitos: retornar MergeResult com clean = false e os caminhos em conflito

Uso de CLI

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

Saída em caso de sucesso:

Código
Merge successful.

Saída em caso de conflito:

Código
CONFLICT in: src/File.php, src/Other.php