/// <summary>
        /// Apply the changes between the two commits to the TFS workspace.  This will make the
        /// changes as granular as possible.
        /// </summary>
        private bool ApplyCommitToWorkspace(CommitRange commitRange)
        {
            var toApply   = new Stack <CommitRange>();
            var current   = commitRange.NewCommit;
            var oldCommit = commitRange.OldCommit;

            while (current.Sha != oldCommit.Sha && current.Parents.Count() == 1)
            {
                var parent = current.Parents.ElementAt(0);
                toApply.Push(new CommitRange(oldCommit: parent, newCommit: current));
                current = parent;
            }

            if (current.Sha != oldCommit.Sha)
            {
                toApply.Push(new CommitRange(oldCommit: oldCommit, newCommit: current));
            }

            while (toApply.Count > 0)
            {
                if (!ApplyCommitToWorkspaceCore(toApply.Pop()))
                {
                    return(false);
                }
            }

            return(true);
        }
        internal void RunCore(Commit baseCommit)
        {
            // TODO: Add cancellation, async, etc ... to the loop
            while (true)
            {
                if (!FetchGitLatest())
                {
                    return;
                }

                var refName = string.Format("refs/remotes/{0}/{1}", _remoteName, _branchName);
                var commit  = _repository.Refs[refName].ResolveAs <Commit>();
                if (commit.Sha == baseCommit.Sha)
                {
                    _host.Verbose("No changes detected, waiting");
                    Thread.Sleep(TimeSpan.FromMinutes(1));
                    continue;
                }

                if (!UpdateWorkspaceToLatest())
                {
                    return;
                }

                var commitRange = new CommitRange(oldCommit: baseCommit, newCommit: commit);
                if (!ApplyCommitToWorkspace(commitRange))
                {
                    return;
                }

                baseCommit = commit;
            }
        }
        private string CreateCheckinMessage(CommitRange commitRange, string ownerDisplayName)
        {
            var builder = new StringBuilder();

            var message      = commitRange.NewCommit.Message;
            var shortMessage = commitRange.NewCommit.MessageShort;

            if (!string.IsNullOrEmpty(shortMessage) &&
                shortMessage.StartsWith(@"Merge pull request") &&
                message.StartsWith(shortMessage))
            {
                // For merged PRs, re-arrange the checkin message to output the core commit message first.
                var strippedMessage = message.Substring(shortMessage.Length).TrimStart('\n', '\r', ' ');
                builder.AppendLine(strippedMessage);
                builder.AppendLine();
                builder.AppendLine(shortMessage);
            }
            else
            {
                builder.AppendLine(message);
                builder.AppendLine();
            }

            builder.AppendFormatLine("Ported from Git -> TFS on behalf of: '{0}'", ownerDisplayName);
            builder.AppendLine();

            builder.AppendFormatLine("From: {0}", commitRange.OldCommit.Sha);
            builder.AppendFormatLine("To: {0}", commitRange.NewCommit.Sha);
            builder.AppendLine();
            builder.AppendFormatLine("[git-commit-sha: {0}]", commitRange.NewCommit.Sha);

            var commitUri = _repositoryUrl + string.Format("/commit/{0}", commitRange.NewCommit.Sha);

            builder.AppendFormatLine("[git-commit-url: {0}", commitUri);

            builder.AppendLine();
            builder.AppendLine("*** This commit was generated automatically by a tool.  Contact [email protected] for help. ***");

            return(builder.ToString());
        }
        /// <summary>
        /// Apply the specific commit to the Workspace.  This method assumes the Workspace is in a good
        /// state at the time the application of the commit occurs.
        /// <return>True if the operation was completed successfully (or there was simply no work to do)</return>
        /// </summary>
        private bool ApplyCommitToWorkspaceCore(CommitRange commitRange)
        {
            Debug.Assert(!HasPendingChangesBesidesLock());

            var newCommit = commitRange.NewCommit;

            _host.Status("Applying {0}", newCommit);

            // Note: This is a suboptimal way of building the tree.  The file system can be changed by
            // local builds and such.  Much better to build directly from the Workspace object.
            var workspaceTree = GitUtils.CreateTreeFromWorkspace(_workspace, _workspacePath, _repository.ObjectDatabase);
            var treeChanges   = _repository.Diff.Compare <TreeChanges>(workspaceTree, newCommit.Tree, compareOptions: new LibGit2Sharp.CompareOptions()
            {
                Similarity = SimilarityOptions.Renames
            });

            if (!treeChanges.Any())
            {
                _host.Status("No changes to apply");
                return(true);
            }

            if (!ApplyGitChangeToWorkspace(treeChanges))
            {
                return(false);
            }

            var ownerInfo = GetCommitOwnerInfo(commitRange.NewCommit);

            var checkinMessage = CreateCheckinMessage(commitRange, ownerInfo.UserDisplayName);

            if (_confirmBeforeCheckin && !ConfirmCheckin(newCommit, checkinMessage))
            {
                return(false);
            }

            try
            {
                _workspace.CheckIn(
                    _workspace.GetPendingChanges(),
                    author: ownerInfo.MappedTfsAuthorName,
                    comment: checkinMessage,
                    checkinNote: null,
                    workItemChanges: null,
                    policyOverride: null);
                _cachedWorkspaceTree = newCommit.Tree;
            }
            catch (Exception ex)
            {
                _host.Error("Unable to complete checkin: {0}", ex.Message);
                _cachedWorkspaceTree = null;
                return(false);
            }

            _host.Status("Checkin complete for {0}", commitRange.NewCommit.Sha);

            // The check in will undo the lock so re-lock now
            if (_lockWorkspacePath)
            {
                LockWorkspacePath();
            }

            return(true);
        }