static void ProcessHistoryInternal(DXVcsWrapper vcsWrapper, GitWrapper gitWrapper, RegisteredUsers users, User defaultUser, string localGitDir, TrackBranch branch, IList<CommitItem> commits, SyncHistoryWrapper syncHistory) { ProjectExtractor extractor = new ProjectExtractor(commits, (item) => { var localCommits = vcsWrapper.GetCommits(item.TimeStamp, item.Items).Where(x => !IsLabel(x)).ToList(); bool hasModifications = false; GitCommit last = null; string token = syncHistory.CreateNewToken(); foreach (var localCommit in localCommits) { string localProjectPath = Path.Combine(localGitDir, localCommit.Track.ProjectPath); DirectoryHelper.DeleteDirectory(localProjectPath); string trackPath = branch.GetTrackRoot(localCommit.Track); vcsWrapper.GetProject(vcsServer, trackPath, localProjectPath, item.TimeStamp); Log.Message($"git stage {localCommit.Track.ProjectPath}"); gitWrapper.Stage(localCommit.Track.ProjectPath); string author = CalcAuthor(localCommit, defaultUser); var comment = CalcComment(localCommit, author, token); User user = users.GetUser(author); try { gitWrapper.Commit(comment.ToString(), user, localCommit.TimeStamp, false); last = gitWrapper.FindCommit(x => true); hasModifications = true; } catch (Exception) { Log.Message($"Empty commit detected for {localCommit.Author} {localCommit.TimeStamp}."); } } if (hasModifications) { gitWrapper.PushEverything(); syncHistory.Add(last.Sha, item.TimeStamp.Ticks, token); } else { var head = syncHistory.GetHead(); syncHistory.Add(head.GitCommitSha, item.TimeStamp.Ticks, token); string author = CalcAuthor(item, defaultUser); Log.Message($"Push empty commits rejected for {author} {item.TimeStamp}."); } syncHistory.Save(); }); int i = 0; while (extractor.PerformExtraction()) Log.Message($"{++i} from {commits.Count} push to branch {branch.Name} completed."); }
static IList<CommitItem> GenerateCommits(DXVcsWrapper vcsWrapper, TrackBranch branch, SyncHistoryWrapper syncHistory, bool mergeCommits) { DateTime lastCommit = CalcLastCommitDate(syncHistory); Log.Message($"Last commit has been performed at {lastCommit.ToLocalTime()}."); var history = vcsWrapper.GenerateHistory(branch, lastCommit).OrderBy(x => x.ActionDate).ToList(); Log.Message($"History generated. {history.Count} history items obtained."); IList<CommitItem> commits = vcsWrapper.GenerateCommits(history).Where(x => x.TimeStamp > lastCommit && !IsLabel(x)).ToList(); if (mergeCommits) commits = vcsWrapper.MergeCommits(commits); return commits; }
static DateTime CalcLastCommitDate(SyncHistoryWrapper syncHistory) { var head = syncHistory.GetHistoryHead(); return new DateTime(head.VcsCommitTimeStamp); }
static MergeRequestResult ProcessOpenedMergeRequest(DXVcsWrapper vcsWrapper, GitWrapper gitWrapper, GitLabWrapper gitLabWrapper, RegisteredUsers users, User defaultUser, string localGitDir, TrackBranch branch, MergeRequest mergeRequest, SyncHistoryWrapper syncHistory) { string autoSyncToken = syncHistory.CreateNewToken(); var lastHistoryItem = syncHistory.GetHead(); Log.Message($"Start merging mergerequest {mergeRequest.Title}"); Log.ResetErrorsAccumulator(); var changes = gitLabWrapper.GetMergeRequestChanges(mergeRequest).ToList(); if (changes.Count >= MaxChangesCount) { Log.Error($"Merge request contains more than {MaxChangesCount} changes and cannot be processed. Split it into smaller merge requests"); AssignBackConflictedMergeRequest(gitLabWrapper, users, mergeRequest, CalcCommentForFailedCheckoutMergeRequest(null)); return MergeRequestResult.Failed; } var genericChange = changes .Where(x => branch.TrackItems.FirstOrDefault(track => CheckItemForChangeSet(x, track)) != null) .Select(x => ProcessMergeRequestChanges(mergeRequest, x, localGitDir, branch, autoSyncToken)).ToList(); bool ignoreValidation = gitLabWrapper.ShouldIgnoreSharedFiles(mergeRequest); if (!ValidateMergeRequestChanges(gitLabWrapper, mergeRequest, ignoreValidation) || !vcsWrapper.ProcessCheckout(genericChange, ignoreValidation, branch)) { Log.Error("Merging merge request failed because failed validation."); AssignBackConflictedMergeRequest(gitLabWrapper, users, mergeRequest, CalcCommentForFailedCheckoutMergeRequest(genericChange)); vcsWrapper.ProcessUndoCheckout(genericChange); return MergeRequestResult.CheckoutFailed; } CommentWrapper comment = CalcComment(mergeRequest, branch, autoSyncToken); mergeRequest = gitLabWrapper.ProcessMergeRequest(mergeRequest, comment.ToString()); if (mergeRequest.State == "merged") { Log.Message("Merge request merged successfully."); gitWrapper.Pull(); gitWrapper.LFSPull(); var gitCommit = gitWrapper.FindCommit(x => CommentWrapper.Parse(x.Message).Token == autoSyncToken); long timeStamp = lastHistoryItem.VcsCommitTimeStamp; if (gitCommit != null && vcsWrapper.ProcessCheckIn(genericChange, comment.ToString())) { var checkinHistory = vcsWrapper.GenerateHistory(branch, new DateTime(timeStamp)).Where(x => x.ActionDate.Ticks > timeStamp); var lastCommit = checkinHistory.OrderBy(x => x.ActionDate).LastOrDefault(); long newTimeStamp = lastCommit?.ActionDate.Ticks ?? timeStamp; var mergeRequestResult = MergeRequestResult.Success; if (!ValidateMergeRequest(vcsWrapper, branch, lastHistoryItem, defaultUser)) mergeRequestResult = MergeRequestResult.Mixed; if (!ValidateChangeSet(genericChange)) mergeRequestResult = MergeRequestResult.Mixed; syncHistory.Add(gitCommit.Sha, newTimeStamp, autoSyncToken, mergeRequestResult == MergeRequestResult.Success ? SyncHistoryStatus.Success : SyncHistoryStatus.Mixed); syncHistory.Save(); Log.Message("Merge request checkin successfully."); return mergeRequestResult; } Log.Error("Merge request checkin failed."); if (gitCommit == null) Log.Error($"Can`t find git commit with token {autoSyncToken}"); var failedHistory = vcsWrapper.GenerateHistory(branch, new DateTime(timeStamp)); var lastFailedCommit = failedHistory.OrderBy(x => x.ActionDate).LastOrDefault(); syncHistory.Add(gitCommit.Sha, lastFailedCommit?.ActionDate.Ticks ?? timeStamp, autoSyncToken, SyncHistoryStatus.Failed); syncHistory.Save(); return MergeRequestResult.Failed; } Log.Message($"Merge request merging failed due conflicts. Resolve conflicts manually."); vcsWrapper.ProcessUndoCheckout(genericChange); AssignBackConflictedMergeRequest(gitLabWrapper, users, mergeRequest, CalcCommentForFailedCheckoutMergeRequest(genericChange)); return MergeRequestResult.Conflicts; }
static ProcessHistoryResult ProcessHistory(DXVcsWrapper vcsWrapper, GitWrapper gitWrapper, RegisteredUsers users, User defaultUser, string gitRepoPath, string localGitDir, TrackBranch branch, int commitsCount, SyncHistoryWrapper syncHistory, bool mergeCommits) { IList<CommitItem> commits = GenerateCommits(vcsWrapper, branch, syncHistory, mergeCommits); if (commits.Count > commitsCount) { Log.Message($"Commits generated. First {commitsCount} of {commits.Count} commits taken."); commits = commits.Take(commitsCount).ToList(); } else { Log.Message($"Commits generated. {commits.Count} commits taken."); } if (commits.Count > 0) ProcessHistoryInternal(vcsWrapper, gitWrapper, users, defaultUser, localGitDir, branch, commits, syncHistory); Log.Message($"Importing history from vcs completed."); return commits.Count > commitsCount ? ProcessHistoryResult.NotEnough : ProcessHistoryResult.Success; }
static MergeRequestResult ProcessMergeRequest(DXVcsWrapper vcsWrapper, GitWrapper gitWrapper, GitLabWrapper gitLabWrapper, RegisteredUsers users, User defaultUser, string localGitDir, TrackBranch branch, MergeRequest mergeRequest, SyncHistoryWrapper syncHistory) { switch (mergeRequest.State) { case "reopened": case "opened": return ProcessOpenedMergeRequest(vcsWrapper, gitWrapper, gitLabWrapper, users, defaultUser, localGitDir, branch, mergeRequest, syncHistory); } return MergeRequestResult.InvalidState; }
static int ProcessMergeRequests(DXVcsWrapper vcsWrapper, GitWrapper gitWrapper, GitLabWrapper gitLabWrapper, RegisteredUsers users, User defaultUser, string gitRepoPath, string localGitDir, string branchName, string tracker, SyncHistoryWrapper syncHistory, string userName) { var project = gitLabWrapper.FindProject(gitRepoPath); TrackBranch branch = GetBranch(branchName, tracker, vcsWrapper); if (branch == null) { Log.Error($"Specified branch {branchName} not found in track file."); return 1; } var mergeRequests = GetMergeRequests(gitLabWrapper, branchName, userName, project); if (!mergeRequests.Any()) { Log.Message("Zero registered merge requests."); return 0; } int result = 0; foreach (var mergeRequest in mergeRequests) { var mergeRequestResult = ProcessMergeRequest(vcsWrapper, gitWrapper, gitLabWrapper, users, defaultUser, localGitDir, branch, mergeRequest, syncHistory); if (mergeRequestResult == MergeRequestResult.Failed) return 1; if (mergeRequestResult == MergeRequestResult.CheckoutFailed || mergeRequestResult == MergeRequestResult.Conflicts || mergeRequestResult == MergeRequestResult.InvalidState) result = 1; } return result; }
static CheckMergeChangesResult CheckChangesForMerging(GitLabWrapper gitLabWrapper, string gitRepoPath, string branchName, SyncHistoryItem head, DXVcsWrapper vcsWrapper, TrackBranch branch, SyncHistoryWrapper syncHistory, User defaultUser) { var project = gitLabWrapper.FindProject(gitRepoPath); if (project == null) { Log.Error($"Can`t find git project {gitRepoPath}"); return CheckMergeChangesResult.Error; } var gitlabBranch = gitLabWrapper.GetBranches(project).Single(x => x.Name == branchName); if (gitlabBranch.Commit.Id.Equals(new Sha1(head.GitCommitSha))) { var commits = GenerateCommits(vcsWrapper, branch, syncHistory, false); if (commits.Count == 0) { var mergeRequests = GetMergeRequests(gitLabWrapper, branchName, defaultUser.UserName, project); if (!mergeRequests.Any()) { Log.Message("Zero registered merge requests."); return CheckMergeChangesResult.NoChanges; } } } return CheckMergeChangesResult.HasChanges; }
static int DoSyncWork(SyncOptions clo) { string localGitDir = clo.LocalFolder != null && Path.IsPathRooted(clo.LocalFolder) ? clo.LocalFolder : Path.Combine(Environment.CurrentDirectory, clo.LocalFolder ?? repoPath); EnsureGitDir(localGitDir); string gitRepoPath = clo.Repo; string username = clo.Login; string password = clo.Password; string gitlabauthtoken = clo.AuthToken; string branchName = clo.Branch; string trackerPath = clo.Tracker; string gitServer = clo.Server; DXVcsWrapper vcsWrapper = new DXVcsWrapper(vcsServer, username, password); TrackBranch branch = FindBranch(branchName, trackerPath, vcsWrapper); if (branch == null) return 1; string historyPath = GetVcsSyncHistory(vcsWrapper, branch.HistoryPath); if (historyPath == null) return 1; SyncHistory history = SyncHistory.Deserialize(historyPath); if (history == null) return 1; SyncHistoryWrapper syncHistory = new SyncHistoryWrapper(history, vcsWrapper, branch.HistoryPath, historyPath); var head = syncHistory.GetHistoryHead(); if (head == null) return 1; GitLabWrapper gitLabWrapper = new GitLabWrapper(gitServer, gitlabauthtoken); RegisteredUsers registeredUsers = new RegisteredUsers(gitLabWrapper, vcsWrapper); User defaultUser = registeredUsers.GetUser(username); if (!defaultUser.IsRegistered) { Log.Error($"default user {username} is not registered in the active directory."); return 1; } var checkMergeChangesResult = CheckChangesForMerging(gitLabWrapper, gitRepoPath, branchName, head, vcsWrapper, branch, syncHistory, defaultUser); if (checkMergeChangesResult == CheckMergeChangesResult.NoChanges) return 0; if (checkMergeChangesResult == CheckMergeChangesResult.Error) return 1; GitWrapper gitWrapper = CreateGitWrapper(gitRepoPath, localGitDir, branch.Name, username, password); if (gitWrapper == null) return 1; ProcessHistoryResult processHistoryResult = ProcessHistory(vcsWrapper, gitWrapper, registeredUsers, defaultUser, gitRepoPath, localGitDir, branch, clo.CommitsCount, syncHistory, true); if (processHistoryResult == ProcessHistoryResult.NotEnough) return 0; if (processHistoryResult == ProcessHistoryResult.Failed) return 1; int result = ProcessMergeRequests(vcsWrapper, gitWrapper, gitLabWrapper, registeredUsers, defaultUser, gitRepoPath, localGitDir, clo.Branch, clo.Tracker, syncHistory, username); if (result != 0) return result; return 0; }
static int DoPatchWork(PatchOptions clo) { string localGitDir = clo.LocalFolder != null && Path.IsPathRooted(clo.LocalFolder) ? clo.LocalFolder : Path.Combine(Environment.CurrentDirectory, clo.LocalFolder ?? repoPath); string targetRepoPath = GetSimpleGitHttpPath(clo.Repo); if (string.IsNullOrEmpty(targetRepoPath)) { Log.Error($"Can`t parse repo path {clo.Repo}"); return 1; } string sourceRepoPath = GetSimpleGitHttpPath(clo.SourceRepo); if (string.IsNullOrEmpty(sourceRepoPath)) { Log.Error($"Can`t parse source repo path {clo.SourceRepo}"); return 1; } string username = clo.Login; string password = clo.Password; string gitlabauthtoken = clo.AuthToken; string targetBranchName = clo.Branch; string trackerPath = clo.Tracker; string gitServer = clo.Server; string sourceBranchName = clo.SourceBranch; string patchdir = clo.PatchDir ?? localGitDir; DXVcsWrapper vcsWrapper = new DXVcsWrapper(vcsServer, username, password); TrackBranch trackBranch = FindBranch(targetBranchName, trackerPath, vcsWrapper); if (trackBranch == null) { return 1; } string historyPath = GetVcsSyncHistory(vcsWrapper, trackBranch.HistoryPath); if (historyPath == null) return 1; SyncHistory history = SyncHistory.Deserialize(historyPath); if (history == null) return 1; SyncHistoryWrapper syncHistory = new SyncHistoryWrapper(history, vcsWrapper, trackBranch.HistoryPath, historyPath); var head = syncHistory.GetHistoryHead(); if (head == null) return 1; GitLabWrapper gitLabWrapper = new GitLabWrapper(gitServer, gitlabauthtoken); Project targetProject = gitLabWrapper.FindProject(targetRepoPath); if (targetProject == null) { Log.Error($"Can`t find target project {targetRepoPath}."); return 1; } Log.Message($"Target project url: {targetProject.HttpUrl}"); Branch targetBranch = gitLabWrapper.GetBranch(targetProject, targetBranchName); if (targetBranch == null) { Log.Error($"Can`t find targetBranch branch {targetBranchName}"); return 1; } Log.Message($"Target branch name: {targetBranch.Name}"); var sourceProject = gitLabWrapper.FindProjectFromAll(sourceRepoPath); if (sourceProject == null) { Log.Error($"Can`t find source project {sourceRepoPath}"); return 1; } Log.Message($"Source project url: {sourceProject.HttpUrl}"); var sourceBranch = gitLabWrapper.GetBranch(sourceProject, sourceBranchName); if (sourceBranch == null) { Log.Error($"Source branch {sourceBranchName} was not found."); return 1; } Log.Message($"Target branch name: {sourceBranch.Name}"); MergeRequest mergeRequest = gitLabWrapper.GetMergeRequests(targetProject, x => x.SourceBranch == sourceBranchName && x.TargetBranch == targetBranchName && x.SourceProjectId == sourceProject.Id).FirstOrDefault(); if (mergeRequest == null) { Log.Error($"Can`t find merge request."); return 1; } Log.Message($"Merge request id: {mergeRequest.Id}."); Log.Message($"Merge request title: {mergeRequest.Title}."); var comments = gitLabWrapper.GetComments(mergeRequest); var mergeRequestSyncOptions = comments?.Where(x => IsXml(x.Note)).Where(x => { var mr = MergeRequestOptions.ConvertFromString(x.Note); return mr?.ActionType == MergeRequestActionType.sync; }).Select(x => (MergeRequestSyncAction)MergeRequestOptions.ConvertFromString(x.Note).Action).LastOrDefault(); if (mergeRequest.Assignee?.Name != username || (!mergeRequestSyncOptions?.PerformTesting ?? false)) { Log.Error($"Merge request is not assigned to service user {username} or doesn`t require testing."); return 1; } GitWrapper gitWrapper = CreateGitWrapper(sourceRepoPath, localGitDir, sourceBranchName, username, password); if (gitWrapper == null) { Log.Error("Can`t create git wrapper."); return 1; } var changes = gitLabWrapper .GetMergeRequestChanges(mergeRequest) .Where(x => trackBranch.TrackItems.FirstOrDefault(track => CheckItemForChangeSet(x, track)) != null) .Select(x => new PatchItem() { SyncAction = CalcSyncAction(x), OldPath = x.OldPath, NewPath = x.NewPath, OldVcsPath = CalcVcsPath(trackBranch, x.OldPath), NewVcsPath = CalcVcsPath(trackBranch, x.NewPath), }).ToList(); var patch = new PatchInfo() { TimeStamp = DateTime.Now.Ticks, Items = changes }; var patchPath = Path.Combine(patchdir, patchZip); using (Package zip = Package.Open(patchPath, FileMode.Create)) { SavePatchInfo(patchdir, patch); AddPart(zip, patchdir, "patch.info"); foreach (var path in CalcFilesForPatch(patch)) { AddPart(zip, localGitDir, path); } } Log.Message($"Patch.info generated at {patchPath}"); return 0; }