private void Plotter_CommitsChanged(object sender, CommitsChangedEventArgs e)
        {
            const int moveAnimationDurationInMilliseconds = 2500;
            const int fadeInDurationInMilliseconds        = 3000;

            Console.WriteLine("Added " + e.AddedCommits.Length + ", removed " + e.RemovedCommits.Length + ", moved " + e.MovedCommits.Length);

            foreach (var commit in e.AddedCommits)
            {
                spritesByCommitHash.Add(commit.Hash, new RevisionSprite(commit));

                Point destinationLocation = CellNumberToAbsoluteLocation(Plotter.GetCellNumber(commit.Hash));

                // When we first start up, we don't want to fade in everything.  But when commits get added, we want to animate that.
                if (e.WasTriggeredByInitialLoad)
                {
                    spritesByCommitHash[commit.Hash].X.PopTo(destinationLocation.X);
                    spritesByCommitHash[commit.Hash].Y.PopTo(destinationLocation.Y);
                    spritesByCommitHash[commit.Hash].Opacity.PopTo(1);
                }
                else
                {
                    // If this commit is being copied from another commit (i.e. if the hash is identical to another commit), animate moving it from the old commit to the new one.
                    List <string> duplicateDiffCommitHashes = this.GetCommitsWithIdenticalDiff(commit.Hash);
                    if (duplicateDiffCommitHashes.Count > 0)
                    {
                        // Arbitrarily pick one of the commits to say it's a duplicate of if there's more than one.
                        var duplicatedRevision = duplicateDiffCommitHashes[duplicateDiffCommitHashes.Count - 1];

                        Point sourceLocation = CellNumberToAbsoluteLocation(Plotter.GetCellNumber(duplicatedRevision));

                        // The source cell might be moving (done down below), so we need the start location, not the end location.
                        foreach (var movedCommit in e.MovedCommits)
                        {
                            if (movedCommit.Revision.Hash == duplicatedRevision)
                            {
                                sourceLocation = CellNumberToAbsoluteLocation(movedCommit.StartCellCoordinates);
                                break;
                            }
                        }

                        spritesByCommitHash[commit.Hash].X.Animate(sourceLocation.X, destinationLocation.X, DateTime.Now, DateTime.Now.AddMilliseconds(moveAnimationDurationInMilliseconds));
                        spritesByCommitHash[commit.Hash].Y.Animate(sourceLocation.Y, destinationLocation.Y, DateTime.Now, DateTime.Now.AddMilliseconds(moveAnimationDurationInMilliseconds));
                        spritesByCommitHash[commit.Hash].Opacity.PopTo(1);
                    }
                    else
                    {
                        spritesByCommitHash[commit.Hash].X.PopTo(destinationLocation.X);
                        spritesByCommitHash[commit.Hash].Y.PopTo(destinationLocation.Y);
                        spritesByCommitHash[commit.Hash].Opacity.Animate(0, 1, DateTime.Now, DateTime.Now.AddMilliseconds(fadeInDurationInMilliseconds));
                    }
                }
            }

            foreach (var commit in e.MovedCommits)
            {
                Point sourceLocation      = CellNumberToAbsoluteLocation(commit.StartCellCoordinates);
                Point destinationLocation = CellNumberToAbsoluteLocation(commit.EndCellCoordinates);
                spritesByCommitHash[commit.Revision.Hash].X.Animate(sourceLocation.X, destinationLocation.X, DateTime.Now, DateTime.Now.AddMilliseconds(moveAnimationDurationInMilliseconds));
                spritesByCommitHash[commit.Revision.Hash].Y.Animate(sourceLocation.Y, destinationLocation.Y, DateTime.Now, DateTime.Now.AddMilliseconds(moveAnimationDurationInMilliseconds));
            }

            foreach (var commit in e.RemovedCommits)
            {
                spritesByCommitHash.Remove(commit.Hash);
            }

            StartAnimationTimer(3);
            Invalidate();
        }