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
$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:
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.
Before: After fast-forward:
A---B (main) A---B---C---D (main)
\
C---D (feature)
$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:
- Encontrar a base de mesclagem (ancestral comum mais próximo)
- Fazer diff da base até a nossa versão e da base até a deles no nível da árvore
- Para cada arquivo alterado em ambas as branches, realizar a mesclagem no nível do conteúdo
- Criar um commit de mesclagem com dois pais
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.
$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:
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
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:
<<<<<<< HEAD
return $this->value + 1;
=======
return $this->value * 2;
>>>>>>> feature
A classe ConflictMarker envolve duas versões em marcadores:
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):
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.
$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.
$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:
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:
MergeBase::findAll()localiza os ancestrais comuns- As árvores da base, nossa e deles são comparadas caminho por caminho
- Para arquivos alterados em apenas uma branch: aplicar essa alteração
- 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
- Se todas as mesclagens forem limpas: criar a árvore mesclada e criar um commit de mesclagem
- Se houver conflitos: retornar
MergeResultcomclean = falsee os caminhos em conflito
Uso de CLI
./vendor/bin/pitmaster merge feature/login
Saída em caso de sucesso:
Merge successful.
Saída em caso de conflito:
CONFLICT in: src/File.php, src/Other.php