public void RemoveEmptyChanges() { int totalChanges = changes.Count; Logger.LogInformation("Removing empty changes from {count} changes", totalChanges); int number = 0; for (int index = 0; index < changes.Count; index++) { number++; if (number % 1000 == 0) { Logger.LogInformation("Checking if change {number} of {total} is empty", number, totalChanges); } var change = changes[index]; if (change.HasChanges()) { continue; } for (int i = index - 1; 0 <= i; i--) { var previousChange = changes[i]; if (previousChange.Branch != change.Branch) { continue; } ChangeSet nextChange = null; int indexNext = index; while (true) { indexNext++; if (changes.Count <= indexNext) { break; } var nextChangeCandidate = changes[indexNext]; if (nextChangeCandidate.Branch != change.Branch) { continue; } nextChange = nextChangeCandidate; break; } ReplaceBranchedFrom(change, previousChange); if (nextChange != null) { foreach (var br in change.GetBranchedFrom()) { nextChange.AddBranchedFrom(br); } // Move over comments about dirs as otherwise they will be lost. int dirIndex = 0; foreach (var dirComment in change.Changes) { if (dirComment.Type != ChangeSet.ChangeType.DIRECTORY_COMMENT) { throw new Exception($"Tryng to move non-dir comment change {dirComment} for {change}"); } nextChange.InsertDirectoryComment(dirIndex, dirComment.FileName, dirComment.Version as DirectoryElementBranchVersion); dirIndex++; } } changes.RemoveAt(index); // force a check for the entry that replaced it index--; Logger.LogDebug("Removing {change} and replacing with {previousChange}", change, previousChange); break; } } RebuildForwardMerges(); RebuildIndexLookup(); Logger.LogInformation("After removing empty change we got {count} changes", changes.Count); }
// Assuming A and C belongs to different branches, B is on the same // branch as A and D is on the same branch as C we merge the following // commit graph // A // /| // C B // |/ // D // to create the graph // A' // / // C' // with A'=A+B and C'=C+D public void SquashMergeDiamonds() { int totalChanges = changes.Count; Logger.LogInformation("Checking if any merge diamonds could be squashed out of {count} changes", totalChanges); bool changed; int number = 0; do { changed = false; foreach (var changeA in changes.ToList()) { if (changeA.GetBranchedFrom().Count != 1) { continue; } int indexA = indexLookup[changeA]; for (int indexB = indexA - 1; 0 <= indexB; indexB--) { var changeB = changes[indexB]; if (changeB.Branch != changeA.Branch) { continue; } if (!Squashable(changeB, changeA)) { break; } if (forwardMerges.ContainsKey(changeB) || changeB.GetBranchedFrom().Count != 1) { break; } ChangeSet changeC = changeA.GetBranchedFrom().First(); if (forwardMerges[changeC].Count != 1) { break; } ChangeSet changeD = null; int indexD = indexLookup[changeC]; while (true) { indexD--; if (indexD < 0) { break; } var changeDCandidate = changes[indexD]; if (changeDCandidate.Branch != changeC.Branch) { continue; } if (!Squashable(changeDCandidate, changeC)) { break; } if (changeB.GetBranchedFrom().First() != changeDCandidate || forwardMerges[changeDCandidate].Count != 1) { break; } changeD = changeDCandidate; break; } if (changeD == null) { break; } ChangeSet aPrime = changeB.Plus(changeA); ChangeSet cPrime = changeD.Plus(changeC); // changeA is always last. changes.RemoveAt(indexA); changes.Insert(indexA, aPrime); int indexC = indexLookup[changeC]; if (indexC < indexB) { changes.RemoveAt(indexB); changes.RemoveAt(indexC); changes.Insert(indexC, cPrime); } else { changes.RemoveAt(indexC); changes.Insert(indexC, cPrime); changes.RemoveAt(indexB); } // changeD is always first. changes.RemoveAt(indexD); aPrime.AddBranchedFrom(cPrime); ReplaceBranchedFrom(changeA, aPrime); ReplaceBranchedFrom(changeC, cPrime); changed = true; RebuildIndexLookup(); RebuildForwardMerges(); number++; if (number % 500 == 0) { Logger.LogInformation("Squashed {number} merge diamonds so far ({total} maximum possible", number, totalChanges); } Logger.LogDebug("Squashing merge diamond {changeA} + {changeB} => {aPrime}; {changeC} + {changeD} => {cPrime}", changeA, changeB, aPrime, changeC, changeD, cPrime); break; } if (changed) { break; } } } while (changed); Logger.LogInformation("After squashing merge diamonds we got {count} changes", changes.Count); }