A common complaint I hear in the field (and see on the mailing lists) is that when you do a Get from TFS, the file’s timestamps are set to whatever time you pulled it down. This is likely fine for most people, but some systems rely on the times matching what was on the server – if I do a second Get Latest forced, the timestamps shouldn’t change.
While I’ve seen posts that the product team is working to address this, it’s actually fairly easy to create a workaround. After all, files are just objects, and can have their properties set – including their creation times.
The first thing to know is how to add a custom task to a Team Build. This article walks you through that.
Based off of that, I created a new C# Project called TFSTasks. I then created a new class called CorrectTimeStampsOnClientFromServer, inheriting from Microsoft.Build.Utilities.Task. I will take in three properties – the TFS Server, the SourceDir and the TeamProject. To do that, I just add simple properties:
private string sourceDir;
[Required]
public string SourceDir
{
get { return sourceDir; }
set { sourceDir = value; }
}
private string teamProject;
[Required]
public string TeamProject
{
get { return teamProject; }
set { teamProject = value; }
}
private string tfsServer;
[Required]
public String TfsServer
{
get { return tfsServer; }
set { tfsServer = value; }
}
Now on to the meat. In Execute, we simply connect to the TFS Server and get all of the items starting at the SourceDir. We then loop over each item, find the file on the local system, and modify the local file’s timestamp to match the check-in date.
Why the check-in date? Unfortunately, TFS doesn’t capture the File System information like Last Modified, etc, when you check in the file. So the checked-in date is the best we can do.
So, again, first we connect to TFS and get a reference to all of the items in the SourceDir:
TeamFoundationServer tfs = new TeamFoundationServer(this.TfsServer);
VersionControlServer vcs = tfs.GetService(typeof(VersionControlServer)) as VersionControlServer;
ItemSet itemSet = vcs.GetItems(this.SourceDir, RecursionType.Full);
Then we loop over the items and process the files and folders:
foreach (Item item in itemSet.Items)
{
Log.LogMessage("Found Server Item " + item.ServerItem);
// $/Code Coverage/
string pathPrefix = "$/" + this.TeamProject;
string localPath = this.SourceDir + item.ServerItem.Substring(pathPrefix.Length).Replace('/', '\\');
Log.LogMessage("localPath: " + localPath);
if (item.ItemType == ItemType.File)
{
ProcessFile(item, localPath);
}
else if (item.ItemType == ItemType.Folder)
{
ProcessDirectory(item, localPath);
}
}
ProcessFile just looks like:
private void ProcessFile(Item item, string localPath)
{
FileInfo file = new FileInfo(localPath);
if (file.Exists)
{
Log.LogMessage(" Found local file. Updating time stamp.");
Log.LogMessage(" Before change: Last Write Time: " + file.LastWriteTime.ToString());
file.LastWriteTime = item.CheckinDate;
Log.LogMessage(" After change: Last Write Time: " + file.LastWriteTime.ToString());
}
else
{
Log.LogWarning("The server item could not be found in the local space");
Log.LogWarning("Looked for it at: " + localPath);
}
}
ProcessDirectory looks identical, except it uses DirectoryInfo objects.
And that’s it! To call it, I added the TFSTasks.dll to the tools directory of my Team Project, and modified my TFSBuild.proj file to add the following just before the closing project node:
TaskName="TFSTasks.CorrectTimeStampsOnClientFromServer"
AssemblyFile="$(SolutionRoot)\\tools\\TFSTasks.dll" />
TfsServer=”http://tfs:8080″
SourceDir=”$(SolutionRoot)”
TeamProject=”$(TeamProject)” />
However, there’s one thing to note. Currently this task will fail because it looks like Team Build keeps a reference open to the .sln file. I’m investigating this and will post again if a solution (no pun intended) is found.
You can download the code and a sample TFSBuild.proj file below. Note that this was build using VSTS/TFS 2008, so there may need to be slight modifications to do this in 2005.
cory, thanks for this example. i’ve posted on several forums and blogs concerning this issue and the TFS team seems to think having the file dates set this way would break incremental builds instead of enabling them.
btw, i see that you’re manully modifying the path of the server item to get the local item. if memory serves, isn’t there a function on the VersionControlServer or Workspace objects to get the local path?
Cory, Re: “Fixing the timestamps on files from Team Foundation Server”
When you published this article you said, “Currently this task will fail because it looks like Team Build keeps a reference open to the .sln file. I’m investigating this and will post again if a solution (no pun intended) is found.”
Have you found a way to execute this without the task failing?
Thank, David Cohen