11using System ;
22using System . Collections . Generic ;
33using System . Linq ;
4- using System . Text ;
54using System . Text . RegularExpressions ;
65using System . Xml ;
76using Microsoft . Extensions . DependencyInjection ;
87using Microsoft . Extensions . Logging ;
98using Microsoft . Extensions . Options ;
109using Microsoft . TeamFoundation . Common ;
1110using Microsoft . TeamFoundation . Server ;
12- using Microsoft . TeamFoundation . Work . WebApi ;
1311using MigrationTools . Clients ;
1412using MigrationTools . DataContracts ;
1513using MigrationTools . Endpoints ;
16- using MigrationTools . Enrichers ;
1714using MigrationTools . Exceptions ;
1815using MigrationTools . FieldMaps ;
19- using MigrationTools . Processors ;
2016using MigrationTools . Processors . Infrastructure ;
2117using MigrationTools . Services ;
2218using MigrationTools . Tools . Infrastructure ;
2319using Newtonsoft . Json ;
24- using Serilog . Context ;
25- using Serilog . Events ;
26- using static Microsoft . TeamFoundation . WorkItemTracking . Client . Node ;
2720using ILogger = Serilog . ILogger ;
2821
2922
@@ -199,7 +192,6 @@ private NodeInfo GetOrCreateNode(string nodePath, DateTime? startDate, DateTime?
199192 Log . LogDebug ( " Not Found:" , currentAncestorPath ) ;
200193 parentNode = null ;
201194 }
202-
203195 }
204196 }
205197 else
@@ -286,7 +278,8 @@ public void ProcessorExecutionBegin(TfsProcessor processor)
286278 Log . LogInformation ( "Migrating all Nodes before the Processor run." ) ;
287279 MigrateAllNodeStructures ( ) ;
288280 RefreshForProcessorType ( processor ) ;
289- } else
281+ }
282+ else
290283 {
291284 Log . LogInformation ( "SKIP: Migrating all Nodes before the Processor run." ) ;
292285 }
@@ -397,13 +390,15 @@ private string GetSystemPath(string newUserPath, TfsNodeStructureType structureT
397390 private static string GetUserFriendlyPath ( string systemNodePath )
398391 {
399392 // Shape of the path is \SourceProject\StructureType\Rest\Of\The\Path, user-friendly shape skips StructureType and initial \
400- var match = Regex . Match ( systemNodePath , @"^\\(?<sourceProject>[^\\]+)\\[^\\]+\\(?<restOfThePath>.*)$" ) ;
393+ var match = Regex . Match ( systemNodePath , @"^\\(?<sourceProject>[^\\]+)\\[^\\]+( \\(?<restOfThePath>.*))? $" ) ;
401394 if ( ! match . Success )
402395 {
403396 throw new InvalidOperationException ( $ "This path is not a valid area or iteration path: { systemNodePath } ") ;
404397 }
398+ string sourceProject = match . Groups [ "sourceProject" ] . Value ;
399+ string restOfThePath = match . Groups [ "restOfThePath" ] . Success ? match . Groups [ "restOfThePath" ] . Value : string . Empty ;
405400
406- return $ "{ match . Groups [ " sourceProject" ] . Value } \\ { match . Groups [ " restOfThePath" ] . Value } ";
401+ return restOfThePath == string . Empty ? sourceProject : $ "{ sourceProject } \\ { restOfThePath } ";
407402 }
408403
409404 private void MigrateAllNodeStructures ( )
@@ -471,6 +466,11 @@ private void ProcessCommonStructure(string treeTypeSource, string localizedTreeT
471466
472467 _pathToKnownNodeMap [ structureParent . Path ] = structureParent ;
473468
469+ if ( Options . MigrateRootNodes )
470+ {
471+ XmlElement mainNode = sourceTree . ChildNodes . OfType < XmlElement > ( ) . First ( ) ;
472+ CreateNewRootNode ( mainNode , nodeStructureType ) ;
473+ }
474474 if ( sourceTree . ChildNodes [ 0 ] . HasChildNodes )
475475 {
476476 // The XPath would look like this: /Nodes/Node[Name=Area]/Children/...
@@ -479,6 +479,20 @@ private void ProcessCommonStructure(string treeTypeSource, string localizedTreeT
479479 }
480480 }
481481
482+ private void CreateNewRootNode ( XmlElement node , TfsNodeStructureType nodeStructureType )
483+ {
484+ string userFriendlyPath = GetUserFriendlyPath ( node . Attributes [ "Path" ] . Value ) ;
485+ string newUserPath = GetNewNodeName ( userFriendlyPath , nodeStructureType ) ;
486+ string newSystemPath = GetSystemPath ( newUserPath , nodeStructureType , _targetLanguageMaps ) ;
487+ if ( ! Regex . IsMatch ( newSystemPath , @"\\" + nodeStructureType + @"\\?$" ) )
488+ {
489+ // Do not do anything if there is no node name after structure type for the new (target) node.
490+ // For example, if the path is just "\Project\Area" or "\Project\Iteration".
491+ // This will happen if there are no mappings for nodes.
492+ GetOrCreateNode ( newSystemPath , null , null ) ;
493+ }
494+ }
495+
482496 private List < string > _matchedPath = new List < string > ( ) ;
483497
484498 /// <summary>
0 commit comments