I have an on-prem TFS 2018 and trying to move a specific project from one project collection to another. As this is not supported by Microsoft (see User Voice suggestion here and StackOverflow thread here and here) and we need full control we decided to move the project programmatically using the REST API.
So I builded a small C#.NET application and included the Microsoft.TeamFoundationServer.Client NuGet package. Everything is working as expected and I'm able to replay the full history of the project in question to another project collection.
Unfortunately, I've problems to set changesets creation date and author. And I was not able to find a solution here or anywhere else.
When I set the CreationDate, the value will be overwritten with the current date by TFS:
TfvcChangeset changeset = new TfvcChangeset()
{
Changes = changes.ToArray(),
Comment = comment,
CreatedDate = sourceChangeset.CreatedDate,
//Author = sourceChangeset.Author,
//CheckedInBy = sourceChangeset.CheckedInBy,
};
TfvcChangesetRef changesetRef = targetVersionControl.CreateChangesetAsync(changeset).Result;
// the resulting changeset reference now has current change date
The code is inspired by a Microsoft sample at GitHub.
When I either set Author or CheckedInBy (see code snippet above) an ArgumentException
occurs at the call of CreateChangesetAsync
:
ArgumentException: The combination of parameters is either not valid or not complete.
(see update below) In my case I just use the value from the source changeset, as both project collections (source and target) are part of the same TFS instance and therefore using the same identities from the TFS configuration database.
According to the Microsoft documentation, I expect that setting both values should be possible, although the documentation is not very precise at all.
Additionally, I was not able to found a solution to update the changeset once it was checked in to TFS.
Note: I know that there is some existing software out there which targets moving project, but they're either outdated, not fully working (e.g. missing move of work items) or to black-boxed for our needs.
Update: My first thoughts about the reuse of the identity in different collections of the same TFS instance where not correct. Although identities are stored only in TFS configuration database (tbl_Identities), a local ID will be assigned to every used identity in a specific project collection (tbl_IdentityMap in the project collection's database). So I created a map of both tables to assign the target Identity ID instead of the source Identity ID. I tried to get the IdentityRef
from the IdentityHttpClient
but only get a Identity
instance, so I create the IdentityRef
manually. The exception mentioned above still occurs.
// _targetConnection is the VssConnection instance
var targetIdentities = _targetConnection.GetClient<IdentityHttpClient>();
var author = targetIdentities.ReadIdentityAsync(Guid.Parse(targetAuthorId)).Result;
changeset.Author = new IdentityRef()
{
// some properties will remain default, as Identity does not contain
Id = author.Id.ToString("D", CultureInfo.InvariantCulture),
DisplayName = author.DisplayName,
//ImageUrl = author.Properties,
//IsAadIdentity = author.IsAadIdentity,
IsContainer = author.IsContainer,
//ProfileUrl = author.ProfileUrl,
//UniqueName = author..UniqueName,
//Url = author.Url,
};
I also tried to create a IdentityRef
from the source changeset's IdentityRef (with the local identity ID of the target project collection) without success (still same exception).