// FileHead.cpp: implementation of the FileHead class. // ////////////////////////////////////////////////////////////////////// #include <clientapi.h> #include "filehead.h" #include "clientloguser.h" #include "changesorter.h" #include "filelogcache.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) { size = 0; name = path; //This is the path to the file. next = NULL; head = NULL; tail = NULL; next_from = NULL; next_into = NULL; flc = newparent; //The FileLogCache that created this FileHead. ui = new ClientLogUser; ui->working = this; //Tell the ClientLogUser to add revisions to this FileHead. if ( flc->cachefile ) { if ( LoadCached( flc->cachefile ) ) return; } if (flc->errors->IsFatal()) return; 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. flc->client->SetArgv(1, args); //Set the argument list to be the file path. flc->client->RunTag( "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 (flc->client->Dropped()) { flc->client->Final(flc->errors); } } /* The destructor - deletes all of its FileRevs as well as its neighboring FileHeads. */ FileHead::~FileHead() { delete ui; 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. } } bool FileHead::LoadCached( char* filename ) { FILE* file = fopen( filename, "r" ); if ( !file ) return false; bool success = false; char* c = new char[1024]; while ( fgets( c, 1024, file ) ) { if ( !strncmp( c, name.Text(), name.Length() ) ) { success = true; ui->OutputInfo( '0', c ); break; } } if ( !success ) return false; while ( fgets( c, 1024, file ) ) { if ( !strncmp( c, "... ... ", 8 ) ) ui->OutputInfo( '2', c+8 ); else if ( !strncmp( c, "... ", 4 ) ) ui->OutputInfo( '1', c+4 ); else if ( !strncmp( c, "//", 2 ) ) ui->OutputInfo( '0', c ); } return true; } /* 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, RevType type, bool istext) { /* Error checking - this will reject blatantly invalid results */ if ( newchange.Length() == 0 || newchange.Atoi() == 0 || newrev.Length() == 0 || newrev.Atoi() == 0 ) return; size++; 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 flc->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* fr = tail; //fr is the FileRev we're looking at. FileTextArrow* text; /* First, precache all of the descendant branches. */ while (fr != NULL) //Unless we hit the end of the line... { if (fr->rev == startrev) { //if this is the startrev credit = true; //begin precaching } if (credit) { if (fr->intocheck) { //Have we already checked this rev? If so, don't do it again. fr = fr->prev; //move up to the next FileRev. continue; //Next loop. } text = fr->intotexts; //If no integ descendants, this will be null. while (text) { text->fhead = flc->Get(text->file, false, this); //get the FileHead text = text->next; //next textarrow } } fr = fr->prev; //Move on to the next FileRev. } flc->client->WaitTag(); /* Now we scanInto all those precached FileHeads. */ credit = false; fr = tail; while (fr != NULL) //Unless we hit the end of the line... { if (fr->rev == startrev) { //if this is the startrev credit = true; //begin doing scanIntos fr->AddFromArrow(caller, atype); //Set a from pointer to the caller FileRev. } if (credit) { if (fr->intocheck) { //Have we already checked this rev? If so, don't do it again. fr = fr->prev; //move up to the next FileRev. continue; //Next loop. } /* If we reach this point, this rev hasn't been checked yet. */ text = fr->intotexts; /* If fr 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. */ if (text->fhead->size == 0) //empty FileHead, result of an error { text = text->next; continue; } text->fhead->scanInto(text->rev, fr, text->type); text = text->next; } fr->intocheck = true; //Record the fact that this FileRev has been checked. } fr = fr->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* fr = tail; //fr is the rev we're currently looking at. while (fr != NULL) //for each FileRev... { if (fr->fromcheck) //If this rev has already been checked { if (fr->rev == endrev) break; //Is it the last one? else fr = fr->prev; //No? Skip over it and continue. continue; } text = fr->fromtexts; while (text) { text->fhead = flc->Get(text->file, true, this); //precache the associated FileHead text = text->next; } if (fr->fromtexts == NULL) { fr->fromcheck = true; //no need to try to scan it later } if (fr->rev == endrev) break; //we're done precaching else fr = fr->prev; //otherwise, next FileRev } flc->client->WaitTag(); /* Now the actual scans. */ fr = tail; while (fr != NULL) //for each FileRev... { if (fr->fromcheck) //If this rev has already been checked { if (fr->rev == endrev) return fr; //Is it the one we want? else fr = fr->prev; //No? Skip over it and continue. continue; } text = fr->fromtexts; while (text) { if (text->fhead->size == 0) //empty FileHead, result of an error { text = text->next; continue; } fr->AddFromArrow(text->fhead->scanFrom(text->rev), text->type); text = text->next; } fr->fromcheck = true; //mark this FileRev as having been scanned if (fr->rev == endrev) return fr; //all done! Return it. else fr = fr->prev; //otherwise, next FileRev } return fr; //this line should never be executed. } void FileHead::scanMain() { FileTextArrow* text; FileRev* fr = tail; //fr 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 (fr != NULL) //Unless we hit the end of the line... { if (!(fr->fromcheck)) { text = fr->fromtexts; while (text) { text->fhead = flc->Get(text->file, true, this); //cache the associated FileHead text = text->next; } } if (!(fr->intocheck)) { text = fr->intotexts; /* If fr doesn't have any integ children, this is null. */ while (text) //For all files in the "intofiles" list. { text->fhead = flc->Get(text->file, false, this); //cache the FileHead text = text->next; //next file } } fr = fr->prev; } flc->client->WaitTag(); //finish creating all revisions /* Now we finish it up by actually going through and scanning the immediate * relatives. */ fr = tail; while (fr != NULL) { if (!(fr->fromcheck)) { fr->fromcheck = true; text = fr->fromtexts; while (text) { if (text->fhead->size == 0) //empty FileHead, result of an error { text = text->next; continue; } fr->AddFromArrow(text->fhead->scanFrom(text->rev), text->type); text = text->next; } } if (!(fr->intocheck)) { fr->intocheck = true; text = fr->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. */ if (text->fhead->size == 0) //empty FileHead, result of an error { text = text->next; continue; } text->fhead->scanInto(text->rev, fr, text->type); text = text->next; } } fr = fr->prev; } }
# | Change | User | Description | Committed | |
---|---|---|---|---|---|
#19 | 2944 | Sam Stafford |
Add the ability to read filelog output from a text file. This functionality (if used; it's off by default) causes FLC to try to find required filelog output from a specified file before it queries the server. This can be used to fill in gaps (eg obliterated files), or more importantly, to draw graphs based on filelog output from a server you don't have access to, such as when trying to diagnose an integration problem for a customer. The filelog-parsing code hasn't been changed much, and isn't very fault-tolerant, so it might crash if fed mangled filelogs. Let the user beware. |
||
#18 | 1796 | Sam Stafford |
Check that a FileHead contains stuff before trying to assign pointers to that stuff. (Fixes a rare crash bug involving protections.) |
||
#17 | 1704 | Sam Stafford |
Change API includes to <*.h> rather than "*.h". No functional change. |
||
#16 | 1684 | Sam Stafford |
Imported new 02.1 p4api headers and libs. MAJOR code cleanup to make it fit without resorting to re-hacking of the API headers. The hacked headers were not L33T. They deserved D34TH. |
||
#15 | 1585 | Sam Stafford |
Add sanity checks to FileHead::addRev() to prevent certain errors from making it into the FLC. |
||
#14 | 1560 | Sam Stafford |
Uninitialized variable - somehow got lost while undoing those bad changes. Oopsie. |
||
#13 | 1556 | Sam Stafford |
Undo 1540 and 1552 and do things right. Preliminary tests seem to indicate that everything works, but I've been taught to tread more cautiously in the future. Need to re-implement the error-checking to enforce the "one file per FileHead" rule. |
||
#12 | 1552 | Sam Stafford |
Quick and kludgey fix to bug introduced by change 1540. Fix probably has to do with combination of using filelog output to fill in the name of a FileHead, using tags to run filelogs asynchronously, and preloading a bunch of FileHead pointers at once. The kludge: store the name which was originally used to create the FileHead and use that in comparisons. The better fix (to be done later): call WaitTag() if needed to fill in the FileHead and get the name of the file. The problem with the current kludge is that it still won't work if the file's "oname" isn't in depot syntax, which is what change 1540 was supposed to address in the first place. Grahr. |
||
#11 | 1550 | Sam Stafford | Add a "size" variable to the FileHead. | ||
#10 | 1547 | Sam Stafford |
Moved revision type from #defined shorts to an enum, RevType. Infrastructure change. |
||
#9 | 1540 | Sam Stafford |
Quick little change to ensure that a FileHead's name is always in depot syntax. Error checking also put in place to ensure that a FileHead doesn't refer to two files. |
||
#8 | 1520 | Sam Stafford |
Make variable names slightly more sensical - expressions like "foo->file->file.Text()" look really bad. Now it's more like "fr->fh->name.Text()", which makes quite a bit more sense, at least if you're familiar with the structure. Infrastructure change. |
||
#7 | 1456 | Sam Stafford |
Use RunTag() and WaitTag() to perform filelogs asynchronously. This is risky because queueing multiple commands with RunTag() is unsupported and known to have problems, but it can also potentially cut lag time in half. |
||
#6 | 1449 | Sam Stafford |
Removed single-argument constructor and associated behavior from FileLogCache. Clients will now need to construct and pass in ClientApi and Error objects - the benefit is that connection settings can be set in the ClientApi, and connection errors will be returned in the Error. |
||
#5 | 1441 | Sam Stafford |
A FileLogCache now maintains a single ClientApi and Error object to be used for all filelog requests. Should be faster. A new constructor is available which takes a ClientApi* and Error* - this is to allow you to handle connection errors yourself rather than having FileLogCache call Error::Abort(). |
||
#4 | 1432 | Sam Stafford |
Visible change: ALL revisions are now kept in the ChangeSorter, not just those that are "relevant". I've decided that the mechanism of filtering for revisions with direct relationships isn't all that useful, even if it's sort of interesting - it prevents you from seeing work in ancestor branches that you haven't yet integrated, for example. Infrastructure change: New set of FileHead* links to help in navigation of the FileLogCache: pointers in the FileLogCache to "head" and "tail" FileHeads, and "next_from" and "next_into" pointers on each FileHead. The idea is you can start at the "head" and then follow "next_into" pointers all the way to the "tail". |
||
#3 | 1430 | Sam Stafford |
Fixing "edit" type FileRevArrows. They were being put in backwards - oopsie. |
||
#2 | 1419 | Sam Stafford | Tweaking and edits to make it compile on its own. | ||
#1 | 1417 | Sam Stafford |
Branching backend stuff off for rigorous testing. Grahr. |
||
//guest/sam_stafford/p4hl/src/dlls/FileHead.cpp | |||||
#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. |