コード例 #1
0
 /// <summary>
 /// Rethrow the authentication failure message as a <see cref="JobException"/> if it is one.
 /// </summary>
 /// <param name="exception">The current <see cref="LibGit2SharpException"/>.</param>
 static void CheckBadCredentialsException(LibGit2SharpException exception)
 {
     if (exception.Message == "too many redirects or authentication replays")
     {
         throw new JobException("Bad git credentials exchange!", exception);
     }
 }
コード例 #2
0
        public void PushToGit(Uri gitCloneUri, string cloneDirectoryPath, CancellationToken cancellationToken)
        {
            cancellationToken.ThrowIfCancellationRequested();

            // The git directory won't exist if the hg repo is empty (gexport won't do anything).
            if (!Directory.Exists(GetGitDirectoryPath(cloneDirectoryPath)))
            {
                return;
            }

            // Git repos should be pushed with git as otherwise large (even as large as 15MB) pushes can fail.

            try
            {
                RunGitOperationOnClonedRepo(gitCloneUri, cloneDirectoryPath, (repository, remoteName) =>
                {
                    _eventLog.WriteEntry(
                        "Starting to push to git repo: " + gitCloneUri + " (" + cloneDirectoryPath + ").",
                        EventLogEntryType.Information);

                    // Refspec patterns on push are not supported, see: http://stackoverflow.com/a/25721274/220230 So
                    // can't use "+refs/*:refs/*" here, must iterate.
                    foreach (var reference in repository.Refs)
                    {
                        cancellationToken.ThrowIfCancellationRequested();

                        // Having "+" + reference.CanonicalName + ":" + reference.CanonicalName as the refspec here
                        // would be force push and completely overwrite the remote repo's content. This would always
                        // succeed no matter what is there but could wipe out changes made between the repo was fetched
                        // and pushed.
                        try
                        {
                            repository.Network.Push(repository.Network.Remotes[remoteName], reference.CanonicalName);
                        }
                        catch (LibGit2SharpException ex)
                        {
                            var extendedException = new LibGit2SharpException(
                                $"{ex.Message} Affected branch: {reference.CanonicalName}",
                                ex)
                            {
                                Source = ex.Source,
                            };

                            // Due to ex.Data being an old ICollection, this copy needs to be this awkward and not a
                            // compact LINQ expression, for example.
                            foreach (var key in ex.Data.Keys)
                            {
                                extendedException.Data.Add(key, ex.Data[key]);
                            }

                            throw extendedException;
                        }
                    }

                    _eventLog.WriteEntry(
                        "Finished pushing to git repo: " + gitCloneUri + " (" + cloneDirectoryPath + ").",
                        EventLogEntryType.Information);
                });
            }
            catch (LibGit2SharpException ex)
            {
                cancellationToken.ThrowIfCancellationRequested();

                // These will be the messages of an exception thrown when a large push times out. So we'll re-try
                // pushing commit by commit.
                if (!ex.Message.Contains("Failed to write chunk footer: The operation timed out") &&
                    !ex.Message.Contains("Failed to write chunk footer: The connection with the server was terminated abnormally") &&
                    !ex.Message.Contains("Failed to receive response: The server returned an invalid or unrecognized response"))
                {
                    throw;
                }

                _eventLog.WriteEntry(
                    "Pushing to the following git repo timed out even after retries: " + gitCloneUri + " (" +
                    cloneDirectoryPath + "). This can mean that the push was simply too large. Trying pushing again, commit by commit.",
                    EventLogEntryType.Warning);

                CdDirectory(GetGitDirectoryPath(cloneDirectoryPath).EncloseInQuotes());

                RunGitOperationOnClonedRepo(gitCloneUri, cloneDirectoryPath, (repository, remoteName) =>
                {
                    var pushCount = 0;

                    var remoteReferences = Repository
                                           .ListRemoteReferences(gitCloneUri.ToGitUrl())
                                           .Select(reference => reference.TargetIdentifier)
                                           .ToArray();

                    // Since we can only push a given commit if we also know its branch we need to iterate through them.
                    // This won't push tags but that will be taken care of next time with the above standard push logic.
                    foreach (var branch in repository.Branches)
                    {
                        cancellationToken.ThrowIfCancellationRequested();

                        // We can't use push by commit hash (as described on
                        // http://stackoverflow.com/questions/3230074/git-pushing-specific-commit) with libgit2 because
                        // of lack of support (see: https://github.com/libgit2/libgit2/issues/3178). So we need to use
                        // git directly. This is super-slow as it iterates over every commit in every branch (and a
                        // commit can be in multiple branches), but will surely work.

                        // To avoid re-pushing already pushed commits we try to start from an already existing remote
                        // reference.
                        var commitFilter = new CommitFilter
                        {
                            IncludeReachableFrom = branch,
                            SortBy = CommitSortStrategies.Reverse,
                        };
                        var commits     = repository.Commits.QueryBy(commitFilter);
                        var commitCount = commits.Count();

                        if (remoteReferences.Any())
                        {
                            // Searching for the remote reference that filters out the most commits for this branch.
                            var i = 0;
                            while (commitCount > 0 && i < remoteReferences.Length)
                            {
                                // If both IncludeReachableFrom and ExcludeReachableFrom is the same then all commits
                                // are filtered out.
                                if (remoteReferences[i] != branch.CanonicalName)
                                {
                                    // Since the filtering will be lazily evaluated the CommitFilter object needs to be
                                    // copied, otherwise the last one would take effect.
                                    var filteredCommitFilter = new CommitFilter
                                    {
                                        IncludeReachableFrom = commitFilter.IncludeReachableFrom,
                                        ExcludeReachableFrom = remoteReferences[i],
                                        SortBy = commitFilter.SortBy,
                                    };

                                    var filteredCommits     = repository.Commits.QueryBy(filteredCommitFilter);
                                    var filteredCommitCount = filteredCommits.Count();

                                    if (filteredCommitCount < commitCount)
                                    {
                                        commits     = filteredCommits;
                                        commitCount = filteredCommitCount;
                                    }
                                }

                                i++;
                            }
                        }

                        // It's costly to iterate over the Commits collection but it could also potentially consume too
                        // much memory to enumerate the whole collection once and keep it in memory. Thus we work in
                        // batches.
                        const int batchSize  = 100;
                        var currentBatchSkip = 0;
                        IEnumerable <Commit> currentBatch;

                        do
                        {
                            currentBatch = commits.Skip(currentBatchSkip).Take(batchSize);

                            foreach (var commit in currentBatch)
                            {
                                cancellationToken.ThrowIfCancellationRequested();

                                var sha = commit.Sha;

                                _eventLog.WriteEntry(
                                    "Starting to push commit " + sha + " to the branch " + branch.FriendlyName + " in the git repo: " + gitCloneUri + " (" + cloneDirectoryPath + ").",
                                    EventLogEntryType.Information);

                                var tryCount     = 0;
                                var reRunGitPush = false;

                                do
                                {
                                    try
                                    {
                                        tryCount++;
                                        reRunGitPush = false;

                                        var branchName = "refs/heads/" + branch.FriendlyName;

                                        // The --mirror switch can't be used with refspec push.
                                        RunCommandAndLogOutput(
                                            "git push " +
                                            gitCloneUri.ToGitUrl().EncloseInQuotes() + " "
                                            + sha + ":" + branchName + " --follow-tags");

                                        pushCount++;

                                        // Let's try a normal push every 300 commits. If it succeeds then the mirroring
                                        // can finish faster (otherwise it could even time out). This may be a larger
                                        // number than in hg revision by revision pulling because git commits can be
                                        // repeated among branches, so the number of commits pushed can be lower than
                                        // the number of push operations.
                                        if (pushCount > 500)
                                        {
                                            PushToGit(gitCloneUri, cloneDirectoryPath, cancellationToken);
                                            return;
                                        }
                                    }
                                    catch (CommandException commandException)
                                    {
                                        if (commandException.IsGitExceptionRealError() &&
                                            // When trying to re-push a commit we'll get an error like below, but this
                                            // isn't an issue: ! [rejected] b028f04f5092cb47db015dd7d9bfc2ad8cd8ce98 ->
                                            // master (non-fast-forward)
                                            !commandException.Error.Contains(" ! [rejected]"))
                                        {
                                            // Pushing commit by commit is very slow, thus restarting from the beginning
                                            // is tedious. Thus if pushing a git commit happens to fail then re-try on
                                            // this micro level first.
                                            if (tryCount < 3)
                                            {
                                                _eventLog.WriteEntry(
                                                    "Pushing commit " + sha + " to the branch " + branch.FriendlyName +
                                                    " in the git repo: " + gitCloneUri + " (" + cloneDirectoryPath +
                                                    ") failed with the following exception: " + commandException +
                                                    "This was try #" + tryCount + ", retrying.",
                                                    EventLogEntryType.Warning);
                                                reRunGitPush = true;

                                                // Waiting a bit so maybe the error will go away if it was temporary.
                                                Thread.Sleep(30000);
                                            }
                                            else
                                            {
                                                throw;
                                            }
                                        }
                                    }
                                }while (reRunGitPush);

                                _eventLog.WriteEntry(
                                    "Finished pushing commit " + sha + " to the branch " + branch.FriendlyName +
                                    " in the git repo: " + gitCloneUri + " (" + cloneDirectoryPath + ").",
                                    EventLogEntryType.Information);
                            }

                            currentBatchSkip += batchSize;
                        }while (currentBatchSkip < commitCount);
                    }
                });

                _eventLog.WriteEntry(
                    "Finished commit by commit pushing to the git repo: " + gitCloneUri + " (" + cloneDirectoryPath + ").",
                    EventLogEntryType.Information);
            }
        }
コード例 #3
0
 public GitResult(bool hasSucceeded, LibGit2SharpException messageException)
 {
     HasSucceeded     = hasSucceeded;
     MessageException = messageException;
 }
コード例 #4
0
 public MissingBranchException(string message, LibGit2SharpException exception) : base(message, exception)
 {
 }