public bool ApplyChanges(IEnumerable<PendingChange> changes, PendingChangeApplyArgs args) { using (PendingCommitState state = new PendingCommitState(Context, changes)) { if (!PreCommit_SaveDirty(state)) return false; if (!PreCommit_AddNewFiles(state)) return false; if (!PreCommit_HandleMissingFiles(state)) return false; state.FlushState(); if (!PreCommit_AddNeededParents(state)) return false; return true; } }
/// <summary> /// Verifies if the log message is valid for the current policy /// </summary> /// <param name="state">The state.</param> /// <returns></returns> private bool PreCommit_VerifyLogMessage(PendingCommitState state) { if (String.IsNullOrWhiteSpace(state.LogMessage)) { DialogResult result = state.MessageBox.Show(PccStrings.NoMessageProvided, "", MessageBoxButtons.YesNo, MessageBoxIcon.Warning); if (result == DialogResult.No) return false; } if (state.LogMessage == null) return true; // Skip checks // And after checking whether the message is valid: Normalize the message the way the CLI would // * No whitespace at the end of lines // * Always a newline at the end StringBuilder sb = new StringBuilder(); foreach (string line in state.LogMessage.Replace("\r", "").Split('\n')) { sb.AppendLine(line.TrimEnd()); } string msg = sb.ToString(); // And make sure the log message ends with a single newline state.LogMessage = msg.TrimEnd() + Environment.NewLine; return true; // Logmessage always ok for now }
/// <summary> /// Save all documents in the selection /// </summary> /// <param name="state">The state.</param> /// <returns></returns> private bool PreCommit_SaveDirty(PendingCommitState state) { IVisualGitOpenDocumentTracker tracker = state.GetService<IVisualGitOpenDocumentTracker>(); if (!tracker.SaveDocuments(state.CommitPaths)) { state.MessageBox.Show(PccStrings.FailedToSaveBeforeCommit, "", System.Windows.Forms.MessageBoxButtons.OK, System.Windows.Forms.MessageBoxIcon.Exclamation); return false; } return true; }
private bool PreCommit_VerifyConfiguration(PendingCommitState state) { using (GitPoolClient client = state.GetService<IGitClientPool>().GetNoUIClient()) { IGitConfig config = client.GetUserConfig(); if ( String.IsNullOrEmpty(config.GetString("user", null, "name")) || String.IsNullOrEmpty(config.GetString("user", null, "email")) ) { state.MessageBox.Show(PccStrings.NameOrEmailNotSet, "", MessageBoxButtons.OK, MessageBoxIcon.Warning); return false; } } return true; }
/// <summary> /// Adds all files which are marked as to be added to Git /// </summary> /// <param name="state">The state.</param> /// <returns></returns> private bool PreCommit_AddNewFiles(PendingCommitState state) { foreach (PendingChange pc in state.Changes) { if (pc.Change != null && pc.Change.State == PendingChangeKind.New) { GitItem item = pc.GitItem; // HACK: figure out why PendingChangeKind.New is still true if (item.IsVersioned) continue; // No need to add GitAddArgs a = new GitAddArgs(); a.AddParents = true; a.Depth = GitDepth.Empty; try { state.Client.Add(pc.FullPath, a); } catch (GitException ex) { GetService<IVisualGitErrorHandler>().OnWarning(ex); return false; } } } return true; }
/// <summary> /// Fixes up missing files by fixing their casing or deleting them /// </summary> /// <param name="state">The state.</param> /// <returns></returns> private bool PreCommit_HandleMissingFiles(PendingCommitState state) { foreach (string path in new List<string>(state.CommitPaths)) { GitItem item = state.Cache[path]; if (item.Status.State != GitStatus.Missing) continue; if (item.IsCasingConflicted) { string correctCasing = GetGitCasing(item); string actualCasing = GitTools.GetTruePath(item.FullPath); if (correctCasing == null || actualCasing == null || !string.Equals(correctCasing, actualCasing, StringComparison.OrdinalIgnoreCase)) continue; // Nothing to fix here :( string correctFile = Path.GetFileName(correctCasing); string actualFile = Path.GetFileName(actualCasing); if (correctFile == actualFile) continue; // Casing issue is not in the file; can't fix :( IVisualGitOpenDocumentTracker odt = GetService<IVisualGitOpenDocumentTracker>(); using (odt.LockDocument(correctCasing, DocumentLockType.NoReload)) using (odt.LockDocument(actualCasing, DocumentLockType.NoReload)) { try { File.Move(actualCasing, correctCasing); // Fix the name in the commit list state.CommitPaths[state.CommitPaths.IndexOf(path)] = actualCasing; } catch { } finally { item.MarkDirty(); GetService<IFileStatusMonitor>().ScheduleGlyphUpdate(item.FullPath); } } } else if (!item.Exists) { GitDeleteArgs da = new GitDeleteArgs(); da.KeepLocal = true; try { state.Client.Delete(path, da); } catch (GitException ex) { GetService<IVisualGitErrorHandler>().OnWarning(ex); return false; } } } return true; }
/// <summary> /// Finalizes the action by committing to the repository /// </summary> /// <param name="state">The state.</param> /// <returns></returns> private bool Commit_CommitToRepository(PendingCommitState state) { bool ok = false; GitCommitResult rslt = null; GitDepth depth = state.CalculateCommitDepth(); if (depth == GitDepth.Unknown) return false; ProgressRunnerResult r = state.GetService<IProgressRunner>().RunModal(PccStrings.CommitTitle, delegate(object sender, ProgressWorkerArgs e) { GitCommitArgs ca = new GitCommitArgs(); ca.Depth = depth; ca.LogMessage = state.LogMessage; ca.AmendLastCommit = state.AmendLastCommit; try { ok = e.Client.Commit( state.CommitPaths, ca, out rslt); } catch (GitException ex) { GetService<IVisualGitErrorHandler>().OnWarning(ex); ok = false; } }); if (rslt != null) { ILastChangeInfo ci = GetService<ILastChangeInfo>(); if (ci != null && rslt.Revision != null) ci.SetLastChange(PccStrings.CommittedPrefix, rslt.Revision.ToString()); } return ok; }
private void PostCommit_IssueTracker(PendingCommitState state, SvnCommitResult result) { IAnkhIssueService iService = GetService<IAnkhIssueService>(); if (iService != null) { IssueRepository iRepo = iService.CurrentIssueRepository; if (iRepo != null) { List<Uri> uris = new List<Uri>(); foreach (PendingChange pc in state.Changes) { uris.Add(pc.Uri); } PostCommitArgs pca = new PostCommitArgs(uris.ToArray(), result.Revision, state.LogMessage); try { iRepo.PostCommit(pca); } catch { }; } } }
/// <summary> /// Verifies if the log message is valid for the current policy /// </summary> /// <param name="state">The state.</param> /// <returns></returns> private bool PreCommit_VerifyLogMessage(PendingCommitState state) { if (state.LogMessage == null) return true; // Skip checks // And after checking whether the message is valid: Normalize the message the way the CLI would // * No whitespace at the end of lines // * Always a newline at the end StringBuilder sb = new StringBuilder(); foreach (string line in state.LogMessage.Replace("\r", "").Split('\n')) { sb.AppendLine(line.TrimEnd()); } string msg = sb.ToString(); // Use the project commit settings class to add an issue number (if available) IProjectCommitSettings pcs = state.GetService<IProjectCommitSettings>(); if (pcs.WarnIfNoIssue && pcs.ShowIssueBox && string.IsNullOrEmpty(state.IssueText) && state.MessageBox.Show(PccStrings.NoIssueNumber, "", MessageBoxButtons.YesNo, MessageBoxIcon.Warning) == DialogResult.No) { return false; } msg = pcs.BuildLogMessage(msg, state.IssueText); // And make sure the log message ends with a single newline state.LogMessage = msg.TrimEnd() + Environment.NewLine; return true; // Logmessage always ok for now }
public bool CreatePatch(IEnumerable<PendingChange> changes, PendingChangeCreatePatchArgs args) { using (PendingCommitState state = new PendingCommitState(Context, changes)) { if (!PreCommit_VerifySingleRoot(state)) // Verify single root 'first' return false; if (!PreCommit_SaveDirty(state)) return false; if (args.AddUnversionedFiles) { if (!PreCommit_AddNewFiles(state)) return false; if (!PreCommit_HandleMissingFiles(state)) return false; } state.FlushState(); if (!PreCommit_AddNeededParents(state)) return false; if (!PreCommit_VerifySingleRoot(state)) // Verify single root 'again' return false; } string relativeToPath = args.RelativeToPath; string relativeToPathP = relativeToPath.EndsWith("\\") ? relativeToPath : (relativeToPath + "\\"); string fileName = args.FileName; GitRevisionRange revRange = new GitRevisionRange(GitRevision.Base, GitRevision.Working); GitDiffArgs a = new GitDiffArgs(); a.IgnoreAncestry = true; a.NoDeleted = false; a.Depth = GitDepth.Empty; using (MemoryStream stream = new MemoryStream()) { GetService<IProgressRunner>().RunModal(PccStrings.DiffTitle, delegate(object sender, ProgressWorkerArgs e) { foreach (PendingChange pc in changes) { GitItem item = pc.GitItem; GitWorkingCopy wc; if (!string.IsNullOrEmpty(relativeToPath) && item.FullPath.StartsWith(relativeToPathP, StringComparison.OrdinalIgnoreCase)) a.RelativeToPath = relativeToPath; else if ((wc = item.WorkingCopy) != null) a.RelativeToPath = wc.FullPath; else a.RelativeToPath = null; e.Client.Diff(item.FullPath, revRange, a, stream); } stream.Flush(); stream.Position = 0; }); using (StreamReader sr = new StreamReader(stream)) { string line; // Parse to lines to resolve EOL issues using (StreamWriter sw = File.CreateText(fileName)) { while (null != (line = sr.ReadLine())) sw.WriteLine(line); } } } return true; }
private bool PreCommit_VerifyIssueTracker(PendingCommitState state) { IAnkhIssueService iService = state.GetService<IAnkhIssueService>(); if (iService != null) { IssueRepository iRepo = iService.CurrentIssueRepository; if (iRepo != null) { List<Uri> uris = new List<Uri>(); foreach (PendingChange pc in state.Changes) { uris.Add(pc.Uri); } PreCommitArgs args = new PreCommitArgs(uris.ToArray(), 1); args.CommitMessage = state.LogMessage; iRepo.PreCommit(args); if (args.Cancel) { return false; } state.LogMessage = args.CommitMessage; } } return true; }
/// <summary> /// Fixes up missing files by fixing their casing or deleting them /// </summary> /// <param name="state">The state.</param> /// <returns></returns> private bool PreCommit_HandleMissingFiles(PendingCommitState state) { foreach (string path in new List<string>(state.CommitPaths)) { SvnItem item = state.Cache[path]; if (item.Status.LocalContentStatus != SvnStatus.Missing) continue; if (item.IsCasingConflicted) { string correctCasing = GetSvnCasing(item); string actualCasing = SvnTools.GetTruePath(item.FullPath); if (correctCasing == null || actualCasing == null || !string.Equals(correctCasing, actualCasing, StringComparison.OrdinalIgnoreCase)) continue; // Nothing to fix here :( string correctFile = Path.GetFileName(correctCasing); string actualFile = Path.GetFileName(actualCasing); if (correctFile == actualFile) continue; // Casing issue is not in the file; can't fix :( IAnkhOpenDocumentTracker odt = GetService<IAnkhOpenDocumentTracker>(); using (odt.LockDocument(correctCasing, DocumentLockType.NoReload)) using (odt.LockDocument(actualCasing, DocumentLockType.NoReload)) { try { File.Move(actualCasing, correctCasing); // Fix the name in the commit list state.CommitPaths[state.CommitPaths.IndexOf(path)] = actualCasing; } catch { } finally { item.MarkDirty(); GetService<IFileStatusMonitor>().ScheduleGlyphUpdate(item.FullPath); } } } else if (!item.Exists) { SvnDeleteArgs da = new SvnDeleteArgs(); da.KeepLocal = true; da.ThrowOnError = false; if (!state.Client.Delete(path, da)) { state.MessageBox.Show(da.LastException.Message, "", System.Windows.Forms.MessageBoxButtons.OK, System.Windows.Forms.MessageBoxIcon.Error); return false; } } } return true; }
/// <summary> /// Adds all files which are marked as to be added to subversion /// </summary> /// <param name="state">The state.</param> /// <returns></returns> private bool PreCommit_AddNewFiles(PendingCommitState state) { foreach (PendingChange pc in state.Changes) { if (pc.Change != null && pc.Change.State == PendingChangeKind.New) { SvnItem item = pc.SvnItem; // HACK: figure out why PendingChangeKind.New is still true if (item.IsVersioned) continue; // No need to add SvnAddArgs a = new SvnAddArgs(); a.AddParents = true; a.Depth = SvnDepth.Empty; a.ThrowOnError = false; if (!state.Client.Add(pc.FullPath, a)) { if (a.LastException != null && a.LastException.SvnErrorCode == SvnErrorCode.SVN_ERR_WC_UNSUPPORTED_FORMAT) { state.MessageBox.Show(a.LastException.Message + Environment.NewLine + Environment.NewLine + PccStrings.YouCanDownloadAnkh, "", MessageBoxButtons.OK, System.Windows.Forms.MessageBoxIcon.Error); return false; } else if (state.MessageBox.Show(a.LastException.Message, "", MessageBoxButtons.OKCancel, System.Windows.Forms.MessageBoxIcon.Error) == DialogResult.Cancel) return false; } } } return true; }
private bool PreCommit_VerifyNoConflicts(PendingCommitState state) { foreach (PendingChange pc in state.Changes) { GitItem item = pc.GitItem; if(item.IsConflicted) { state.MessageBox.Show(PccStrings.OneOrMoreItemsConflicted, "", MessageBoxButtons.OK, MessageBoxIcon.Exclamation); return false; } } return true; }
/// <summary> /// Adds all new parents of files to add to Git /// </summary> /// <param name="state">The state.</param> /// <returns></returns> private bool PreCommit_AddNeededParents(PendingCommitState state) { foreach (string path in new List<string>(state.CommitPaths)) { GitItem item = state.Cache[path]; if (item.IsNewAddition) { GitItem parent = item.Parent; GitWorkingCopy wc = item.WorkingCopy; if (wc == null) { // This should be impossible. A node can't be added and not in a WC item.MarkDirty(); continue; } string wcPath = wc.FullPath; while (parent != null && !string.Equals(parent.FullPath, wcPath, StringComparison.OrdinalIgnoreCase) && parent.IsNewAddition) { if (!state.CommitPaths.Contains(parent.FullPath)) state.CommitPaths.Add(parent.FullPath); parent = parent.Parent; } } } return true; }
private bool PreCommit_VerifySingleRoot(PendingCommitState state) { GitWorkingCopy wc = null; foreach (PendingChange pc in state.Changes) { GitItem item = pc.GitItem; if (item.IsVersioned || item.IsVersionable) { GitWorkingCopy w = item.WorkingCopy; if (wc == null) wc = w; else if (w != null && w != wc) { StringBuilder sb = new StringBuilder(); sb.AppendLine(PccStrings.CommitSingleWc); sb.AppendFormat(PccStrings.WorkingCopyX, wc.FullPath); sb.AppendLine(); sb.AppendFormat(PccStrings.WorkingCopyX, w.FullPath); sb.AppendLine(); state.MessageBox.Show(sb.ToString(), "", MessageBoxButtons.OK, MessageBoxIcon.Exclamation); return false; } } } return true; }
/// <summary> /// Finalizes the action by committing to the repository /// </summary> /// <param name="state">The state.</param> /// <returns></returns> private bool Commit_CommitToRepository(PendingCommitState state) { bool ok = false; SvnCommitResult rslt = null; SvnDepth depth = state.CalculateCommitDepth(); if (depth == SvnDepth.Unknown) return false; StringBuilder outOfDateMessage = null; ProgressRunnerResult r = state.GetService<IProgressRunner>().RunModal(PccStrings.CommitTitle, delegate(object sender, ProgressWorkerArgs e) { SvnCommitArgs ca = new SvnCommitArgs(); ca.Depth = depth; ca.KeepLocks = state.KeepLocks; ca.KeepChangeLists = state.KeepChangeLists; ca.LogMessage = state.LogMessage; ca.AddExpectedError(SvnErrorCode.SVN_ERR_RA_OUT_OF_DATE); ca.AddExpectedError(SvnErrorCode.SVN_ERR_WC_NOT_UP_TO_DATE); ca.AddExpectedError(SvnErrorCode.SVN_ERR_FS_TXN_OUT_OF_DATE); ok = e.Client.Commit( state.CommitPaths, ca, out rslt); if(!ok && ca.LastException != null) { switch (ca.LastException.SvnErrorCode) { case SvnErrorCode.SVN_ERR_WC_NOT_UP_TO_DATE: case SvnErrorCode.SVN_ERR_RA_OUT_OF_DATE: case SvnErrorCode.SVN_ERR_FS_TXN_OUT_OF_DATE: outOfDateMessage = new StringBuilder(); Exception ex = ca.LastException; while(ex != null) { outOfDateMessage.AppendLine(ex.Message); ex = ex.InnerException; } break; } } }); if (outOfDateMessage != null) { state.MessageBox.Show(outOfDateMessage.ToString(), PccStrings.OutOfDateCaption, MessageBoxButtons.OK, MessageBoxIcon.Error); } if (rslt != null) { ILastChangeInfo ci = GetService<ILastChangeInfo>(); if (ci != null) ci.SetLastChange(PccStrings.CommittedPrefix, rslt.Revision.ToString()); if (!string.IsNullOrEmpty(rslt.PostCommitError)) state.MessageBox.Show(rslt.PostCommitError, PccStrings.PostCommitError, MessageBoxButtons.OK, MessageBoxIcon.Exclamation); else { PostCommit_IssueTracker(state, rslt); } } return ok; }