From cfcb3fa608215107dbabc1fdef35d5b5d3d31284 Mon Sep 17 00:00:00 2001 From: Charles Zhang Date: Sun, 1 Sep 2019 19:18:44 -0400 Subject: [PATCH] [Core] (Core Logic) Fix VirtualRepository pass through function for tracking specific item - [Core] (Improvement) Fix logic for searching through commit history for potentially changing item names for VirtualRepository's `PassThrough()` function with target itemname - [Core] (Improvement) Add tracking tag changes for for VirtualRepository's `PassThrough()` function with target itemname --- SomewhereStandard/VirtualRepository.cs | 169 +++++++++++++++++++------ 1 file changed, 127 insertions(+), 42 deletions(-) diff --git a/SomewhereStandard/VirtualRepository.cs b/SomewhereStandard/VirtualRepository.cs index c145e6d..b9807a5 100644 --- a/SomewhereStandard/VirtualRepository.cs +++ b/SomewhereStandard/VirtualRepository.cs @@ -14,6 +14,9 @@ namespace Somewhere /// Virtual Repository simulates all operations in journal and creates /// a state that mimic effects to database from input commits at a given time /// + /// + /// The implementation of this class is subject to change so it's internal + /// internal class VirtualRepository { #region Private Types @@ -86,6 +89,8 @@ public void PassThrough(IEnumerable commits) break; case JournalEvent.CommitOperation.ChangeItemName: items[commitEvent.Target].Name = commitEvent.UpdateValue; // Assume it exists and there is no name conflict due to natural order of commit journal records + items.Add(commitEvent.UpdateValue, items[commitEvent.Target]); + items.Remove(commitEvent.Target); break; case JournalEvent.CommitOperation.ChangeItemTags: items[commitEvent.Target].Tags = commitEvent.UpdateValue; // Assume it exists @@ -126,55 +131,132 @@ public void PassThrough(IEnumerable commits) /// The output for this tracking will be a sequence of updates to that particular file /// /// Can be any particular name the file ever taken + /// + /// The complexity is that the name can occur anywhere any time and can be delted then recreated + /// which is in essence different entities but it still make sense to show it + /// public void PassThrough(List commits, string targetFilename) { - HashSet usedNames = new HashSet(); - usedNames.Add(targetFilename); - List relevantCommits = new List(); - // Go through each commit once in reverse order to collect all potential names - foreach (var commit in commits.Reverse()) + // Extract all commit events + var events = commits.Where(row => row.JournalType == JournalType.Commit) + .Select(row => row.JournalEvent).ToList(); + // Go through each commit once to collect name progression + Dictionary> nameProgression = new Dictionary>(); // The development of name changes for any given path + Dictionary>> nameOccurences = new Dictionary>>(); // The occurences of names in any paths + void UpdateNameOccurence(string name, List sourcePath) { - var commitEvent = commit.JournalEvent; - // Skip irrelevant entries - if (commit.JournalType == JournalType.Log) - // Skip this commit event - continue; - // Even if the commit's target name is not targetFilename, it may still refer to the same file - else if (!usedNames.Contains(commitEvent.Target)) + if (nameOccurences.ContainsKey(name)) + nameOccurences[name].Add(sourcePath); + else + nameOccurences[name] = new HashSet>() { sourcePath }; + } + foreach (var commitEvent in events) + { + switch (commitEvent.Operation) { - switch (commitEvent.Operation) - { - // If there is a change name event, then the target can still be relevant - case JournalEvent.CommitOperation.ChangeItemName: - if (usedNames.Contains(commitEvent.UpdateValue)) + case JournalEvent.CommitOperation.CreateNote: + case JournalEvent.CommitOperation.AddFile: + if(!nameProgression.ContainsKey(commitEvent.Target)) // The progression might already contains the name if it was created before then renamed or deleted + nameProgression.Add(commitEvent.Target, new List()); + nameProgression[commitEvent.Target].Add(commitEvent.Target); + UpdateNameOccurence(commitEvent.Target, nameProgression[commitEvent.Target]); + break; + case JournalEvent.CommitOperation.ChangeItemName: + foreach (var namePath in nameOccurences[commitEvent.Target]) + { + namePath.Add(commitEvent.UpdateValue); + UpdateNameOccurence(commitEvent.UpdateValue, namePath); + } + break; + default: + break; + } + } + // Go through each commit to collect tag progression + Dictionary> tagProgression = new Dictionary>(); // The development of tag changes for any given path + Dictionary>> tagOccurences = new Dictionary>>(); // The occurences of tags in any paths + Dictionary> tagUsers = new Dictionary>(); // Tags and the names it was used with + void UpdateTagOccurence(string tag, List sourcePath) + { + if (tagOccurences.ContainsKey(tag)) + tagOccurences[tag].Add(sourcePath); + else + tagOccurences[tag] = new HashSet>() { sourcePath }; + } + foreach (var commitEvent in events) + { + switch (commitEvent.Operation) + { + case JournalEvent.CommitOperation.ChangeItemTags: + var tags = commitEvent.UpdateValue.SplitTags(); + // Tag creation + foreach (var tag in tags) + { + // Record tag + if (!tagProgression.ContainsKey(tag)) // Tag might already exist because it was created before { - // Record this as a used name - usedNames.Add(commitEvent.Target); - relevantCommits.Add(commit); - break; + var path = new List() { tag }; + tagProgression.Add(tag, path); + UpdateTagOccurence(tag, path); } - else - // Skip this commit event - continue; - case JournalEvent.CommitOperation.ChangeItemTags: - case JournalEvent.CommitOperation.ChangeItemContent: - case JournalEvent.CommitOperation.DeleteTag: - case JournalEvent.CommitOperation.RenameTag: - case JournalEvent.CommitOperation.CreateNote: - case JournalEvent.CommitOperation.AddFile: - case JournalEvent.CommitOperation.DeleteFile: - default: - // Skip this commit event - continue; - } + // Record item name to tag + if (!tagUsers.ContainsKey(tag)) + tagUsers[tag] = new HashSet(); + tagUsers[tag].Add(commitEvent.Target); + } + break; + case JournalEvent.CommitOperation.RenameTag: + // Tag renaming + foreach (var tagPath in tagOccurences[commitEvent.Target]) + { + tagPath.Add(commitEvent.UpdateValue); + UpdateTagOccurence(commitEvent.UpdateValue, tagPath); + } + break; + default: + break; + } + } + // Null check + if (!nameOccurences.ContainsKey(targetFilename)) + return; + // Go through each commit to find relevant commits + List relevantCommits = new List(); + HashSet usedNames = new HashSet(nameOccurences[targetFilename].SelectMany(path => path)); + bool IsTagUserEverInvolvedTargetFilename(string sourceTag) + => tagOccurences[sourceTag] + // Get all paths items for the source tag + .SelectMany(path => path) + // Get all users for the ever taken variety of the tag and check whether them contained the wanted target file + .Select(tag => tagUsers[tag] + // Get the names that tag are created with + .SelectMany(name => + // Get the sequences of all changes for that name + nameProgression[name]) + .Contains(targetFilename)) + .Contains(true); + foreach (var commitEvent in events) + { + // If event target is one of used names, then it's relevant + if (usedNames.Contains(commitEvent.Target)) + relevantCommits.Add(commitEvent); + // If this operation is about tags, i.e. its target parameter is not name of item, then it can still be relevant + else if (commitEvent.Operation == JournalEvent.CommitOperation.RenameTag + || commitEvent.Operation == JournalEvent.CommitOperation.DeleteTag) + { + // Check whether the tag's tagged items involve the wanted item + var oldTag = commitEvent.Target; + var newTag = commitEvent.UpdateValue; // Can be null if operation is DeleteTag + if ( // Old tag's user's name progression contains target file name + IsTagUserEverInvolvedTargetFilename(oldTag) || + // New tag's user is involved + (newTag != null && IsTagUserEverInvolvedTargetFilename(newTag))) + relevantCommits.Add(commitEvent); } - // Commit contains the target filename - else - relevantCommits.Add(commit); } // Go through each commit again to simulate changes in normal order int stepSequence = 1; - foreach (var commit in relevantCommits.Reverse()) + foreach (var commitEvent in relevantCommits) { // Initialize new item by referring to an earlier version Item newItem = new Item() @@ -184,16 +266,19 @@ public void PassThrough(List commits, string targetFilename) Content = Items.LastOrDefault()?.Content }; // Handle this commit event - var commitEvent = commit.JournalEvent; switch (commitEvent.Operation) { case JournalEvent.CommitOperation.CreateNote: newItem.Name = $"(Step: {stepSequence})" + commitEvent.Target; // Use note name - newItem.Remark = "New"; // Indicate the note has been created at this step + newItem.Tags = null; // Clear tags for new item + newItem.Content = null; // Clear content for new item + newItem.Remark = "New creation"; // Indicate the note has been created at this step break; case JournalEvent.CommitOperation.AddFile: newItem.Name = $"(Step: {stepSequence})" + commitEvent.Target; // Use file name - newItem.Remark = "New"; // Indicate the file has been created at this step + newItem.Tags = null; // Clear tags for new item + newItem.Content = null; // Clear content for new item + newItem.Remark = "New add"; // Indicate the file has been created at this step break; case JournalEvent.CommitOperation.DeleteFile: newItem.Remark = "Deleted"; // Indicate the file has been deleted at this step