/// <summary> /// Add a single revision from the git log. /// </summary> public void Add(GitRevision revision, RevisionNodeFlags types) { if (!_nodeByObjectId.TryGetValue(revision.ObjectId, out RevisionGraphRevision revisionGraphRevision)) { // This revision is added from the log, but not seen before. This is probably a root node (new branch) OR the revisions // are not in topo order. If this the case, we deal with it later. revisionGraphRevision = new RevisionGraphRevision(revision.ObjectId, ++_maxScore); revisionGraphRevision.LaneColor = revisionGraphRevision.IsCheckedOut ? 0 : _maxScore; _nodeByObjectId.TryAdd(revision.ObjectId, revisionGraphRevision); } else { // This revision was added earlier, but is now found in the log. // Increase the score to the current maxScore to keep the order in tact. revisionGraphRevision.EnsureScoreIsAbove(++_maxScore); } // This revision may have been added as a parent before. Probably only the ObjectId is known. Set all the other properties. revisionGraphRevision.GitRevision = revision; revisionGraphRevision.ApplyFlags(types); // No build the revisions parent/child structure. The parents need to added here. The child structure is kept in synch in // the RevisionGraphRevision class. if (revision.ParentIds is not null) { foreach (ObjectId parentObjectId in revision.ParentIds) { if (!_nodeByObjectId.TryGetValue(parentObjectId, out RevisionGraphRevision parentRevisionGraphRevision)) { // This parent is not loaded before. Create a new (partial) revision. We will complete the info in the revision // when this revision is loaded from the log. parentRevisionGraphRevision = new RevisionGraphRevision(parentObjectId, ++_maxScore); _nodeByObjectId.TryAdd(parentObjectId, parentRevisionGraphRevision); } else { // This revision is already loaded, add the existing revision to the parents list of new revision. // If the current score is lower, cache is invalid. The new score will (probably) be higher. MarkCacheAsInvalidIfNeeded(parentRevisionGraphRevision); } // Store the newly created segment (connection between 2 revisions) revisionGraphRevision.AddParent(parentRevisionGraphRevision, out int newMaxScore); _maxScore = Math.Max(_maxScore, newMaxScore); if (types.HasFlag(RevisionNodeFlags.OnlyFirstParent)) { break; } } } // Ensure all parents are loaded before adding it to the _nodes list. This is important for ordering. ImmutableInterlocked.Update(ref _nodes, (list, revision) => list.Add(revision), revisionGraphRevision); }
public void Add(GitRevision revision, RevisionNodeFlags flags) { var parentIds = revision.ParentIds; // If we haven't seen this node yet, create a new junction. // We process revisions in reverse order. // For each revision we create a node for the revision and its parents. // Most of the time we will have a node for this revision, created for // a parent of revision processed beforehand. if (!GetOrCreateNode(revision.ObjectId, out var node) && (parentIds == null || parentIds.Count == 0)) { // The revision has not been seen yet -- it must be a leaf node. // Create a junction for it. _junctions.Add(new Junction(node)); } Count++; node.Revision = revision; node.Flags = flags; node.Index = _nodes.Count; _nodes.Add(node); var isCheckedOut = flags.HasFlag(RevisionNodeFlags.CheckedOut); foreach (var parentId in parentIds ?? Array.Empty <ObjectId>()) { GetOrCreateNode(parentId, out var parent); if (parent.Index < node.Index) { // TODO: We might be able to recover from this with some work, but // since we build the graph async it might be tough to figure out. Debug.WriteLine("The nodes must be added such that all children are added before their parents"); continue; } if (node.Descendants.Count == 1 && node.Ancestors.Count <= 1 && node.Descendants[0].Oldest == node && parent.Ancestors.Count == 0 && // If this is true, the current revision is in the middle of a branch // and is about to start a new branch. This will also mean that the last // revisions are non-relative. Make sure a new junction is added and this // is the start of a new branch (and color!) !isCheckedOut) { // The node isn't a junction point. Just the parent to the node's // (only) ancestor junction. node.Descendants[0].Add(parent); } else if (node.Ancestors.Count == 1 && node.Ancestors[0].Youngest != node) { // The node is in the middle of a junction. We need to split it. _junctions.Add(node.Ancestors[0].SplitIntoJunctionWith(node)); // The node is a junction point. We are a new junction _junctions.Add(new Junction(node, parent)); } else if (parent.Descendants.Count == 1 && parent.Descendants[0].Oldest != parent) { // The parent is in the middle of a junction. We need to split it. _junctions.Add(parent.Descendants[0].SplitIntoJunctionWith(parent)); // The node is a junction point. We are a new junction _junctions.Add(new Junction(node, parent)); } else { // The node is a junction point. We are a new junction _junctions.Add(new Junction(node, parent)); } } var isRelative = isCheckedOut || node.Descendants.Any(d => d.IsRelative); var needsRebuild = false; foreach (var ancestor in node.Ancestors) { ancestor.IsRelative |= isRelative; // Uh, oh, we've already processed this lane. We'll have to update some rows. var parent = ancestor.TryGetParent(node); if (parent != null && parent.InLane != int.MaxValue) { Debug.WriteLine("We have to start over at lane {0} because of {1}", ancestor.Oldest.Descendants.Aggregate(ancestor.Oldest.InLane, (current, dd) => Math.Min(current, dd.Youngest.InLane)), node); needsRebuild = true; break; } } if (needsRebuild) { // TODO: It would be nice if we didn't have to start completely over...but it wouldn't // be easy since we don't keep around all of the necessary lane state for each step. var lastLaneIndex = Count - 1; ClearLanes(); CacheTo(lastLaneIndex); // We need to redraw everything Updated?.Invoke(); } else { Update(node); } bool GetOrCreateNode(ObjectId objectId, out Node n) { if (!_nodeByObjectId.TryGetValue(objectId, out n)) { n = new Node(objectId); _nodeByObjectId.Add(objectId, n); return(false); } return(true); } }