Migrating to TFS has many benefits.
- Tracking of Change sets/change lists. MorphX does not track groups of files checked in together. This information can be gleaned directly from SQL tables, but is not very easy to get at.
- Better tools for viewing history. MorphX history can only be viewed from within the AX Dev environment and only the first 20 changes can be viewed. This is very limiting for high touch objects because you quickly hit this limit. When using TFS you can see the last 20 changes which is much more useful, but you also have full access to Visual Studio and the tf.exe command line interface.
- Integration with bug tracking. Check-ins can be automatically checked in against bugs. Although I haven't used it, I read that AX 2012 supports checking in against TFS work items. In my current job we are using FogBugz which has a built in way to register TFS check ins that are tagged with BugzId: <ID> in the description and associate those with the bug history.
- More ways to share diffs. Using TFS shelve sets you can easily shelve a change that someone else can review the diff of. This is one way (better than passing around XPO projects and comparing in AX) that peer reviews can be done.
- Integration with TFS Build server.
- Check in policies.
My initial research on migrating led me to the TFS Integration Platform. At first this seemed promising and probably would be a good way to do this, but would still require at least some basic work to get the X++ files and history in a compatible format. I quickly came to the realization that this tool was overkill for what I wanted.
MorphX version control stores the version history in a table named SysVersionControlMorphXRevisionTable. This table contains all of the information that you would want to track in version control, as well the actual version of the source file as a blob. There is a method on the table that can be used to write the file to disk.
I realized that with this table and the command line interface to TFS a quick and dirty job could be written to "recreate" the MorphX check ins in TFS. It's even possible to check in on behalf of another user and maintain that history. The only thing you can't do is set the timestamp for the check-in, but this info can be tagged in the check-in comment and this was good enough for me.
I hope you find this job useful. In Part 2 of this article I mention some of the road bumps I had along the way and what I did to get over them.
[UPDATE: The source code for this job is now hosted on GitHub as open source. Please find the latest code there and feel free to contribute improvements.]
https://github.com/JOROCONSULTING/morphx2TFS
[download file]
static void SourceControl_MorphX_to_TFS(Args _args)
{
SysVersionControlMorphXRevisionTable VCSMorphXRevisonTable;
Int MorphXChangeListNumber = 0;
SysVersionControlItemComment previousComment;
UTCDateTime previousDate;
UserId previousUser;
str authorStr = "";
str ExportFolder = @'c:\Enlistments\AXDEV\DAX';
str FilePath;
str TFSFilePath;
str FolderPath;
System.IO.Path path;
System.IO.FileInfo fileInfo;
//str tfPath = @"C:\Program Files (x86)\Microsoft Visual Studio 10.0\Common7\IDE\tf.exe "
//Use the following line for testing if you need to see the output of the tf.exe, pause after each command, or redirect output
str tfPath = @"C:\Temp\tf.cmd";
str authorStr(userId _userId)
{
UserInfo userInfo;
;
select id, networkAlias from userInfo where userInfo.Id == _userId;
if (userInfo.networkAlias != "Admin" && userInfo.networkAlias != "")
{
return ' /author:' + userInfo.networkAlias;
}
return "";
}
void submitChange(UserId _userId)
{
//Submit the TFS Changelist
if (!WinAPI::shellExecute(tfPath,
' checkin' + authorStr(_userId) +
' /noprompt /comment:"' + previousComment +
' OriginalDate:' + DateTimeUtil::toStr(previousDate),
"","",1,true))
{
info(strfmt('Failed: checkin %1 /noprompt /comment:"%2 OriginalDate: %3"',authorStr(_userId), previousComment, DateTimeUtil::toStr(previousDate)));
}
}
;
//ITEMPATH, VERSION, COMMENT, ACTION, CREATEDDATETIME, CREATEDBY
while select * from VCSMorphXRevisonTable order by CREATEDDATETIME
where VCSMorphXRevisonTable.CREATEDDATETIME > 2011-08-02T02:52:14
{
//If the comment changes or if the user is different,or if the date is changed by more than 60 seconds then create a new changelist.
if (((previousComment != VCSMorphXRevisonTable.Comment) ||
(previousUser != VCSMorphXRevisonTable.createdBy) ||
(DateTimeUtil::getDifference(previousDate,VCSMorphXRevisonTable.createdDateTime) > 60)))
{
/*
//Return after 7 changelists for testing
if (MorphXChangeListNumber > 7)
{
return;
}
*/
//Don't submit the first time
if (MorphXChangeListNumber != 0)
{
submitChange(VCSMorphXRevisonTable.createdBy);
}
//set the previous variable to the current
previousComment = VCSMorphXRevisonTable.Comment;
previousUser = VCSMorphXRevisonTable.createdBy;
previousDate = VCSMorphXRevisonTable.createdDateTime;
//Increment the morphX changelist number
MorphXChangeListNumber++;
}
FilePath = ExportFolder + VCSMorphXRevisonTable.ItemPath + '.xpo';
FolderPath = System.IO.Path::GetDirectoryName(FilePath);
//Create the path if it doesn't exist
if (!WinAPI::folderExists(FilePath))
{
WinAPI::createDirectoryPath(FolderPath);
}
switch (VCSMorphXRevisonTable.Action)
{
case "Add":
//Write the file to disk first, then add it to version control
try
{
//Export the version of the object into a folder for this version
VCSMorphXRevisonTable.writeToFile(FilePath);
}
catch (Exception::Error)
{
info(strfmt("Error adding %1",FilePath));
}
if(!WinAPI::shellExecute(tfPath,VCSMorphXRevisonTable.Action + ' "' + FilePath + '" /noprompt', "", "", 1, true))
{
info(strfmt("Failed %1 %2", VCSMorphXRevisonTable.Action, FilePath));
}
break;
case "Edit":
//Check out the file first, then delete the copy on disk and replace with the one from MorphX VCS
if(!WinAPI::shellExecute(tfPath,VCSMorphXRevisonTable.Action + ' "' + FilePath + '" /noprompt', "", "", 1, true))
{
info(strfmt("Failed %1 %2", VCSMorphXRevisonTable.Action, FilePath));
}
try
{
WinAPI::deleteFile(FilePath);
//Export the version of the object into a folder for this version
VCSMorphXRevisonTable.writeToFile(FilePath);
}
catch (Exception::Error)
{
info(strfmt("Error editing %1",FilePath));
}
break;
case "Delete":
//Delete from TFS, then delete from file system
if(!WinAPI::shellExecute(tfPath,VCSMorphXRevisonTable.Action + ' "' + FilePath + '" /noprompt', "", "", 1, true))
{
info(strfmt("Failed %1 %2", VCSMorphXRevisonTable.Action, FilePath));
}
try
{
WinAPI::deleteFile(FilePath);
}
catch (Exception::Error)
{
info(strfmt("Error deleting %1",FilePath));
}
break;
}
}
//Submit the last changelist
submitChange(VCSMorphXRevisonTable.createdBy);
info(strfmt("%1 Changelists from MorphX processed",MorphXChangeListNumber));
}
No comments:
Post a Comment