private void ExecuteMigration(CommandOption token, CommandOption url, CommandOption configFile, bool forceFresh)
        {
            ConfigJson config        = null;
            var        itemCount     = 0;
            var        revisionCount = 0;
            var        importedItems = 0;
            var        sw            = new Stopwatch();

            sw.Start();

            try
            {
                var s_configFile = "";
                var s_adoUrl     = "";
                var s_adoToken   = "";
#if DEBUG
                s_configFile = "c:\\path\\to\\file\\config-scrum.json";
                s_adoUrl     = "https://dev.azure.com/org";
                s_adoToken   = "token";
#else
                s_configFile = configFile.Value();
                s_adoUrl     = url.Value();
                s_adoToken   = token.Value();
#endif

                ConfigReaderJson configReaderJson = new ConfigReaderJson(s_configFile);
                config = configReaderJson.Deserialize();

                var context = MigrationContext.Init("wi-import", config.Workspace, config.LogLevel, forceFresh);

                // connection settings for Azure DevOps/TFS:
                // full base url incl https, name of the project where the items will be migrated (if it doesn't exist on destination it will be created), personal access token
                var settings = new Settings(s_adoUrl, config.TargetProject, s_adoToken)
                {
                    BaseAreaPath      = config.BaseAreaPath ?? string.Empty,      // Root area path that will prefix area path of each migrated item
                    BaseIterationPath = config.BaseIterationPath ?? string.Empty, // Root iteration path that will prefix each iteration
                    IgnoreFailedLinks = config.IgnoreFailedLinks,
                    ProcessTemplate   = config.ProcessTemplate
                };

                // initialize Azure DevOps/TFS connection. Creates/fetches project, fills area and iteration caches.
                var agent = Agent.Initialize(context, settings);

                if (agent == null)
                {
                    Logger.Log(LogLevel.Critical, "Azure DevOps/TFS initialization error.");
                    return;
                }

                var executionBuilder = new ExecutionPlanBuilder(context);
                var plan             = executionBuilder.BuildExecutionPlan();

                itemCount     = plan.ReferenceQueue.AsEnumerable().Select(x => x.OriginId).Distinct().Count();
                revisionCount = plan.ReferenceQueue.Count;

                BeginSession(configFile.Value(), config, forceFresh, agent, itemCount, revisionCount);

                while (plan.TryPop(out ExecutionPlan.ExecutionItem executionItem))
                {
                    try
                    {
                        if (!forceFresh && context.Journal.IsItemMigrated(executionItem.OriginId, executionItem.Revision.Index))
                        {
                            continue;
                        }

                        WorkItem wi = null;

                        if (executionItem.WiId > 0)
                        {
                            wi = agent.GetWorkItem(executionItem.WiId);
                        }
                        else
                        {
                            wi = agent.CreateWI(executionItem.WiType);
                        }

                        Logger.Log(LogLevel.Info, $"Processing {importedItems + 1}/{revisionCount} - wi '{wi.Id}', jira '{executionItem.OriginId}, rev {executionItem.Revision.Index}'.");

                        agent.ImportRevision(executionItem.Revision, wi);
                        importedItems++;
                    }
                    catch (AbortMigrationException)
                    {
                        Logger.Log(LogLevel.Info, "Aborting migration...");
                        break;
                    }
                    catch (Exception ex)
                    {
                        try
                        {
                            Logger.Log(ex, $"Failed to import '{executionItem.ToString()}'.");
                        }
                        catch (AbortMigrationException)
                        {
                            break;
                        }
                    }
                }
            }
            catch (CommandParsingException e)
            {
                Logger.Log(LogLevel.Error, $"Invalid command line option(s): {e}");
            }
            catch (Exception e)
            {
                Logger.Log(e, $"Unexpected migration error.");
            }
            finally
            {
                EndSession(itemCount, revisionCount, sw);
            }
        }
        private void ExecuteMigration(CommandOption token, CommandOption url, CommandOption configFile, bool forceFresh)
        {
            ConfigJson config = null;

            try
            {
                string           configFileName   = configFile.Value();
                ConfigReaderJson configReaderJson = new ConfigReaderJson(configFileName);
                config = configReaderJson.Deserialize();

                // Migration session level settings
                // where the logs and journal will be saved, logs aid debugging, journal is for recovery of interupted process
                string migrationWorkspace = config.Workspace;

                // level of log messages that will be let through to console
                LogLevel logLevel;
                switch (config.LogLevel)
                {
                case "Info": logLevel = LogLevel.Info; break;

                case "Debug": logLevel = LogLevel.Debug; break;

                case "Warning": logLevel = LogLevel.Warning; break;

                case "Error": logLevel = LogLevel.Error; break;

                case "Critical": logLevel = LogLevel.Critical; break;

                default: logLevel = LogLevel.Debug; break;
                }

                // set up log, journal and run session settings
                var context = MigrationContext.Init(migrationWorkspace, logLevel, forceFresh);

                // connection settings for Azure DevOps/TFS:
                // full base url incl https, name of the project where the items will be migrated (if it doesn't exist on destination it will be created), personal access token
                var settings = new Settings(url.Value(), config.TargetProject, token.Value())
                {
                    BaseAreaPath      = config.BaseAreaPath ?? string.Empty,      // Root area path that will prefix area path of each migrated item
                    BaseIterationPath = config.BaseIterationPath ?? string.Empty, // Root iteration path that will prefix each iteration
                    IgnoreFailedLinks = config.IgnoreFailedLinks,
                    ProcessTemplate   = config.ProcessTemplate
                };

                // initialize Azure DevOps/TFS connection. Creates/fetches project, fills area and iteration caches.
                var agent = Agent.Initialize(context, settings);
                if (agent == null)
                {
                    Logger.Log(LogLevel.Error, "Azure DevOps/TFS initialization error. Exiting...");
                    return;
                }

                var executionBuilder = new ExecutionPlanBuilder(context);
                var plan             = executionBuilder.BuildExecutionPlan();

                while (plan.TryPop(out ExecutionPlan.ExecutionItem executionItem))
                {
                    try
                    {
                        if (!forceFresh && context.Journal.IsItemMigrated(executionItem.OriginId, executionItem.Revision.Index))
                        {
                            continue;
                        }

                        WorkItem wi = null;

                        if (executionItem.WiId > 0)
                        {
                            wi = agent.GetWorkItem(executionItem.WiId);
                        }
                        else
                        {
                            wi = agent.CreateWI(executionItem.WiType);
                        }

                        agent.ImportRevision(executionItem.Revision, wi);
                    }
                    catch (AbortMigrationException)
                    {
                        Logger.Log(LogLevel.Info, "Aborting migration...");
                        break;
                    }
                    catch (Exception ex)
                    {
                        try
                        {
                            Logger.Log(ex);
                        }
                        catch (AbortMigrationException)
                        {
                            break;
                        }
                    }
                }
            }
            catch (CommandParsingException e)
            {
                Logger.Log(LogLevel.Error, $"Invalid command line option(s): {e}");
            }
            catch (Exception e)
            {
                Logger.Log(LogLevel.Error, $"Unexpected error: {e}");
            }
        }