private async Task FindBaselineBranchAsync(MigrationContext context, MigratedProject project, CancellationToken cancellationToken)
        {
            Logger.Info($"Looking for releases branch for '{project.SourcePath}'");

            //Look for the releases folder
            (string itemPath, Version version) = await context.SourceService.FindLatestVersionAsync(ItemPath.BuildPath(project.SourcePath, Settings.ReleaseBranch), cancellationToken).ConfigureAwait(false);

            //If there is a latest release then we'll use it as baseline otherwise download the baseline branch
            if (String.IsNullOrEmpty(itemPath))
            {
                Logger.Debug($"No releases found, looking for base branch from '{project.SourcePath}'");

                var baseline = await context.SourceService.FindItemAsync(ItemPath.BuildPath(project.SourcePath, Settings.BaselineBranch), false, cancellationToken).ConfigureAwait(false);

                if (baseline != null)
                {
                    itemPath = baseline.Path;
                }
                ;
            }
            ;

            if (!String.IsNullOrEmpty(itemPath))
            {
                project.BaselinePath = itemPath;
                project.Version      = version;
            }
            ;

            Logger.Info($"Found branch for '{project.SourcePath}' - baseline Path = {itemPath}, Version = {version}");
        }
        private async Task CommitRepoAsync(MigratedProject project, GitCommand command, string message, CancellationToken cancellationToken)
        {
            Logger.Info($"Committing repo '{project.DestinationFullPath}' - {message}");

            //Have to use Git for this
            await command.CommitAndPushChangesAsync(project.LocalFullPath, Settings.GitMasterBranch, message, cancellationToken).ConfigureAwait(false);
        }
        private async Task CreateReleaseBranchAsync(MigratedProject project, GitCommand command, CancellationToken cancellationToken)
        {
            Logger.Info($"Creating branch for release for '{project.DestinationFullPath}");

            var branchName = await BuildBranchNameAsync(project, Settings.GitReleaseBranch, cancellationToken).ConfigureAwait(false);

            Logger.Info($"Branch = {branchName}");

            //Have to use Git for this
            await command.CheckOutBranchAsync(project.LocalFullPath, branchName, true, cancellationToken).ConfigureAwait(false);

            //Have to use Git for this
            //We have already pulled over the files so we can just commit to snapshot the release branch
            await command.CommitAndPushChangesAsync(project.LocalFullPath, branchName, $"Import of release {project.Version}", cancellationToken).ConfigureAwait(false);
        }
        private Task <string> BuildBranchNameAsync(MigratedProject project, string branch, CancellationToken cancellationToken)
        {
            return(Task.Run(() => {
                var engine = new TextSubstitutionEngine("{", "}");
                engine.Rules.Add(new SimpleTextSubstitutionRule("major", project.Version?.Major.ToString() ?? "0"));
                engine.Rules.Add(new SimpleTextSubstitutionRule("minor", project.Version?.Minor.ToString() ?? "0"));
                engine.Rules.Add(new SimpleTextSubstitutionRule("build", project.Version?.Build.ToString() ?? "0"));
                engine.Rules.Add(new SimpleTextSubstitutionRule("revision", project.Version?.Revision.ToString() ?? "0"));
                engine.Rules.Add(new SimpleTextSubstitutionRule("version", project.Version?.ToString() ?? "0.0.0.0"));

                engine.Rules.Add(new SimpleTextSubstitutionRule("yyyy", DateTime.Now.ToString("yyyy")));
                engine.Rules.Add(new SimpleTextSubstitutionRule("MM", DateTime.Now.ToString("MM")));
                engine.Rules.Add(new SimpleTextSubstitutionRule("dd", DateTime.Now.ToString("dd")));
                engine.Rules.Add(new SimpleTextSubstitutionRule("MMM", DateTime.Now.ToString("MMM")));
                engine.Rules.Add(new SimpleTextSubstitutionRule("yy", DateTime.Now.ToString("yy")));

                return engine.Process(branch);
            }, cancellationToken));
        }
        private async Task UpdateMetadataFileAsync(string metadataFile, MigratedProject project, CancellationToken cancellationToken)
        {
            Logger.Info($"Updating metadata file '{metadataFile}'");

            //Open or create the file
            if (!File.Exists(metadataFile))
            {
                Logger.Warning($"No metadata file found '{metadataFile}");
                return;
            }
            ;

            await Task.Run(() => {
                var engine = new TextSubstitutionEngine("{", "}");
                engine.Rules.Add(new ObjectTextSubstitutionRule <MigratedProject>(project));
                engine.Rules.Add(new SimpleTextSubstitutionRule("Date", DateTime.Now.ToString()));
                engine.Rules.Add(new SimpleTextSubstitutionRule("TfsCollectionUrl", Host.Settings.SourceCollectionUrl));

                var text = File.ReadAllText(metadataFile);
                text     = engine.Process(text);
                File.WriteAllText(metadataFile, text);
            }, cancellationToken).ConfigureAwait(false);
        }
        private async Task <MigratedProject> MigrateProjectAsync(MigrationContext context, ProjectSettings projectToMigrate, CancellationToken cancellationToken)
        {
            Logger.StartActivity($"Migrating project '{projectToMigrate.SourcePath}'");

            var migratedProject = new MigratedProject()
            {
                SourcePath    = projectToMigrate.SourcePath,
                LocalFullPath = FileSystem.BuildPath(context.OutputPath, projectToMigrate.DestinationPath),

                DestinationPath        = projectToMigrate.DestinationPath,
                DestinationProjectName = projectToMigrate.DestinationProject
            };

            migratedProject.StartProfiling();
            try
            {
                using (var logger = Logger.BeginScope("MigrateProject"))
                {
                    // Create the target repo
                    migratedProject.DestinationRepo = await CreateGitRepoAsync(context, migratedProject, cancellationToken).ConfigureAwait(false);

                    cancellationToken.ThrowIfCancellationRequested();

                    if (projectToMigrate.HasBranches)
                    {
                        // Find the baseline branch for this project, if any
                        await FindBaselineBranchAsync(context, migratedProject, cancellationToken).ConfigureAwait(false);

                        if (!String.IsNullOrEmpty(migratedProject.BaselinePath))
                        {
                            cancellationToken.ThrowIfCancellationRequested();

                            // Download the baseline
                            await DownloadFolderAsync(context.SourceService, migratedProject.BaselinePath, migratedProject.LocalFullPath, cancellationToken).ConfigureAwait(false);

                            cancellationToken.ThrowIfCancellationRequested();

                            // Clean the folder
                            var fileCount = await CleanFolderAsync(migratedProject.LocalFullPath, cancellationToken).ConfigureAwait(false);

                            cancellationToken.ThrowIfCancellationRequested();

                            // It is possible that the baseline branch is actually empty in which case we'll
                            // get an error when trying to commit so skip this if there are no files
                            if (fileCount > 0)
                            {
                                // Commit the changes to the master branch
                                var msg = $"Committing baseline version from TFS - {migratedProject.BaselinePath}";
                                await CommitRepoAsync(migratedProject, context.GitCommand, msg, cancellationToken).ConfigureAwait(false);

                                cancellationToken.ThrowIfCancellationRequested();

                                // Create a release for this version so it can be found later if there is a release
                                if (migratedProject.Version != null)
                                {
                                    await CreateReleaseBranchAsync(migratedProject, context.GitCommand, cancellationToken).ConfigureAwait(false);

                                    //Switch back to master because we won't be doing anything else with this branch
                                    Logger.Debug($"Changing to branch '{Settings.GitMasterBranch}'");
                                    await context.GitCommand.CheckOutBranchAsync(migratedProject.LocalFullPath, Settings.GitMasterBranch, false, cancellationToken).ConfigureAwait(false);
                                }
                                ;
                            }
                            else
                            {
                                Logger.Warning($"No files in baseline '{migratedProject.BaselinePath}', skipping commit of master branch");
                            }

                            cancellationToken.ThrowIfCancellationRequested();
                        }
                        ;
                    }
                    ;

                    // Download the latest version of the code to master
                    {
                        migratedProject.DevelopmentPath = projectToMigrate.HasBranches ? ItemPath.BuildPath(migratedProject.SourcePath, Settings.DevelopmentBranch) : migratedProject.SourcePath;

                        // We need to be able to tell what stuff was deleted so wipe the directory structure and start over, except the .git folder
                        await FileSystem.ClearDirectoryAsync(migratedProject.LocalFullPath, new[] { ".git" }, cancellationToken).ConfigureAwait(false);

                        cancellationToken.ThrowIfCancellationRequested();

                        // Download the dev branch
                        await DownloadFolderAsync(context.SourceService, migratedProject.DevelopmentPath, migratedProject.LocalFullPath, cancellationToken).ConfigureAwait(false);

                        cancellationToken.ThrowIfCancellationRequested();

                        // Clean the folder
                        await CleanFolderAsync(migratedProject.LocalFullPath, cancellationToken).ConfigureAwait(false);

                        cancellationToken.ThrowIfCancellationRequested();

                        // Copy the template files over any existing files
                        await CopyTemplateAsync(migratedProject.LocalFullPath, Settings.TemplatePath, cancellationToken).ConfigureAwait(false);

                        cancellationToken.ThrowIfCancellationRequested();

                        // Update the metadata file, if any
                        if (!String.IsNullOrEmpty(Settings.MetadataFile))
                        {
                            var metadataFile = FileSystem.BuildPath(migratedProject.LocalFullPath, Settings.MetadataFile);
                            await UpdateMetadataFileAsync(metadataFile, migratedProject, cancellationToken).ConfigureAwait(false);

                            cancellationToken.ThrowIfCancellationRequested();
                        }
                        ;

                        // Commit the changes
                        var msg = $"Committing latest version from TFS - {migratedProject.DevelopmentPath}";
                        await CommitRepoAsync(migratedProject, context.GitCommand, msg, cancellationToken).ConfigureAwait(false);

                        cancellationToken.ThrowIfCancellationRequested();
                    };

                    //Clean up the structure if set
                    if (Settings.CleanAfterCommit)
                    {
                        await FileSystem.RemoveDirectoryAsync(migratedProject.LocalFullPath, cancellationToken).ConfigureAwait(false);

                        cancellationToken.ThrowIfCancellationRequested();
                    }
                    ;
                };

                var totalTime = migratedProject.StopProfiling();
                Logger.StopActivity($"Migrated project '{projectToMigrate.SourcePath}' in {totalTime}");
            } catch (Exception e)
            {
                migratedProject.Error = e;
                Logger.Error(e);
            };

            return(migratedProject);
        }
        private async Task <GitRepository> CreateGitRepoAsync(MigrationContext context, MigratedProject project, CancellationToken cancellationToken)
        {
            var targetProject = await context.TargetServer.FindProjectAsync(project.DestinationProjectName, cancellationToken).ConfigureAwait(false);

            cancellationToken.ThrowIfCancellationRequested();

            var repo = new GitRepository()
            {
                ProjectReference = targetProject,
                Name             = project.DestinationPath
            };

            //Get the repo, if it exists
            var existingRepo = await context.TargetService.FindRepositoryAsync(targetProject, repo.Name, cancellationToken).ConfigureAwait(false);

            Logger.Debug($"Creating Git repo '{project.DestinationFullPath}'");

            //Delete the repo if it exists
            if (existingRepo != null)
            {
                Logger.Debug($"Deleting repo '{project.DestinationFullPath}'");
                await context.TargetService.DeleteRepositoryAsync(existingRepo, cancellationToken).ConfigureAwait(false);
            }
            ;

            //Clone the repo locally
            Logger.Debug($"Initializing repo '{project.DestinationFullPath}' to '{project.LocalFullPath}'");
            return(await CreateLocalRepositoryAsync(context, repo, context.OutputPath, cancellationToken).ConfigureAwait(false));
        }