/*
* Copyright 2002 Perforce Software. All rights reserved.
*
* This file is part of Perforce - the FAST SCM System.
*/
/*
* MultiMerge - merge a bunch of files and return a merged list
*/
# include <stdhdrs.h>
# include <error.h>
# include <errorlog.h>
# include <strbuf.h>
# include <filesys.h>
# include <debug.h>
# include <readfile.h>
# include <diffsp.h>
# include "diffan.h"
# include "diff.h"
# include "diffmulti.h"
/*
* MergeSequence -- FileSys, (diff) Sequence, and id
*
* MergeSequence just holds diff Sequence and its FileSys together,
* along with a revision number for identification.
*
* Note that ~MergeSequence() deletes both the FileSys and Sequence.
*/
struct MergeSequence {
MergeSequence() { s = 0; f = 0; }
~MergeSequence() { delete s; delete f; }
FileSys *f;
Sequence *s;
int revId;
int chgId;
} ;
/*
* MergeLine::AddLines() - insert new lines into a MergeLine chain
*
* Inserts into the chain at the point of *p lines x thru y of sequence f.
* (i.e. inserts between p and *p).
*
* p points to the previous entry's 'next' pointer for easy manipulation,
* and is left pointing to the 'next' pointer of the last entry added.
*
* All lines are marked with lowerRev = upperRev = current rev.
*/
MergeLine **
MergeLine::AddLines(
MergeLine **p,
MergeSequence *f,
MultiMerge *m,
LineNo x,
LineNo y )
{
// Seek once, then copy all lines
f->s->SeekLine( x );
// From x to y
while( x < y )
{
MergeLine *c = new MergeLine;
// Get line text.
// XXX LineTypeRaw?
// XXX Impose junky limit on lines
LineLen l = f->s->Length( x );
if( l > 10000 )
l = 10000;
f->s->CopyLines( x, x + 1, c->buf.Alloc( l ), l, LineTypeRaw );
// New lines have this MergeSequence's revId
c->lowerRev =
c->upperRev = f->revId;
c->lowerChg =
c->upperChg = f->chgId;
c->from = 0;
c->merge = m;
// Dance to insert between p and *p, moving p up
c->next = *p;
*p = c;
p = &c->next;
}
// Return last entry's 'next'
return p;
}
/*
* MergeLines::MarkLines() - mark current lines in chain with last rev seen
*
* For lines at the rev of the previous file, mark them with the given
* rev. We ignore lines whose rev is less than the rev of the previous
* file, as they didn't participate in the diff we're now merging.
*/
MergeLine **
MergeLine::MarkLines( MergeLine **p, LineNo count,
int prevRev, int nextRev, int prevChg, int nextChg )
{
for( ; count; p = &(*p)->next )
if( (*p)->upperChg == prevChg )
{
(*p)->upperRev = nextRev;
(*p)->upperChg = nextChg;
--count;
}
return p;
}
/*
* MultiMerge::MultiMerge() - minimal initialization
*/
MultiMerge::MultiMerge( StrPtr *diffFlags )
: flags( diffFlags )
{
fx = 0;
chain = 0;
reader = 0;
index = 0;
}
/*
* MultiMerge::~MultiMerge() - zonk MergeLine chain and leftover MergeSequence
*/
MultiMerge::~MultiMerge()
{
delete fx;
// Delete what's left of the chain
while( chain )
{
MergeLine *next = chain->next;
delete chain;
chain = next;
}
}
/*
* MultiMerge::Add() - add another file for delta display
*/
void
MultiMerge::Add( FileSys *f, int revId, int chgId, Error *e )
{
// Make next sequence. We need a pair for diffing.
MergeSequence *fy = new MergeSequence;
// We use chgId for sorting because it's useful across branches,
// but spec depots don't have changes, so fake it with revId.
if ( chgId < 1 )
chgId = revId;
fy->f = f;
fy->s = new Sequence( fy->f, flags, e );
fy->revId = revId;
fy->chgId = chgId;
if( e->Test() )
{
delete fy;
return;
}
// If we don't have a previous sequence,
// we'll just add this one wholesale.
if( !fx )
{
fx = fy;
MergeLine::AddLines( &chain, fx, this, 0, fx->s->Lines() );
reader = chain;
return;
}
// Create the diff between last and current sequence,
// and walk the snake, merging the new sequence with the chain.
DiffAnalyze *diff = new DiffAnalyze( fx->s, fy->s );
Snake *s = diff->GetSnake();
Snake *t;
// We walk with *p, so that we can update the
// head (chain) or next easily.
MergeLine **p = &chain;
for( ; t = s->next; s = t )
{
// I'm sure you forgot:
// s->x -> s->u common lines in fx leading to next diff
// s->y -> s->v common lines in fy leading to next diff
// s->u -> t->x lines unique to fx
// s->v -> t->y lines unique to fy
// Mark old lines s->x -> s->u as current (new revId)
// Mark old lines s->u -> t->x as no longer current (old revId)
// Insert fy's lines s->v -> t->y as current
p = MergeLine::MarkLines( p, s->u - s->x,
fx->revId, fy->revId, fx->chgId, fy->chgId );
p = MergeLine::MarkLines( p, t->x - s->u,
fx->revId, fx->revId, fx->chgId, fx->chgId );
p = MergeLine::AddLines( p, fy, this, s->v, t->y );
}
// Mark (last) lines s->x -> s->u as current.
p = MergeLine::MarkLines( p, s->u - s->x,
fx->revId, fy->revId, fx->chgId, fy->chgId );
// Done diffing fx & fy.
// So fy becomes fx for the next round.
delete diff;
delete fx;
fx = fy;
// For Read()
// Gets set many times, but so what.
reader = chain;
}
/*
* MultiMerge::Dump() - dump MergeLine chain for debugging
*/
void
MultiMerge::Dump()
{
MergeLine *c;
for( c = chain; c; c = c->next )
{
c->buf.Terminate();
p4debug.printf("%d->%d %s", c->lowerRev, c->upperRev, c->buf.Text() );
}
}
/*
* MultiMerge::Read() - extra the merged result, a line at a time
*/
MergeLine *
MultiMerge::Read( int &lower, int &upper, StrPtr &string, int chg )
{
if( !reader )
return 0;
// For deep annotate, get lower from earliest integ source.
// If no deep annotate, from is 0, so this is harmless.
MergeLine *source = reader;
while ( source->from )
source = source->from;
if ( chg )
{
lower = source->lowerChg;
upper = reader->upperChg;
}
else
{
lower = source->lowerRev;
upper = reader->upperRev;
}
string = reader->buf;
MergeLine *r = reader;
reader = reader->next;
return r;
}
# if 0
// sample use
main( int argc, char *argv[] )
{
argc--, argv++;
MultiMerge dd;
int revId = 0;
Error e;
while( argc-- )
{
FileSys *f = FileSys::Create( FST_BINARY );
f->Set( *argv++ );
++revId;
dd.Add( f, revId, revId, &e );
}
AssertLog.Abort( &e );
dd.Dump();
return 0;
}
# endif