public IEnumerable<IChangeReport> GetChangeRecords(Revision revision) { var changes = new List<IChangeReport>(); revision.EnsureParentRevisionInfo(); if (!revision.HasAtLeastOneParent) { //describe the contents of the initial checkin foreach (var fileInRevision in Repository.GetFilesInRevision(revision)) { CollectChangesInFile(fileInRevision, null, changes); } } else { IEnumerable<RevisionNumber> parentRevs = revision.GetLocalNumbersOfParents(); foreach (RevisionNumber parentRev in parentRevs) { foreach (var fileInRevision in Repository.GetFilesInRevision(revision) .Where(fileInRevision => parentRevs.Count() == 1 || fileInRevision.FullPath.ToLowerInvariant().EndsWith(".chorusnotes"))) { CollectChangesInFile(fileInRevision, parentRev.LocalRevisionNumber, changes); } } if (parentRevs.Count() > 1) changes = new List<IChangeReport>(changes.Distinct()); } return changes; }
public void Synchronizer_DoesNotReportOldChangeOnOtherBranch() { string savedSettings = ""; var rev1 = new Revision(null, "default", "Fred", "1234", "hash1234", "change something"); // The first revision we see on another branch doesn't produce a warning...it might be something old everyone has upgraded from. var revs = new[] { rev1 }; Assert.That(LiftSynchronizerAdjunct.GetRepositoryBranchCheckData(revs, "7.2.1", ref savedSettings), Is.Null); //Assert.That(savedSettings, Is.EqualTo("default:1234")); // Don't really care what is here as long as it works var rev2 = new Revision(null, "default", "Fred", "1234", "hash1234", "change something else"); revs = new[] { rev2 }; Assert.That(LiftSynchronizerAdjunct.GetRepositoryBranchCheckData(revs, "7.2.1", ref savedSettings), Is.Null); }
public bool IsDirectDescendantOf(Revision revision) { EnsureParentRevisionInfo(); //TODO: this is only checking direct descendant return(Parents.Any(p => p.Hash == revision.Number.Hash)); }
private static string RevisionId(Revision revision) { return revision.Number.LocalRevisionNumber; }
private void HistoryPageRevisionSelectionChanged(object sender, RevisionEventArgs e) { _currentRevision = e.Revision; }
/// <summary> /// There may be more, but for now: take care of the case where one guy has a file not /// modified (and not checked in), and the other guy is going to hammer it (with a remove /// or change). /// </summary> private void RemoveMergeObstacles(Revision rev1, Revision rev2) { /* this has proved a bit hard to get right. * when a file is in a recently brought in changeset, and also local (but untracked), status --rev ___ lists the file twice: * * >hg status --rev 14 * R test.txt * ? test.txt * */ //todo: push down to hgrepository var files = Repository.GetFilesInRevisionFromQuery(rev1 /*this param is bogus*/, "status -ru --rev " + rev2.Number.LocalRevisionNumber); foreach (var file in files) { if (file.ActionThatHappened == FileInRevision.Action.Deleted)// listed with 'R' { //is it also listed as unknown? if (files.Any(f => f.FullPath == file.FullPath && f.ActionThatHappened == FileInRevision.Action.Unknown)) { try { var newPath = file.FullPath + "-" + Path.GetRandomFileName() + ".ChorusRescuedFile"; _progress.WriteWarning( "Renamed {0} to {1} because it is not part of {2}'s repository but it is part of {3}'s, and this would otherwise prevent a merge.", file.FullPath, Path.GetFileName(newPath), rev1.UserId, rev2.UserId); if (!File.Exists(file.FullPath)) { _progress.WriteError("The file marked for rescuing didn't actually exist. Please report this bug in Chorus."); continue; } File.Move(file.FullPath, newPath); } catch (Exception error) { _progress.WriteError("Could not move the file. Error follows."); _progress.WriteException(error); throw; } } } } }
/// <returns>false if nothing needed to be merged, true if the merge was done. Throws exception if there is an error.</returns> private bool MergeTwoChangeSets(Revision head, Revision theirHead) { string chorusMergeFilePath = Path.Combine(ExecutionEnvironment.DirectoryOfExecutingAssembly, "ChorusMerge.exe"); #if MONO // The replace is only useful for use with the MonoDevelop environment whcih doesn't honor $(Configuration) in the csproj files. // When this is exported as an environment var it needs escaping to prevent the shell from replacing it with an empty string. // When MonoDevelop is fixed this can be removed. chorusMergeFilePath = chorusMergeFilePath.Replace("$(Configuration)", "\\$(Configuration)"); #endif using (new ShortTermEnvironmentalVariable("HGMERGE", '"' + chorusMergeFilePath + '"')) { // Theory has it that is a tossup on who ought to win, unless there is some more principled way to decide. // If 'they' end up being the right answer, or if it ends up being more exotic, // then be sure to change the alpha and beta info in the MergeSituation class. //using (new ShortTermEnvironmentalVariable(MergeOrder.kConflictHandlingModeEnvVarName, MergeOrder.ConflictHandlingModeChoices.TheyWin.ToString())) // Go with 'WeWin', since that is the default and that is how the alpha and beta data of MergeSituation is set, right before this method is called. using (new ShortTermEnvironmentalVariable(MergeOrder.kConflictHandlingModeEnvVarName, MergeOrder.ConflictHandlingModeChoices.WeWin.ToString())) { var didMerge = Repository.Merge(_localRepositoryPath, theirHead.Number.LocalRevisionNumber); FailureSimulator.IfTestRequestsItThrowNow("SychronizerAdjunct"); return didMerge; } } }
/// <summary> /// This method handles post merge tasks including the commit after the merge /// </summary> /// <param name="head"></param> /// <param name="peopleWeMergedWith"></param> private void DoPostMergeCommit(Revision head) { //that merge may have generated notes files where they didn't exist before, //and we want these merged //version + updated/created notes files to go right back into the repository // args.Append(" -X " + SurroundWithQuotes(Path.Combine(_pathToRepository, "**.ChorusRescuedFile"))); AppendAnyNewNotes(_localRepositoryPath); _sychronizerAdjunct.PrepareForPostMergeCommit(_progress); AddAndCommitFiles(GetMergeCommitSummary(head.UserId, Repository)); }
private void AddRow(Revision rev) { var dateString = rev.DateString; DateTime when; //TODO: this is all a guess and a mess //I haven't figured out how/why hg uses this strange date format, //nor if it is going to do it on all machines, or will someday //change. if (DateTime.TryParseExact(dateString, "ddd MMM dd HH':'mm':'ss yyyy zzz", null, DateTimeStyles.AssumeUniversal, out when)) { when = when.ToLocalTime(); dateString = when.ToShortDateString()+" "+when.ToShortTimeString(); } object image; if (rev.Summary.ToLower().Contains("conflict")) image = HistoryRowIcons.Warning; else if (rev.Parents.Count > 1) image = HistoryRowIcons.Merge; else { var colonLocation = rev.Summary.IndexOf(':'); string appName = rev.Summary; if (colonLocation > 0) { appName = appName.Substring(0, colonLocation); } var bracketLocation = appName.IndexOf(']'); if (bracketLocation > 0) { appName = appName.Substring(0, bracketLocation); } appName = appName.Trim(new char[] { '[', '+' }); // there was a bug in chorus that introduced the + //temp hack... the app has now been fixed to not include this appName = appName.Replace("0.5", ""); switch (appName.Trim()) { case "WeSay": image = HistoryRowIcons.WeSay; break; case "WeSay Configuration Tool": image = HistoryRowIcons.WeSayConfiguration; break; case "FieldWorks": image = HistoryRowIcons.FieldWorks; break; case "Bloom": image = HistoryRowIcons.Bloom16x16; break; default: image = HistoryRowIcons.GenericCheckin; break; } } int nIndex = _historyGrid.Rows.Add(new [] { image, false, false, dateString, rev.UserId, GetDescriptionForListView(rev) }); var row = _historyGrid.Rows[nIndex]; row.Tag = rev; row.Cells[0].ToolTipText = rev.Number.LocalRevisionNumber + ": " + rev.Number.Hash; var idx = row.Cells.Count - _extraColumns.Count; foreach (var extraColumn in _extraColumns) { row.Cells[idx++].Value = extraColumn.StringSupplier.Invoke(rev); } }
private string GetDescriptionForListView(Revision rev) { var s = rev.Summary.Substring(rev.Summary.IndexOf(']')+1).Trim(); if(s=="auto") return string.Empty; return s; }
public void SelectedRevisionChanged(Revision descriptor) { if (_revisionSelectedEvent!=null) _revisionSelectedEvent.Raise(descriptor); }
public ServerRevision(string id, Revision rev) { RemoteId = id; Revision = rev; }
public bool IsMatchingStub(Revision stub) { return stub.Summary.Contains(string.Format("({0} partial from", UserId)); }
public bool IsDirectDescendantOf(Revision revision) { EnsureParentRevisionInfo(); //TODO: this is only checking direct descendant return Parents.Any(p => p.Hash == revision.Number.Hash); }
/// <returns>false if nothing needed to be merged, true if the merge was done. Throws exception if there is an error.</returns> private bool MergeTwoChangeSets(Revision head, Revision theirHead) { // Theory has it that is a tossup on who ought to win, unless there is some more principled way to decide. // If 'they' end up being the right answer, or if it ends up being more exotic, // then be sure to change the alpha and beta info in the MergeSituation class. //using (new ShortTermEnvironmentalVariable(MergeOrder.kConflictHandlingModeEnvVarName, MergeOrder.ConflictHandlingModeChoices.TheyWin.ToString())) // Go with 'WeWin', since that is the default and that is how the alpha and beta data of MergeSituation is set, right before this method is called. using (new ShortTermEnvironmentalVariable(MergeOrder.kConflictHandlingModeEnvVarName, MergeOrder.ConflictHandlingModeChoices.WeWin.ToString())) { var didMerge = Repository.Merge(_localRepositoryPath, theirHead.Number.LocalRevisionNumber); FailureSimulator.IfTestRequestsItThrowNow("SychronizerAdjunct"); return didMerge; } }
public void Synchronizer_HandlesBothDefaultBranchOptions() { // Revisions can come in with both default or empty string on the default branch depending on OS string savedSettings = ""; var rev1 = new Revision(null, "default", "Fred", "1234", "hash1234", "change something"); // The first revision we see on another branch doesn't produce a warning...it might be something old everyone has upgraded from. var revs = new[] { rev1 }; Assert.That(LiftSynchronizerAdjunct.GetRepositoryBranchCheckData(revs, "", ref savedSettings), Is.Null); var rev2 = new Revision(null, "", "Joe", "1235", "hash1235", "change something else"); // To get the right result this time, the list of revisions must include both branches we are pretending are in the repo. revs = new[] { rev1, rev2 }; Assert.That(LiftSynchronizerAdjunct.GetRepositoryBranchCheckData(revs, "default", ref savedSettings), Is.Null); // first change we've seen on this branch var rev3 = new Revision(null, "default", "Fred", "1236", "hash1236", "Fred's second change"); revs = new[] { rev2, rev3 }; Assert.That(LiftSynchronizerAdjunct.GetRepositoryBranchCheckData(revs, "default", ref savedSettings), Is.Null); var rev4 = new Revision(null, "", "Joe", "1236", "hash1237", "Joe's second change"); revs = new[] { rev3, rev4 }; Assert.That(LiftSynchronizerAdjunct.GetRepositoryBranchCheckData(revs, "", ref savedSettings), Is.Null); }
private bool CheckAndWarnIfNoCommonAncestor(Revision a, Revision b ) { if (null ==Repository.GetCommonAncestorOfRevisions(a.Number.Hash,b.Number.Hash)) { _progress.WriteWarning( "This repository has an anomaly: the two heads we want to merge have no common ancestor. You should get help from the developers of this application."); _progress.WriteWarning("1) \"{0}\" on {1} by {2} ({3}). ", a.GetHashCode(), a.Summary, a.DateString, a.UserId); _progress.WriteWarning("2) \"{0}\" on {1} by {2} ({3}). ", b.GetHashCode(), b.Summary, b.DateString, b.UserId); return true; } return false; }
private void SetRevision(Revision descriptor) { Cursor.Current = Cursors.WaitCursor; if (descriptor != null) { Changes = _revisionInspector.GetChangeRecords(descriptor); } else { Changes = null; } if(UpdateDisplay !=null) { UpdateDisplay.Invoke(this, null); } Cursor.Current = Cursors.Default; }
private void MergeHeadsOrRollbackAndThrow(HgRepository repo, Revision workingRevBeforeSync) { try { MergeHeads(); } catch (Exception error) { foreach (var chorusMergeProcess in Process.GetProcessesByName("ChorusMerge")) { _progress.WriteWarning(string.Format("Killing ChorusMerge Process: '{0}'...", chorusMergeProcess.Id)); chorusMergeProcess.Kill(); } _progress.WriteException(error); _progress.WriteError("Rolling back..."); UpdateToTheDescendantRevision(repo, workingRevBeforeSync); //rollback throw; } }
internal void SelectedRevisionChanged(Revision descriptor) { // This was a public method, but it was changed to internal, since the whole model instance is effectively internal, // since it is not exposed beyond its view, which is also not exposed to anyone. // In an ideal world, the RevisionSelectedEvent instance in _revisionSelectedEvent would allow client code to add new subscribers, // but that world doesn't exist yet, so the view has added a kludge Windows event and calls it, after callinng this method. if (_revisionSelectedEvent!=null) _revisionSelectedEvent.Raise(descriptor); }
/// <summary> /// Sets up everything necessary for a call out to the ChorusMerge executable /// </summary> /// <param name="targetHead"></param> /// <param name="sourceHead"></param> private void PrepareForMergeAttempt(Revision targetHead, Revision sourceHead) { //this is for posterity, on other people's machines, so use the hashes instead of local numbers MergeSituation.PushRevisionsToEnvironmentVariables(targetHead.UserId, targetHead.Number.Hash, sourceHead.UserId, sourceHead.Number.Hash); MergeOrder.PushToEnvironmentVariables(_localRepositoryPath); _progress.WriteMessage("Merging {0} and {1}...", targetHead.UserId, sourceHead.UserId); _progress.WriteVerbose(" Revisions {0}:{1} with {2}:{3}...", targetHead.Number.LocalRevisionNumber, targetHead.Number.Hash, sourceHead.Number.LocalRevisionNumber, sourceHead.Number.Hash); RemoveMergeObstacles(targetHead, sourceHead); }
public BookMark(HgRepository repository) { _repository = repository; _revision = _repository.GetRevisionWorkingSetIsBasedOn(); }
/// <summary> /// If everything got merged, then this is trivial. But in case of a merge failure, /// the "tip" might be the other guy's unmergable data (maybe because he has a newer /// version of some application than we do) We don't want to switch to that! /// /// So if there are more than one head out there, we update to the one that is a descendant /// of our latest checkin (which in the simple merge failure case is the the checkin itself, /// but in a 3-or-more source scenario could be the result of a merge with a more cooperative /// revision). /// </summary> private void UpdateToTheDescendantRevision(HgRepository repository, Revision parent) { try { var heads = repository.GetHeads(); //if there is only one head for the branch we started from just update if (heads.Count(rev => rev.Branch == parent.Branch) == 1) { repository.Update(); //update to the tip _sychronizerAdjunct.SimpleUpdate(_progress, false); return; } if (heads.Count == 0) { return;//nothing has been checked in, so we're done! (this happens during some UI tests) } // when there are more than 2 people merging and there's a failure or a no-op merge happened foreach (var head in heads) { if (parent.Number.Hash == head.Number.Hash || (head.Branch == parent.Branch && head.IsDirectDescendantOf(parent))) { repository.RollbackWorkingDirectoryToRevision(head.Number.LocalRevisionNumber); _sychronizerAdjunct.SimpleUpdate(_progress, true); return; } } _progress.WriteWarning("Staying at previous-tip (unusual). Other users recent changes will not be visible."); } catch (UserCancelledException) { throw; } catch (Exception error) { ExplainAndThrow(error, "Could not update."); } }
private bool ShowRevisionPredicate(Revision revision) { return !revision.Summary.ToLower().Contains("hide"); }
private static string BranchName(Revision revision) { var name = revision.Branch; return string.IsNullOrWhiteSpace(name) ? @"default" : name; }
public void SynchronizerWithOnlyCurrentBranchRevision_ReportsNothing() { var rev1 = new Revision(null, "default", "Fred", "1234", "hash1234", "change something"); var revs = new[] { rev1 }; string savedSettings = ""; Assert.That(LiftSynchronizerAdjunct.GetRepositoryBranchCheckData(revs, "default", ref savedSettings), Is.Null); Assert.That(savedSettings,Is.EqualTo("")); // Even if we have remembered a previous revision on our own branch, we don't report problems on the current branch. var rev2 = new Revision(null, "default", "Fred", "1235", "hash1234", "change something else"); revs = new[] { rev2 }; savedSettings = "default"; Assert.That(LiftSynchronizerAdjunct.GetRepositoryBranchCheckData(revs, "default", ref savedSettings), Is.Null); Assert.That(savedSettings, Is.EqualTo("")); // still no revs on other branches to save. }
/// <summary> /// Do a no-op merge. (See: http://mercurial.selenic.com/wiki/PruningDeadBranches#No-Op_Merges) /// </summary> private void NoopMerge(HgRepository repo, Revision keeperRevision, Revision gonerRevision) { string optionalComment = null; using (var optionalCommentDlg = new OptionalCommentDlg()) { if (optionalCommentDlg.ShowDialog(this) == DialogResult.OK && !string.IsNullOrWhiteSpace(optionalCommentDlg.OptionalComment)) { optionalComment = optionalCommentDlg.OptionalComment.Trim(); } } // Merge goner into keeper. repo.Merge(_repoFolder, gonerRevision.Number.LocalRevisionNumber); // Revert the merge. repo.Execute(repo.SecondsBeforeTimeoutOnMergeOperation, "revert", "-a", "-r", keeperRevision.Number.LocalRevisionNumber); // Commit var comment = string.Format(@"No-Op Merge: Revert repository to revision '{0}'", keeperRevision.Number.LocalRevisionNumber); if (!string.IsNullOrWhiteSpace(optionalComment)) comment = string.Format(@"{0}. {1}", comment, optionalComment); repo.Commit(true, comment); }
public bool IsMatchingStub(Revision stub) { return(stub.Summary.Contains(string.Format("({0} partial from", UserId))); }