FileHead.cpp #14

  • //
  • guest/
  • sam_stafford/
  • p4hl/
  • src/
  • dlls/
  • FileHead.cpp
  • View
  • Commits
  • Open Download .zip Download (9 KB)
// FileHead.cpp: implementation of the FileHead class.
//
//////////////////////////////////////////////////////////////////////

#include "FileHead.h"

//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////

/* When a FileHead is constructed, it immediately populates itself with
 * all the necessary FileRev objects to make it a complete representation
 * of this file.  Integration records are not evaluated at this time. */
FileHead::FileHead(StrBuf path, FileLogCache* newparent)
{
	file = path; //This is the path to the file.
	next = NULL;
	head = NULL;
	tail = NULL;

	next_from = NULL;
	next_into = NULL;

	parent = newparent; //The FileLogCache that created this FileHead.
	
	if (parent->errors->IsFatal()) return;

	ClientLogUser ui;
	ui.working = this; //Tell the ClientLogUser to add revisions to this FileHead.
		
	char* args[1]; //An array that will hold the arguments to the p4 command.
	args[0] = path.Text(); //Path to the file - this will be the arg to p4 filelog.
	
	parent->client->SetArgv(1, args); //Set the argument list to be the file path.
	parent->client->Run( "filelog", &ui ); //Run "p4 filelog <file>".

	/* At this point, the ClientLogUser takes care of creating FileRevs and
	 * arranging them.  By the time the filelog command finishes executing, it's
	 * all done. */
	
	/* Check connection and bail if dropped. */
	if (parent->client->Dropped())
	{
		parent->client->Final(parent->errors);

	}
}

/* The destructor - deletes all of its FileRevs as well as its neighboring FileHeads. */
FileHead::~FileHead()
{
	if (head != NULL) {
		delete head; //deleting the head FileRev deletes them all.
	}
	if (next != NULL) {
		delete next; //pass the deletion message on to the next FileHead.
	}
}

/* addRev is called from within ClientLogUser each time a new revision is given in
 * the filelog.  It adds a revision to the tail of the chain of FileRevs. */
void FileHead::addRev(StrBuf newrev, StrBuf newchange, short type, bool istext)
{
	if (head != NULL) //if we already have one or more FileRevs here
	{
		tail->next = new FileRev(newrev, this, newchange, type, istext); //tack this on the end
		tail->next->prev = tail; //set its prev pointer

		/* add appropriate "edit" FileRevArrow */
		tail->AddFromArrow(tail->next, edit);

		tail = tail->next; //update the tail pointer to reflect the addition
	}
	else //this is the first revision we're adding
	{
		head = new FileRev(newrev, this, newchange, type, istext); //make it the head
		tail = head; //and make it the tail also
	}
	//add the change number to the list
	parent->changes->AddChange(newchange);
}

/* See the header file - scanInto is called when another FileHead has determined that
 * this one is its descendant.  The first argument indicates at what point this FileHead
 * received content from the donor FileHead, and the second argument indicates what specific
 * revision that integration was from. 
 * Note that scanInto operates on all the revisions from startrev-head.  Note also that
 * a FileHead's revisions start with the head and work backwards, so we'll be starting with
 * the head revision and working down until we hit startrev - when we hit startrev, we point it
 * at the "caller" FileRev and return. */
void FileHead::scanInto(StrBuf startrev, FileRev* caller, ArrowType atype)
{
	bool credit = false; //Don't start looking yet.
	FileRev* foo = tail; //foo is the FileRev we're looking at.
	FileTextArrow* text;

	/* First, precache all of the descendant branches. */
	
	while (foo != NULL) //Unless we hit the end of the line...
	{
		if (foo->rev == startrev) { //if this is the startrev
			credit = true; //begin precaching
		}
		if (credit)
		{
			if (foo->intocheck) 
			{				//Have we already checked this rev?  If so, don't do it again.
				foo = foo->prev; //move up to the next FileRev.
				continue; //Next loop.
			}
			text = foo->intotexts;
			//If no integ descendants, this will be null.
			while (text)
			{
				text->fhead = parent->Get(text->file, false, this); //get the FileHead
				text = text->next; //next textarrow
			}
		}
		foo = foo->prev; //Move on to the next FileRev.
	}

	/* Now we scanInto all those precached FileHeads. */
	credit = false;
	foo = tail;
	while (foo != NULL) //Unless we hit the end of the line...
	{
		if (foo->rev == startrev) { //if this is the startrev
			credit = true; //begin doing scanIntos
			foo->AddFromArrow(caller, atype); //Set a from pointer to the caller FileRev.
		}
		if (credit)
		{
			if (foo->intocheck) 
			{				//Have we already checked this rev?  If so, don't do it again.
				foo = foo->prev; //move up to the next FileRev.
				continue; //Next loop.
			}
			/* If we reach this point, this rev hasn't been checked yet. */
			text = foo->intotexts;
			/* If foo doesn't have any integ children, these will be empty StrBufs. */
			while (text)
			{
				/* Here we find the FileHead representing the file from the "into"
				 * integ record.  We then scanInto it, giving the file that we're
				 * looking at right now as the "caller" arg, and the revision from
				 * the integ record as the startrev. */
				text->fhead->scanInto(text->rev, foo, text->type);
				text = text->next;
			}
			foo->intocheck = true; //Record the fact that this FileRev has been checked.
		}
		foo = foo->prev; //Move on to the next FileRev.
	}
}

/* scanFrom returns a FileRev* , which is then used to fill the "from" pointer
 * of its caller.  The endrev argument indicates the end of the revision range -
 * in scanFrom, we start at that rev and then work our way down to the earliest
 * revision. */
FileRev* FileHead::scanFrom(StrBuf endrev)
{
	FileTextArrow* text;
	FileRev* foo = tail; //foo is the rev we're currently looking at.
	while (foo != NULL) //for each FileRev...
	{
		if (foo->fromcheck) //If this rev has already been checked
		{
			if (foo->rev == endrev) break; //Is it the last one?
			else foo = foo->prev; //No?  Skip over it and continue.
			continue;
		}
		text = foo->fromtexts;
		while (text)
		{
			text->fhead = parent->Get(text->file, true, this); //precache the associated FileHead
			text = text->next;
		}
		if (foo->fromtexts == NULL)
		{
			foo->fromcheck = true; //no need to try to scan it later
		}
		if (foo->rev == endrev) break; //we're done precaching
		else foo = foo->prev; //otherwise, next FileRev
	}

	/* Now the actual scans. */

	foo = tail;
	while (foo != NULL) //for each FileRev...
	{
		if (foo->fromcheck) //If this rev has already been checked
		{
			if (foo->rev == endrev) return foo; //Is it the one we want?
			else foo = foo->prev; //No?  Skip over it and continue.
			continue;
		}
		text = foo->fromtexts;
		while (text)
		{
			foo->AddFromArrow(text->fhead->scanFrom(text->rev), text->type);
			text = text->next;
		}
		foo->fromcheck = true; //mark this FileRev as having been scanned
		if (foo->rev == endrev) return foo; //all done!  Return it.
		else foo = foo->prev; //otherwise, next FileRev
	}
	return foo; //this line should never be executed.
}

void FileHead::scanMain() 
{
	FileTextArrow* text;
	FileRev* foo = tail; //foo is the FileRev we're looking at.

	/* In order to help make a nicely-arranged branch graph, we're going to
	 * precache all of this file's immediate relatives before tracing them
	 * to find the "extended family". */
	while (foo != NULL) //Unless we hit the end of the line...
	{
		if (!(foo->fromcheck)) 
		{
			text = foo->fromtexts;
			while (text) 
			{
				text->fhead = parent->Get(text->file, true, this); //cache the associated FileHead
				text = text->next;
			}
		}
		if (!(foo->intocheck)) 
		{
			text = foo->intotexts;
			/* If foo doesn't have any integ children, this is null. */
			while (text) //For all files in the "intofiles" list.
			{
				text->fhead = parent->Get(text->file, false, this); //cache the FileHead
				text = text->next; //next file
			}
		}
		foo = foo->prev;
	}

	/* Now we finish it up by actually going through and scanning the immediate
	 * relatives. */
	foo = tail;
	while (foo != NULL)
	{
		if (!(foo->fromcheck))
		{
			foo->fromcheck = true;
			text = foo->fromtexts;
			while (text)
			{
				foo->AddFromArrow(text->fhead->scanFrom(text->rev), text->type);
				text = text->next;
			}
		}
		if (!(foo->intocheck))
		{
			foo->intocheck = true;
			text = foo->intotexts;
			while (text)
			{
				/* Here we find the FileHead representing the file from the "into"
				 * integ record.  We then scanInto it, giving the filerev that we're
				 * looking at right now as the "caller" arg, and the revision from
				 * the integ record as the startrev. */
				text->fhead->scanInto(text->rev, foo, text->type);
				text = text->next;
			}
		}
		foo = foo->prev;
	}
}
# Change User Description Committed
#23 1797 Sam Stafford Fix a permissions-related crashing bug.
#22 1709 Sam Stafford Propagate bug fix and include change from p4hltest.

No functional change - P4HL never used "numarrows" so the bug
doesn't affect it.
#21 1689 Sam Stafford Integrate 02.1 API and code cleanup to P4HL.
 Lots of work.  Phew.
#20 1586 Sam Stafford Migrate outstanding changes into P4HL - no functional change.
#19 1561 Sam Stafford Integ changes to P4HL for testing.
#18 1557 Sam Stafford Integrate bug fix for testing.
 Looks good so far.
#17 1548 Sam Stafford Integrate RevType change to make sure it works.
 It does.
#16 1521 Sam Stafford Integrated change 1520 to P4HL.
 Updated CObjectFile::Expand() to use
the new variable name.

Infrastructure change.
#15 1457 Sam Stafford Use RunTag() to queue up filelog commands.
 Should in theory improve
performance, although I haven't noticed any great difference.

Integration only change.
#14 1450 Sam Stafford Major performance improvement - use one ClientApi connection for all
filelogs.
Improves querying time about tenfold on large requests!

Had to move client->Final() to the constructor to ensure that
connection is cleaned up promptly and doesn't hang things up.
#13 1433 Sam Stafford Integ display: if you see one of a file's revisions, you see them all.
Previously, your view was limited to those revisions which were
directly
related to the file you asked about.  However, if you asked about a
file branched from the mainline, this meant that you couldn't see
mainline changes that weren't yet integrated into your branch, and
that's
not terribly useful.
#12 1431 Sam Stafford Fixed bug with "weight" ("contrib" attribute) of white beams being
drawn
incorrectly - I'd confused myself when I was adding them to
FileHead::addRev().
All better now.
#11 1420 Sam Stafford Dummy integrate changes made thus far back to mainline, so it doesn't
get propagated back there later.
#10 1405 Sam Stafford Phew - this was a big one!

New functionality:
    The rare case in which a revision has multiple parents, due to
    multiple resolves before submit, is now handled properly.  There
    is no limit on the number of "parents" a revision may have.

    Integration lines are now always "weighted" to indicate whether
    they contributed all, some, or none to the target.  For example,
    a "branch" line will be very solid and wide, whereas an "ignore"
    will be thin and faint.

Rearchitecture:
    Now using low-cost structs to keep track of integration information.
    Also being just a little more efficient with scanning through large
    data structures.  Quite a bit of general code bloat trimmed off now
    that some of the kludges are gone.

Possible problems:
    Not sure yet, but it might happen that "duplicate" integration
    pointers will be created, now that it's not a single variable which
    would get overwritten in the event of a duplicate.

to-do:
    Trim off obsolete member variables.  Use more enums and fewer #defs.
#9 1394 Sam Stafford More support for FileRevArrows, including the addition of the
FileTextArrow that will replace the ListList.  As of right now
the old functionality is untouched and I've just been adding
stuff on.

Submitting now because I've reached the point where I have to
start making changes that have a high chance of breaking stuff.
*cringe*
#8 1008 Sam Stafford Fixed a bug with the whole "istext" thing - wasn't setting the bit on
CObjectRevs other than "main", so they'd all default to false.  Now it
seems to be better.  Also cleaned up the style a little bit by including
istext in the constructors, rather than setting it after construction.
#7 1007 Sam Stafford A stab at making P4HL a bit more accessible to Perforce novices.
During the Precache() phase of the P4HL objects, a "p4 info" is
executed.  If this returns any errors, odds are there's no server
there (I can't think of what other error "p4 info" would return).
If this happens, use "public.perforce.com:1666" as the new port
value for all future Perforce transactions during this session,
and send a message to the console explaining this.
#6 981 Sam Stafford scanFrom and scanInto modified to use the "precaching" technique.
All of the tests I've run so far with this code have yielded very
comprehensible graphs, and performance is still very speedy.

This should be the final fix for job 4.
#5 980 Sam Stafford Kludginess removed from scanMain().
 Should be a bit more
efficient now, in theory, but no real functional change (not
checking in the DLL).

Now to fix up scanFrom and scanInto....
#4 975 Sam Stafford Nigh-complete fix for job 4 - the "scan" methods all
go from tail to head now, and Get uses the improved
"addAfter" method where appropriate.

An unforeseen problem was the fact that a complex integ
history can get followed through more than one possible
path, causing later versions of a given file to get scanned
before earlier versions, and messing up the graph.

This is fixed (albeit in a kludgey fashion) in scanMain here
by making two passes through the FileHead, caching the files
on the first pass and actually running scans on the second
pass.  A slightly more efficient way of handling it might be
to keep a list of FileRevs that need to be scanned - perhaps
by declaring a temporary FileHead to serve as a list?  Once
it's been hammered out in scanMain() satisfactorily that method
can be employed in the other scan methods.
#3 974 Sam Stafford Partial fixes to the sorting of a FileLogCache as it's created.
  FileRevs are now doubly-linked. 
  FileHead now has a scanMain() function that acts as an interleaved
   scanFrom and scanInto.

The next step will be to have scans go from #1 to #head (this is
why FileRevs are now doubly linked).
#2 958 Sam Stafford Fixed another bug found at the 2001 Conference.
 This was
one with integ pointers not being drawn correctly.  It turned
out to be a mistake in FileHead::scanInto.  If the "intocheck"
bit was set, the rev would be skipped over.  In most cases this
is fine, but what if the previous scanInto sweep on that rev
wasn't sent by the rev's integ parent?  In that case, when the
integ parent calls scanInto on that rev, it gets ignored because
it looks like scanInto has already done its work there.

The fix was one line: when skipping over an intocheck'd rev, if
it's the startrev, make sure the from pointer is set.

(There may be an analogous bug in scanFrom - will have to check.)

(Update: No such bug in scanFrom.)
#1 937 Sam Stafford Renaming my guest directory to the more conventional
sam_stafford.
//guest/samwise/p4hl/src/dlls/FileHead.cpp
#1 936 Sam Stafford Adding P4HL to the public depot.
 See relnotes.txt for
installation instructions; all relevant files are under
p4hl/dist.

Source code is under p4hl/src in the form of a VC++ project.