// VSSMigrate.cpp : Defines the entry point for the console application. // #include "stdafx.h" #include "VSSMigrate.h" #include #include #include #include #include #include #include #include #ifdef _DEBUG #define new DEBUG_NEW #endif struct ParseData { CString comment; CString date; CString time; CString vss_user; }; // The one and only application object CWinApp theApp; CString vssEXE; CString vssDIR; CString vssPROJ; CString vssUSER; CString svnEXE; CString svnUSER; CString svnPASSWORD; CString svnURL; CString svnPROJ; CString outputDIR; CString repoDIR; CString cmdEXE; std::set projList; //alphabetized and arranged shorted to longest this way std::set fileList; bool ReadProperties(LPCTSTR cmdLine); bool CreateDirectories(); bool BuildFileList(); using namespace std; bool bDebug = false; bool bRetry = false; int numCommands = 0; int numFilesHandled = 0; int numRevisionsHandled = 0; TCHAR startTime[100] = {0}; void Cleanup() { printf("Cleaning %s\n", repoDIR); CString cmd; cmd.Format(_T("del %s\\* /f /s /q"), repoDIR); system(cmd); cmd.Format(_T("rmdir %s /s /q"), repoDIR); system(cmd); } CString ReadEntireFile(LPCTSTR filename) { CString results; HANDLE hf = ::CreateFile(filename, GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, 0); if(INVALID_HANDLE_VALUE != hf) { DWORD ignored = 0, read = 0, size = GetFileSize(hf, &ignored); if(FALSE == ReadFile(hf, results.GetBuffer(size), size, &read, 0)) printf("Failed to read %s\n", filename); else results.ReleaseBuffer(read); CloseHandle(hf); } else printf("Failed to open %s for reading\n", filename); return results; } bool RunCommand(const CString& cmd, bool bRetrying = false) { PROCESS_INFORMATION pi = {0}; STARTUPINFO si = {0}; si.cb = sizeof(si); numCommands++; if(bDebug) printf("Running [%s]\n", cmd); CString tmp = cmd; if(FALSE == CreateProcess(NULL, tmp.LockBuffer(), NULL, NULL, TRUE, CREATE_SUSPENDED, NULL, NULL, &si, &pi)) { printf("Failed to start %s. LastError=%d\n", cmd, GetLastError()); return false; } tmp.UnlockBuffer(); ResumeThread(pi.hThread); if(WAIT_OBJECT_0 != WaitForSingleObject(pi.hProcess, 300000)) { printf("Error waiting for %s to launch and finish--will continue anyway\n", cmd); } DWORD exit = 0; GetExitCodeProcess(pi.hProcess, &exit); CloseHandle(pi.hThread); CloseHandle(pi.hProcess); if(0 != exit) { printf("Command failed: %s\n", cmd); if(bRetry && (false == bRetrying)) { printf("Will auto-retry in 10 seconds...\n"); Sleep(10000); return RunCommand(cmd, true); } printf("Press Y to try again, or N to fail\n"); char ch = _getch(); if(('Y' == ch) || ('y' == ch)) return RunCommand(cmd, true); return false; } return true; } bool CreateDirectories() { printf("##### Creating directories\n"); for(std::set::iterator itr = projList.begin(); projList.end() != itr; itr++) { CString dir; dir.Format(_T("%s\\%s"), repoDIR, (LPCTSTR)(*itr) + 2); //get past the $/ dir.Replace(_T('/'), _T('\\')); TCHAR* cPtr = (LPTSTR)(LPCTSTR)_tcschr(dir, _T('\\')); while(NULL != cPtr) { *cPtr = _T('\0'); ::CreateDirectory(dir, 0); *cPtr = _T('\\'); cPtr = (LPTSTR)(LPCTSTR)_tcschr(cPtr + 1, _T('\\')); } ::CreateDirectory(dir, 0); } return true; } bool ImportDirectories() { printf("##### Importing directories\n"); CString outFile = outputDIR + _T("\\ImportDirs.txt"); DeleteFile(outFile); //only check out the directories that we're working with CString url; url.Format(_T("%s/%s"), svnURL, (LPCTSTR)svnPROJ + 2); //get past $/ url.Replace(_T("\\"), _T("/")); CString dir; dir.Format(_T("%s\\%s"), repoDIR, (LPCTSTR)vssPROJ + 2); dir.Replace(_T("/"), _T("\\")); CString cmd; _chdir(dir); cmd.Format(_T("%s --message \"Initial import\" import \"%s\" %s %s"), svnEXE, url, svnUSER, svnPASSWORD); if(false == RunCommand(cmd)) return false; _chdir(outputDIR); Cleanup(); cmd.Format(_T("%s --non-interactive checkout \"%s\" \"%s\" %s %s"),svnEXE, url, dir, svnUSER, svnPASSWORD); if(false == RunCommand(cmd)) return false; return true; } bool BuildFileList() { printf("##### Building file list\n"); CString outFile = outputDIR + _T("\\FileList.txt"); DeleteFile(outFile); CString cmd; //DRN 3/27/08 -- quoting project path in case it has quotes cmd.Format(_T("%s DIR \"%s\" -I- -R -O@%s %s"), vssEXE, vssPROJ, outFile, vssUSER); if(false == RunCommand(cmd)) return false; //read in the file CString heirarchy = ReadEntireFile(outFile); if(heirarchy.IsEmpty()) { printf("No results from building file list\n"); return false; } //split it into separate lines TCHAR* cPtr = _tcstok(heirarchy.LockBuffer(), _T("\r\n")); CString baseProj; while(NULL != cPtr) { if((cPtr[0] == _T('$')) && (cPtr[_tcslen(cPtr) - 1] == _T(':'))) { baseProj = cPtr; baseProj.Replace(_T(':'), _T('/')); projList.insert(baseProj); } else { if(cPtr[0] == _T('$')) { //just telling us about a sub project CString part; part.Format(_T("%s%s"), baseProj, cPtr + 1); projList.insert(part); } else { //this is a file size_t len = _tcslen(cPtr); CString tmp = cPtr; tmp.MakeLower(); bool bSkip = false; if(0 == _tcsncmp(tmp, _T("no items found"), 14)) bSkip = true; if((len > 7) && (0 == _tcsicmp((LPCTSTR)tmp + (len - 7), _T("item(s)")))) bSkip = true; if(false == bSkip) { CString part; part.Format(_T("%s%s"), baseProj, cPtr); fileList.insert(part); } else { //should be the summary _ASSERT(0 != _tcsstr(cPtr, _T("item"))); } } } cPtr = _tcstok(NULL, _T("\r\n")); } heirarchy.UnlockBuffer(); printf("%d files\n", fileList.size()); return true; } bool IsSectionLine(LPCTSTR line) { //count asterisks int astCount = 0; LPCTSTR cPtr = line; while(0 != *cPtr) { if(*cPtr == _T('*')) astCount++; cPtr++; } CString tmp = line; tmp.MakeLower(); if((astCount > 15) && ((0 != _tcsstr(tmp, _T("version "))) || (0 != _tcsstr(tmp, _T("label:"))))) return true; else return false; } std::map ParseHistory(LPCTSTR history) { std::map results; //first make the start line be either a version or a label by pulling the //label back up onto the version line CString work = history; work.Replace(_T("*****\r\nLabel:"), _T(" Label:")); //now make a vector of strings std::vector lines; TCHAR* cPtr = _tcstok(work.LockBuffer(), _T("\r\n")); _ASSERT(0 != _tcsstr(cPtr, _T("History of $"))); cPtr = _tcstok(NULL, _T("\r\n")); while(NULL != cPtr) { lines.push_back(cPtr); cPtr = _tcstok(NULL, _T("\r\n")); } int version = 0; CString comment; //CString datetime; TCHAR theDate[9]; TCHAR theTime[7]; //TCHAR theVss_user[32]; CString theVss_user; std::vector::iterator itr = lines.begin(); while(lines.end() != itr) { bool bSectionLine = IsSectionLine(*itr); CString line = *itr; line.MakeLower(); if(bSectionLine) { //do we have data from the last section? if(0 != version) { //results[version] = comment; results[version].comment = comment; results[version].date = theDate; results[version].time = theTime; results[version].vss_user = theVss_user; version = 0; comment.Empty(); theVss_user.Empty(); } line.Trim(_T("* ")); if(0 == _tcsncmp(line, _T("version "), 8)) { //it's a version so we want to keep it version = _ttoi((LPCTSTR)line + 8); } else { if(0 == _tcsncmp(line, _T("label:"), 6)) { //label, so skip itr++; while(false == IsSectionLine(*itr)) { itr++; if(lines.end() == itr) break; } if(lines.end() != itr) itr--; //back up so when we inc it will be on the next section } else { //unrecognized line printf("Don't know how to parse %s--exitting!!\n", *itr); exit(0); } } } else { //not a section line, so either the author line or a comment line if( /*(0 == _tcsncmp(line, _T("user: "), 6)) || */ (0 == _tcsncmp(line, _T("checked in"), 10)) || (0 == _tcsncmp(line, _T("rolled back"), 11)) || (0 == _tcsncmp(line, _T("created"), 7))) //skip this line ; else { CString dateString; CString timeString; CString vss_userString; if(0 == _tcsncmp(line, _T("comment:"), 8)) { comment = ((*itr) + 8); } else { //must have stuff, so a continuation of the the comment comment += _T(" "); comment += *itr; if ((dateString = (CString)_tcsstr(line, _T("date: ")))) { _tcscpy(theDate, dateString.Mid(6, 8)); } if ((timeString = (CString)_tcsstr(line, _T("time: ")))) { _tcscpy(theTime, timeString.Mid(6, 5)); } if (0 == _tcsncmp(line, _T("user: "), 6)) { vss_userString = ((*itr) + 6); CString temp = vss_userString.Mid(vss_userString.Find("Date:")); int i = vss_userString.GetLength() - temp.GetLength(); theVss_user = vss_userString.Left(i).Trim(); } } } } if(lines.end() != itr) itr++; } //add the last stuff that might be there if(0 != version) { results[version].comment = comment; results[version].date = theDate; results[version].time = theTime; results[version].vss_user = theVss_user; //results[version] = comment; version = 0; comment.Empty(); } return results; } void GetAndAddFiles() { // Loop all files (kazei) for(std::set::iterator fitr = fileList.begin(); fileList.end() != fitr; fitr++) { numFilesHandled++; printf("Handling %s\n", *fitr); CString outFile = outputDIR + _T("\\FileHist.txt"); DeleteFile(outFile); CString cmd; cmd.Format(_T("%s HISTORY -I- -O@%s \"%s\" %s"), vssEXE, outFile, *fitr, vssUSER); RunCommand(cmd); CString history = ReadEntireFile(outFile); std::map verComment = ParseHistory(history); // Loop every version for the current file (kazei) for(std::map::iterator vcItr = verComment.begin(); verComment.end() != vcItr; vcItr++) { numRevisionsHandled++; //pull each version, then add to svn int ver = (*vcItr).first; CString cmd; CString dir; dir.Format(_T("%s%s"), repoDIR, ((LPCTSTR)*fitr) + 1); //get past the $ on the front dir.Replace(_T("/"), _T("\\")); CString fileName; //trim off file name TCHAR* cPtr = (LPTSTR)(LPCTSTR)_tcsrchr(dir, _T('\\')); if(NULL != cPtr) { fileName = cPtr + 1; *cPtr = _T('\0'); } //cmd.Format(_T("%s GET -GTM -W -I- -GL\"%s\" -V%d %s \"%s\""), // JMW Set Ignore to always answer yes to avoid problem when project has been deleted cmd.Format(_T("%s GET -GTM -W -I-Y -GL\"%s\" -V%d %s \"%s\""), vssEXE, dir, ver, vssUSER, *fitr); CString outFile2 = outputDIR + _T("\\GetResult.txt"); DeleteFile(outFile2); if(false == RunCommand(cmd)) { printf("Failed to run %s\n", cmd); continue; } _chdir(dir); // If it's the first version, add the file. (kazei) if(vcItr == verComment.begin()) { //need to add file cmd.Format(_T("%s add \"%s\""), svnEXE, fileName); DeleteFile(outFile2); if(false == RunCommand(cmd)) { printf("Failed to add file: %s\n"); return; } } CString msg; msg.Format(_T("%s (vss version: %d, vss user: %s)"), (*vcItr).second.comment, ver, (*vcItr).second.vss_user); msg.Replace(_T("\""), _T("\\\"")); // Add svn:date here (kazei) SYSTEMTIME st; GetLocalTime(&st); st.wYear = atoi(("20" + (*vcItr).second.date.Left(2))); st.wMonth = atoi((*vcItr).second.date.Mid(3, 2)); st.wDay = atoi((*vcItr).second.date.Mid(6, 2)); st.wHour = atoi((*vcItr).second.time.Left(2).Trim()); st.wMinute = atoi((*vcItr).second.time.Mid(3, 2)); st.wSecond = 0; SetLocalTime(&st); //DWORD foo = GetLastError(); cmd.Format(_T("%s commit --quiet --non-interactive --non-recursive %s %s --message \"%s\" \"%s\""), svnEXE, svnUSER, svnPASSWORD, msg, fileName); DeleteFile(outFile2); if(false == RunCommand(cmd)) { printf("Failed to commit file: %s\n", *fitr); return; } _chdir(outputDIR); } //done with this file, so delete it to ensure disk space doesn't become and issue CString doneFile; doneFile.Format(_T("%s%s"), repoDIR, ((LPCTSTR)*fitr) + 1); //get past the $ on the front DeleteFile(doneFile); } } bool ReadProperties(LPCTSTR cmdLine) { TCHAR buffer[1000] = {0}; GetPrivateProfileString(_T("Settings"), _T("VSSWIN32"), _T(""), buffer, sizeof(buffer), cmdLine); PathAppend(buffer, _T("ss.exe")); if(0 != _access(buffer, 0)) { printf("VSSWIN32 does not point at the VSS\\Win32 directory which contains the ss.exe file. (Looking for: %s)\n", buffer); return false; } vssEXE = buffer; //if there is a space in the path, quote it if(0 != _tcschr(vssEXE, _T(' '))) { vssEXE.Insert(0, _T('\"')); vssEXE += _T("\""); } GetPrivateProfileString(_T("Settings"), _T("VSSDIR"), _T(""), buffer, sizeof(buffer), cmdLine); vssDIR = buffer; PathAppend(buffer, _T("data")); if(0 != _access(buffer, 0)) { printf("VSSDIR does not VSS repository (with a Data directory beneath it). (Looking for: %s)\n", buffer); return false; } GetPrivateProfileString(_T("Settings"), _T("VSSPROJ"), _T(""), buffer, sizeof(buffer), cmdLine); if(3 > _tcslen(buffer)) { printf("VSSPROJ is not set (also must have a project name e.g. $/myProject)\n"); return false; } vssPROJ = buffer; GetPrivateProfileString(_T("Settings"), _T("VSSUSER"), _T(""), buffer, sizeof(buffer), cmdLine); if(0 != _tcslen(buffer)) { vssUSER.Format(_T("-Y%s"), buffer); GetPrivateProfileString(_T("Settings"), _T("VSSPASSWORD"), _T(""), buffer, sizeof(buffer), cmdLine); if(0 != _tcslen(buffer)) { vssUSER += _T(","); vssUSER += buffer; } //if there is a space in the path, quote it if(0 != _tcschr(vssUSER, _T(' '))) { vssUSER.Insert(2, _T('\"')); vssUSER += _T("\""); } } GetPrivateProfileString(_T("Settings"), _T("SVNWIN32"), _T(""), buffer, sizeof(buffer), cmdLine); PathAppend(buffer, _T("svn.exe")); if(0 != _access(buffer, 0)) { printf("SVNWIN32 does not point at the SVN\\bin directory which contains the svn.exe file. (Looking for: %s)\n", buffer); return false; } svnEXE = buffer; //if there is a space in the path, quote it if(0 != _tcschr(svnEXE, _T(' '))) { svnEXE.Insert(0, _T('\"')); svnEXE += _T("\""); } GetPrivateProfileString(_T("Settings"), _T("SVNUSER"), _T(""), buffer, sizeof(buffer), cmdLine); if(0 != _tcslen(buffer)) svnUSER.Format(_T("--username \"%s\""), buffer); GetPrivateProfileString(_T("Settings"), _T("SVNPASSWORD"), _T(""), buffer, sizeof(buffer), cmdLine); if(0 != _tcslen(buffer)) svnPASSWORD.Format(_T("--password %s"), buffer); GetPrivateProfileString(_T("Settings"), _T("SVNURL"), _T(""), buffer, sizeof(buffer), cmdLine); if(0 == _tcslen(buffer)) { printf("SSURL is not set\n"); return false; } svnURL = buffer; GetPrivateProfileString(_T("Settings"), _T("SVNPROJ"), _T(""), buffer, sizeof(buffer), cmdLine); if(3 > _tcslen(buffer)) { printf("SVNPROJ is not set (also must have a project name e.g. $/myProject)\n"); return false; } svnPROJ = buffer; GetPrivateProfileString(_T("Settings"), _T("WORKDIR"), _T(""), buffer, sizeof(buffer), cmdLine); if(0 != _access(buffer, 0)) { printf("WORKDIR does not point at an existing directory\n"); return false; } outputDIR = buffer; PathAppend(buffer, _T("_migrate")); ::CreateDirectory(buffer, 0); repoDIR = buffer; GetSystemDirectory(buffer, sizeof(buffer)); PathAppend(buffer, _T("cmd.exe")); cmdEXE = buffer; GetPrivateProfileString(_T("Settings"), _T("DEBUG"), _T("0"), buffer, sizeof(buffer), cmdLine); if(1 == _ttoi(buffer)) bDebug = true; GetPrivateProfileString(_T("Settings"), _T("AUTORETRY"), _T("0"), buffer, sizeof(buffer), cmdLine); if(1 == _ttoi(buffer)) bRetry= true; return true; } int _tmain(int argc, TCHAR* argv[], TCHAR* envp[]) { int nRetCode = 0; _tzset(); time_t tnow = time(NULL); struct tm* now = localtime(&tnow); strftime(startTime, sizeof(startTime), "%c", now); //check command lines if((argc != 2) || (false == ReadProperties(argv[1]))) { printf("Usage: VSSMigrate \n\n"); printf("The configuration INI file should contain:\n"); printf("[Settings]\n"); printf("VSSWIN32= # VSS\\Win32 directory which contains ss.exe\n"); printf("VSSDIR= #VSS repository directory (contains srcsafe.ini)\n"); printf("VSSPROJ= #VSS project to start at (ie $/Product)\n"); printf("VSSUSER= #User to use for VSS commands, use blank for none\n"); printf("VSSPASSWORD= #password to use for VSS commands, blank is OK\n\n"); printf("SVNWIN32= # VSS\\Win32 directory which contains ss.exe\n"); printf("SVNUSER= #User to use for SVN commands, use blank for none\n"); printf("SVNPASSWORD= #password to use for SVN commands, blank is OK\n"); printf("SVNURL= #URL to use for the root of the check in\n\n"); printf("SVNPROJ= #SVN project to start at (ie $/Product)\n"); printf("WORKDIR= #Directory under which files and directories will be created as work progresses\n"); printf("DEBUG=1 #turn on debug output, blank is OK\n"); printf("AUTORETRY=1 #if a command fails to run, it will be run automatically 1 time before failing\n"); printf("\n\nPress any key"); _getch(); return 0; } Cleanup(); CString tmp; tmp.Format("SSDIR=%s", vssDIR); _putenv(tmp); //thank you Ricardo Stuven if(false == BuildFileList()) goto Error; if(false == CreateDirectories()) goto Error; if(false == ImportDirectories()) goto Error; GetAndAddFiles(); // Time code removed since the system clock is messed with anyway. //printf("\n\nStart time: %s\n", startTime); //tnow = time(NULL); //now = localtime(&tnow); //strftime(startTime, sizeof(startTime), "%c", now); //printf("End time: %s\n", startTime); printf("External commands run: %d\n", numCommands); printf("Files Migrated: %d\n", numFilesHandled); printf("File Revisions Migrated: %d\n", numRevisionsHandled); Cleanup(); return nRetCode; Error: printf("\n\nExiting. Press any key to exit and clean up the working directory"); _getch(); Cleanup(); return nRetCode; }