507 lines
12 KiB
PHP
507 lines
12 KiB
PHP
<?php
|
|
/**
|
|
* <pre>
|
|
* Invision Power Services
|
|
* IP.Board v3.4.8
|
|
* Three Way Merge Class
|
|
* Last Updated: $Date: 2012-05-10 16:10:13 -0400 (Thu, 10 May 2012) $ BY $author$
|
|
* </pre>
|
|
*
|
|
* @author Matt Mecham
|
|
* @copyright (c) 2001 - 2010 Invision Power Services, Inc.
|
|
* @license http://www.invisionpower.com/company/standards.php#license
|
|
* @package IP.Board
|
|
* @link http://www.invisionpower.com
|
|
* @version $Rev: 10721 $
|
|
*
|
|
*/
|
|
|
|
class ThreeWayMerge
|
|
{
|
|
/**
|
|
* Original text
|
|
* For example, template bit from 3.0.5
|
|
*
|
|
* @var array
|
|
*/
|
|
protected $_ORIG = array();
|
|
|
|
/**
|
|
* New text
|
|
* For example, template bit from 3.1.0
|
|
*
|
|
* @var array
|
|
*/
|
|
protected $_NEW = array();
|
|
|
|
/**
|
|
* Custom text
|
|
* For example, user customized template bit
|
|
*
|
|
* @var array
|
|
*/
|
|
protected $_CUSTOM = array();
|
|
|
|
/**
|
|
* Debug messages
|
|
*
|
|
* @var array
|
|
*/
|
|
public $DEBUG = array();
|
|
|
|
/**
|
|
* Diff type
|
|
*
|
|
* @var string
|
|
*/
|
|
public $diffType = 'lite';
|
|
|
|
/**
|
|
* Construct
|
|
*
|
|
* @param string Original Text
|
|
* @param string New Text
|
|
* @param string Custom Text
|
|
* @param string Method ('lite' or 'ipb')
|
|
* @return @e void
|
|
*/
|
|
public function __construct( $ORIG, $NEW, $CUSTOM, $mType='lite' )
|
|
{
|
|
$this->_ORIG = ( ! is_array( $ORIG ) ) ? explode( "\n", str_replace( "\r\n", "\n", trim( $ORIG ) ) ) : $ORIG;
|
|
$this->_NEW = ( ! is_array( $NEW ) ) ? explode( "\n", str_replace( "\r\n", "\n" , trim( $NEW ) ) ) : $NEW;
|
|
$this->_CUSTOM = ( ! is_array( $CUSTOM ) ) ? explode( "\n", str_replace( "\r\n", "\n" , trim( $CUSTOM ) ) ) : $CUSTOM;
|
|
|
|
if ( $mType != 'lite' )
|
|
{
|
|
$this->diffType = $mType;
|
|
}
|
|
|
|
/* Set include path.. */
|
|
@set_include_path( IPS_KERNEL_PATH . 'PEAR/' );/*noLibHook*/
|
|
|
|
/* OMG.. too many PHP 5 errors under strict standards */
|
|
if ( IN_DEV )
|
|
{
|
|
$oldReportLevel = error_reporting( 0 );
|
|
error_reporting( $oldReportLevel ^ E_STRICT );
|
|
}
|
|
else
|
|
{
|
|
error_reporting( 0 );
|
|
}
|
|
|
|
/* ugh */
|
|
if ( $this->diffType == 'lite' )
|
|
{
|
|
require_once 'Text/Diff3.php';/*noLibHook*/
|
|
require_once 'Text/Diff/Renderer.php';/*noLibHook*/
|
|
require_once 'Text/Diff/Renderer/inline.php';/*noLibHook*/
|
|
}
|
|
else
|
|
{
|
|
require_once 'Text/Diff.php';/*noLibHook*/
|
|
require_once 'Text/Diff/Renderer.php';/*noLibHook*/
|
|
require_once 'Text/Diff/Renderer/ips3waymerge.php';/*noLibHook*/
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Perform the merge and return the result
|
|
*
|
|
* On conflict, will throw an exception with a message of CONFLICT
|
|
*
|
|
* @return @e string
|
|
* @throws Exception
|
|
*/
|
|
|
|
public function merge()
|
|
{
|
|
$this->_addDebugMsg( '---- START--o---', $this->_ORIG );
|
|
$this->_addDebugMsg( '---- START--n---', $this->_NEW );
|
|
$this->_addDebugMsg( '---- START--c---', $this->_CUSTOM );
|
|
|
|
if ( $this->diffType == 'lite' )
|
|
{
|
|
/* Create the Diff object. */
|
|
$diff = new Text_Diff3( $this->_ORIG, $this->_NEW, $this->_CUSTOM );
|
|
|
|
/* Output the diff in unified format. */
|
|
return $this->_postProcessQuick( implode( "\n", $diff->mergedOutput( 'XXXNEWXXX', 'XXXCUSTOMXXX' ) ) );
|
|
}
|
|
else
|
|
{
|
|
/* ORIG - NEW */
|
|
$diff = new Text_Diff( 'auto', array( $this->_ORIG, $this->_NEW ) );
|
|
$renderer = new Text_Diff_Renderer_ips3waymerge();
|
|
$orig_new = explode( "\n", trim( $renderer->render($diff) ) );
|
|
|
|
|
|
/* ORIG - CUSTOM */
|
|
$diff = new Text_Diff( 'auto', array( $this->_ORIG, $this->_CUSTOM ) );
|
|
$renderer = new Text_Diff_Renderer_ips3waymerge();
|
|
$orig_custom = explode( "\n", trim( $renderer->render($diff) ) );
|
|
|
|
|
|
/* Process the arrays */
|
|
$orig_new_map = $this->_fetchProcessed( $orig_new, 1 );
|
|
$orig_custom_map = $this->_fetchProcessed( $orig_custom );
|
|
|
|
/* Add to debug */
|
|
$this->_addDebugMsg( '---- OLD DEFAULT------', $this->_ORIG );
|
|
$this->_addDebugMsg( '---- NEW DEFAULT------', $this->_NEW );
|
|
$this->_addDebugMsg( '---- USER DEFAULT------', $this->_CUSTOM );
|
|
$this->_addDebugMsg( '---- OLD DEFAULT > NEW DEFAULT DIFF------', $orig_new_map );
|
|
$this->_addDebugMsg( '---- OLD DEFAULT > USER CUSTOM DIFF------', $orig_custom_map );
|
|
|
|
/* Process the thing */
|
|
try
|
|
{
|
|
$return = $this->_processMerge( $orig_new_map, $orig_custom_map );
|
|
|
|
return $return;
|
|
}
|
|
catch( Exception $e )
|
|
{
|
|
throw new Exception( $e->getMessage() );
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Post process merge (quick)
|
|
*
|
|
* @param string Merge text
|
|
* @return @e string
|
|
*/
|
|
protected function _postProcessQuick( $text )
|
|
{
|
|
/* INIT */
|
|
$count = 0;
|
|
//print $text; exit();
|
|
/* MATCH */
|
|
preg_match_all( "#<<<<<<< XXXNEWXXX(.+?)=======(.+?)>>>>>>> XXXCUSTOMXXX#si", $text, $matches );
|
|
|
|
if ( is_array($matches) AND count($matches) )
|
|
{
|
|
foreach( $matches[1] as $index => $m )
|
|
{
|
|
/* Yeah, I like readable code and copying and pasting evidently */
|
|
$_all = $matches[0][$index];
|
|
$_new = trim( $matches[1][$index], "\n" );
|
|
$_custom = trim( $matches[2][$index], "\n" );
|
|
|
|
$_new = "<ips:conflict id=\"{$count}\">\n<ips:cblock type=\"original\">\n" . $_custom . "\n</ips:cblock>\n<ips:cblock type=\"new\">\n" . $_new . "\n</ips:cblock>\n</ips:conflict>";
|
|
|
|
$text = str_replace( $_all, $_new, $text );
|
|
|
|
$count++;
|
|
}
|
|
}
|
|
|
|
return $text;
|
|
}
|
|
|
|
/**
|
|
* Process merge
|
|
* Takes the two maps (old-new, old-custom) and merges.
|
|
* Will throw an error on conflict.
|
|
*
|
|
* @param array old-new map
|
|
* @param array old-custom map
|
|
* @return @e string
|
|
*/
|
|
protected function _processMerge( $orig_new_map, $orig_custom_map )
|
|
{
|
|
$len = count( $this->_ORIG );
|
|
$c = 0;
|
|
$conflict = 0;
|
|
$final = array();
|
|
|
|
while( $c <= $len )
|
|
{
|
|
$left = $this->_sliceFromLineNumber( $orig_new_map, $c );
|
|
$right = $this->_sliceFromLineNumber( $orig_custom_map, $c );
|
|
|
|
if ( $left['type'] == 'ok' AND $right['type'] == 'ok' )
|
|
{
|
|
$test1 = $this->_textToAddAtLine( $orig_new_map, $c );
|
|
$test2 = $this->_textToAddAtLine( $orig_custom_map, $c );
|
|
|
|
/* Potential conflict, are both modifications to this line the same? */
|
|
if ( count( $test1 ) AND count( $test2 ) AND implode( "\n", $test1 ) != implode( "\n", $test2 ) )
|
|
{
|
|
$conflict++;
|
|
|
|
$final[] = "<ips:conflict id=\"{$conflict}\">";
|
|
$final[] = "<ips:cblock type=\"original\">";
|
|
|
|
if ( count( $test2 ) )
|
|
{
|
|
foreach( $test2 as $t )
|
|
{
|
|
$final[] = $t;
|
|
}
|
|
}
|
|
|
|
$final[] = "</ips:cblock>";
|
|
$final[] = "<ips:cblock type=\"new\">";
|
|
|
|
if ( count( $test1 ) )
|
|
{
|
|
foreach( $test1 as $t )
|
|
{
|
|
$final[] = $t;
|
|
}
|
|
}
|
|
|
|
$final[] = "</ips:cblock>";
|
|
$final[] = "</ips:conflict>";
|
|
}
|
|
else
|
|
{
|
|
if ( count( $test1 ) )
|
|
{
|
|
foreach( $test1 as $t )
|
|
{
|
|
$final[] = $t;
|
|
}
|
|
}
|
|
|
|
if ( count( $test2 ) )
|
|
{
|
|
foreach( $test2 as $t )
|
|
{
|
|
//$final[] = $t;
|
|
}
|
|
}
|
|
|
|
$final[] = $left['data'];
|
|
}
|
|
}
|
|
else if ( $left['type'] == 'del' AND $right['type'] == 'ok' )
|
|
{
|
|
/* Add in anything from left */
|
|
$final = $this->_textToAddAtLine( $orig_new_map, $c, $final, 'lnxx' );
|
|
|
|
/* One line or more exists in both? */
|
|
$final = $this->_textToAddAtLine( $orig_custom_map, $c, $final, 'lc--' );
|
|
}
|
|
else if ( $left['type'] == 'ok' AND $right['type'] == 'del' )
|
|
{
|
|
/* Add in right */
|
|
$final = $this->_textToAddAtLine( $orig_custom_map, $c, $final, 'rcxx' );
|
|
|
|
/* Anything to add from the new side? */
|
|
$final = $this->_textToAddAtLine( $orig_new_map, $c, $final, 'rn--' );
|
|
}
|
|
else if ( $left['type'] == 'del' AND $right['type'] == 'del' )
|
|
{
|
|
$test1 = $this->_textToAddAtLine( $orig_new_map, $c );
|
|
$test2 = $this->_textToAddAtLine( $orig_custom_map, $c );
|
|
|
|
/* Potential conflict, are both modifications to this line the same? */
|
|
if ( count( $test1 ) AND count( $test2 ) AND implode( "\n", $test1 ) != implode( "\n", $test2 ) )
|
|
{
|
|
$conflict++;
|
|
|
|
$final[] = "<ips:conflict id=\"{$conflict}\">";
|
|
$final[] = "<ips:cblock type=\"original\">";
|
|
|
|
if ( count( $test2 ) )
|
|
{
|
|
foreach( $test2 as $t )
|
|
{
|
|
$final[] = $t;
|
|
}
|
|
}
|
|
|
|
$final[] = "</ips:cblock>";
|
|
$final[] = "<ips:cblock type=\"new\">";
|
|
|
|
if ( count( $test1 ) )
|
|
{
|
|
foreach( $test1 as $t )
|
|
{
|
|
$final[] = $t;
|
|
}
|
|
}
|
|
|
|
$final[] = "</ips:cblock>";
|
|
$final[] = "</ips:conflict>";
|
|
}
|
|
else
|
|
{
|
|
if ( count( $test1 ) )
|
|
{
|
|
foreach( $test1 as $t )
|
|
{
|
|
/* We're removing, dumbass */
|
|
//$final[] = $c . ' dexx1' . $t;
|
|
}
|
|
}
|
|
|
|
if ( count( $test2 ) )
|
|
{
|
|
foreach( $test2 as $t )
|
|
{
|
|
/* We're removing, dumbass */
|
|
//$final[] = $c . ' dexx2 ' . $t;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
$c++;
|
|
}
|
|
|
|
return implode( "\n", $final );
|
|
}
|
|
|
|
/**
|
|
* Returns an array slice for the ORIG line number X
|
|
*
|
|
* @param array Re-processed Diff Array
|
|
* @param int ORIG line number
|
|
* @return @e mixed
|
|
*/
|
|
protected function _sliceFromLineNumber( $data, $n )
|
|
{
|
|
foreach ( $data as $i )
|
|
{
|
|
if ( $i['line'] == $n AND $i['type'] != 'ins' )
|
|
{
|
|
return $i;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Returns an array of lines to add at the current ORIG line number X
|
|
*
|
|
* @param array Re-processed Diff Array
|
|
* @param int ORIG line number
|
|
* @param array Array to add to
|
|
* @param string Newline data (not used)
|
|
* @return @e array
|
|
*/
|
|
protected function _textToAddAtLine( $data, $n, $return=array(), $p='' )
|
|
{
|
|
$return = ( is_array( $return ) AND count( $return ) ) ? $return : array();
|
|
|
|
foreach ( $data as $i )
|
|
{
|
|
if ( $i['line'] == $n AND $i['type'] == 'ins' )
|
|
{
|
|
$return[] = $i['data'];
|
|
}
|
|
}
|
|
|
|
return $return;
|
|
}
|
|
|
|
/**
|
|
* Re-process DIFF.
|
|
* Figures out mappings to original line numbers, etc
|
|
*
|
|
* @param array Array of DIFF data
|
|
* @param boolean Log or not (unused)
|
|
* @return @e array
|
|
*/
|
|
protected function _fetchProcessed( $data, $log=0 )
|
|
{
|
|
/* Counter to ensure unique entry in $return */
|
|
$c = 0;
|
|
|
|
/* Counter to keep a corrilation with the original line numbering */
|
|
$n = 0;
|
|
|
|
/* Variable to retain value of $n when processing insertion rows */
|
|
$old_n = 0;
|
|
|
|
/* Flag to note if the previous row was a delete and therefore treat a proceeding insert as a replacement */
|
|
$delete_was_last = 0;
|
|
|
|
/* Freeze the line number when we first see a delete line (assume that more may follow if removing a block */
|
|
$freeze = 0;
|
|
|
|
foreach( $data as $l )
|
|
{
|
|
if ( substr( $l, 0, 1 ) == '+' )
|
|
{
|
|
/* AAA: If the last line was a delete, this is treated as a replacement */
|
|
if ( $delete_was_last )
|
|
{
|
|
/* We want to ensure that the insert retains the same ORIGINAL line number as the FIRST line of the recent delete(s)
|
|
So we effectively 'freeze' N for the inserts and store what would be the next number in $old_n */
|
|
$old_n = ( $old_n ) ? $old_n : $n;
|
|
$n = $freeze;
|
|
}
|
|
else if ( $freeze )
|
|
{
|
|
/* Ok, the previous was an insert after a delete line, so use the line number that would have been the first delete line number */
|
|
$n = $old_n;
|
|
}
|
|
|
|
$return[ $c ] = array( 'data' => substr( $l, 1 ),
|
|
'type' => 'ins',
|
|
'line' => $n );
|
|
|
|
/* Delete was not last. Reset freeze flag */
|
|
$delete_was_last = 0;
|
|
$freeze = 0;
|
|
}
|
|
else if ( substr( $l, 0, 1 ) == '-' )
|
|
{
|
|
$return[ $c ] = array( 'data' => substr( $l, 1 ),
|
|
'type' => 'del',
|
|
'line' => $n );
|
|
|
|
/* We want to 'freeze' the line number if this is the first delete line */
|
|
$freeze = ( $delete_was_last ) ? $freeze : $n;
|
|
|
|
/* AAA: We've just deleted a line, so flag this up because if an insert is next, then it's a replacement */
|
|
$delete_was_last = 1;
|
|
|
|
/* BBB: We deleted a line from the original, so increment the original line number */
|
|
$n++;
|
|
}
|
|
else
|
|
{
|
|
/* If this is a line after an insert or delete, use the old_n line number */
|
|
if ( $old_n )
|
|
{
|
|
$n = $old_n;
|
|
}
|
|
|
|
$return[ $c ] = array( 'data' => $l,
|
|
'type' => 'ok',
|
|
'line' => $n );
|
|
|
|
$n++;
|
|
$delete_was_last = 0;
|
|
$freeze = 0;
|
|
$old_n = 0;
|
|
}
|
|
|
|
$c++;
|
|
}
|
|
|
|
//print_r( $return );
|
|
return $return;
|
|
}
|
|
|
|
/**
|
|
* Add debug messages
|
|
*
|
|
* @param string TITLE
|
|
* @param mixed Data
|
|
* @return @e void
|
|
*/
|
|
protected function _addDebugMsg( $title, $data )
|
|
{
|
|
$this->_DEBUG[] = $title . "\n" . var_export( $data, TRUE );
|
|
}
|
|
|
|
} |