// Implementation of ClientLogUser. Warning: manual text parsing // ahead. Avert your eyes. #include <clientapi.h> #include "changesorter.h" #include "filehead.h" #include "filelogcache.h" #include "dateops.h" #include "clientloguser.h" ClientLogUser::ClientLogUser( FileHead* w, Error* e ) : doneone( false ), toomany( false ), working( w ), error( e ) { } ClientLogUser::~ClientLogUser() { } // This is where the magic happens. // Lines of filelog output will have a level of '0', '1', or '2'. // Level '0' is the depot syntax file name - put this into working->name. // Level '1' is a revision record, looking like this: // #2 change 767 edit on 2001/09/25 by sam@luey (binary+k) 'description' // Level '2' is an integ record, looking like this: // branch from //depot/foo/bar#1,#2 void ClientLogUser::OutputInfo( char level, const_char *data ) { if ( toomany ) return; // make sure to only process ONE file. if ( level == '0' ) { if ( doneone ) // this is the second file this CLU has seen! { toomany = true; return; } char* ptr = data; working->name.Clear(); while ( *ptr && *ptr != '\n' ) working->name.Append( ptr++, 1 ); doneone = true; } // If this is a rev record, create a new FileRev object for it. if ( level == '1' ) { StrBuf rev = StrBuf(); // rev number StrBuf change = StrBuf(); // change number StrBuf us = StrBuf(); // user StrBuf da = StrBuf(); // date StrBuf act = StrBuf(); // action RevType type; // revision type (corresponds to action) StrBuf ft = StrBuf(); // filetype char* ptr = data+1; // skip over '#' // We're now pointing at the rev number. for ( ; *ptr != ' ' ; ptr++ ) // Run to next space. rev.Append( ptr, 1 ); // rev now contains the rev number if ( *(ptr+1) == 'd' ) return; // "default change" - old server! ptr += 8; // skip over " change " // We're now pointing at the change number. for ( ; *ptr != ' ' ; ptr++ ) // Run to next space. change.Append( ptr, 1 ); // change now contains the change number. ptr++; // skip over " " // We're now looking at the rev type. for ( ; *ptr != ' ' ; ptr++ ) // Run to next space. act.Append(ptr, 1); // act now contains the revision's action. // The type is determined from the first char unless it's an 'i', // in which case it could be either "import" or "integ". switch (*act.Text()) { default: case 'e': type = EDIT; break; // edit case 'a': type = ADD; break; // add case 'b': type = BRANCH; break; // branch case 'd': type = DEL; break; // delete case 'i': type = UNKNOWN_TYPE; break; // ? } if (type == UNKNOWN_TYPE) // resolve ? case { if ( *( act.Text() + 1 ) == 'm' ) type = BRANCH; // import else type = INTEG; // integ } ptr += 4; // skip over " on " // We're now looking at the date. for ( ; *ptr != ' ' ; ptr++ ) // Run to next space. da.Append(ptr, 1); // da now contains the date. // Interlude: if we're pruning by start date/change, this is a good // time to check that and bail. if ( ( working->flc->dstart && DateOps::cmp( da.Text(), working->flc->dstart ) < 0 ) || ( working->flc->cstart && change.Atoi() < StrRef( working->flc->cstart ).Atoi() ) ) { toomany = true; return; } ptr += 4; // skip over " by " // We're now looking at the user. for ( ; *ptr != '@' ; ptr++ ) // Run to the '@' us.Append(ptr, 1); // us now contains the user. while (*ptr != ' ') ptr++; // Run to next space. ptr += 2; // Skip over " (" // We're now looking at the filetype. for ( ; *ptr != ')' ; ptr++ ) // Run to the ')' ft.Append(ptr, 1); // ft now contains the filetype. // Add the rev and set attributes not set in constructor. working->addRev( rev, change, type, da ); working->tail->user = us; working->tail->ftype = ft; working->tail->action = act; return; // All done with this line of output! } // Here's where we handle integ records. if ( level == '2' ) { ArrowType atype; // Integ type. StrBuf file = StrBuf(); // File path. StrBuf rev = StrBuf(); // Revision number. char* ptr = data; // First char of integ type. switch ( *ptr ) { default: case 'm': atype = merge; break; // merge case 'b': atype = branch; break; // branch case 'e': atype = impure; break; // edit case 'a': atype = impure; break; // add case 'c': atype = copy; break; // copy case 'i': atype = ignore; break; // ignored case 'd': atype = copy; break; // delete } while ( *ptr != ' ' ) ptr++; // Skip to the next space ptr++; // Skip the space. // We're now looking at the first letter of the direction. // A "from" integ record will be indicated by the word "from" // in most cases. For example, "merge from" or "branch from". // The exception is the word"ignored". If this is the case, // we'll be looking at a filespec rather than a "from" or "into". if ( *ptr == 'f' || *ptr == '/' ) // 'from' or a file { // This is a "from" record. if ( *ptr == 'f' ) // If "from", skip past it to the file. { while ( *ptr != ' ' ) ptr++; ptr++; // now we're at the start of the "from" filespec } //If not, it was "ignored" and we're there already. for ( ; *ptr != '#' ; ptr++ ) // Run to the '#' marker. file.Append(ptr, 1); // file now contains the parent file. ptr ++; // Skip "#". Now looking at a rev number. // At this point we'll either see a single revision or a rev // range. If it's a revision range it'll be of the form: // #12,#15 // whereas a single revision will just be // #15 // If it's a range, we want to look at the last number. for ( ; *ptr && *ptr != ',' && *ptr != '\n' ; ptr++ ) rev.Append( ptr, 1 ); // rev now is either the single rev, or the first in a range. if ( *ptr == '\0' || *ptr == '\n' ) //Single revision - use it. { working->tail->AddFromText(file, rev, atype, data); return; // All done with this line. } //This isn't the end of the line, so there must be another rev rev.Clear(); ptr += 2; // skip ",#" to get to the end rev for ( ; *ptr && *ptr != '\n' ; ptr++ ) rev.Append( ptr, 1 ); // Now rev contains the end rev of the range. working->tail->AddFromText( file, rev, atype, data ); return; // Done with this line. } else // This line is an "into". { while ( *ptr != ' ' ) ptr++; // Skip over "into" or "by". ptr++; // Skip the " ". // We're at the start of an "into" filespec. for ( ; *ptr != '#' ; ptr++ ) // Run to the "#" file.Append( ptr, 1 ); // file now has the target file. ptr++; // Skip "#" and get to the rev. This is not a range. for ( ; *ptr && *ptr != '\n' ; ptr++ ) rev.Append( ptr, 1 ); // rev now contains the target rev. working->tail->AddIntoText(file, rev, atype, data); return; // Done! } } } // Trap errors in that one central Error object that everything points to. void ClientLogUser::HandleError( Error* err ) { StrBuf* msg = new StrBuf; // XXX - Error.Set() doesn't copy the buffer int sev = err->GetSeverity(); err->Fmt( msg ); error->Set( (ErrorSeverity)sev, msg->Text() ); }
# | Change | User | Description | Committed | |
---|---|---|---|---|---|
#3 | 2946 | Sam Stafford |
Pull in read-filelogs-from-file capabilities. No functional change yet. |
||
#2 | 2945 | Sam Stafford | These changes are all already reflected in P4QTree. | ||
#1 | 2377 | Sam Stafford | P4QTree. |