Magit recently gained support for something like this, but the feature is still a bit rough around the edges and has to be enabled explicitly.
A similar feature has existed for a long time - when you move from one commit to another in a log buffer, and another window in the same frame is the repository's revision buffer, then that buffer is refreshed to show that commit instead of whatever commit it was previously showing.
Until very recently the revision buffer was created when you first moved from one commit to another. I have changed that behavior and now the revision buffer is only ever updated. If no window in the same frame already displays that buffer, then moving around the log will no longer cause the buffer to be displayed in some new window or an existing window which previously displayed some other buffer.
So now you have to press SPC once to display the revision buffer. Only then will it start being updated when you move around the log buffer.
If you don't want the revision buffer to be updated, then do this:
(remove-hook 'magit-section-movement-hook
'magit-log-maybe-update-revision-buffer)
If on the other hand you also want the revision buffer to be updated when moving around in the status buffer, then use this:
(add-hook 'magit-section-movement-hook
'magit-status-maybe-update-revision-buffer)
It is now possible for a blob buffer to be automatically updated in a similar way. First add this:
(add-hook 'magit-section-movement-hook
'magit-log-maybe-update-blob-buffer)
And maybe also:
(add-hook 'magit-section-movement-hook
'magit-status-maybe-update-blob-buffer)
Then, inside a log (or status) buffer press RET to display or update the revision buffer, and to also switch to that buffer. Move to the file you are interested in and press RET again. You are now in a "blob-visiting" buffer, which is similar to a file-visiting buffer, except that it does not visit the file in the working tree but the file as it was in some commit.
Switch back to the log (or status) buffer and move to another commit. The blob buffer is being updated.
Note that displaying another blob, does not actually reuse the existing blob buffer. Instead it creates a new buffer which visits that blob and then displays that buffer in the window which previously displayed the other blob buffer. That other buffer is not being deleted, which unfortunately means that quite a few buffers can accumulate in a short period of time. Which would be one of those rough edges, I mentioned earlier. Pressing q in a blob buffer does delete it, so you can clean up by going to the window which was used to display the various blob buffers and keep pressing q until all the buffers have been deleted.
Additionally it is possible to navigate to the next or previous blob from within a blob buffer using p and n. I assume that in most cases you start with a recent blob and then move backward to older blobs using p. If you do that and then want to move in the other direction, I recommend you use q to kill the current buffer and thereby move to the previous buffer i.e. the next blob.
Also note that you don't have to first visit a revision to be able to visit a blob. You could also use M-x magit-find-file RET <revision> RET <file> RET
directly, but I find that less convenient.
undo-tree
uses a tree (it's in the name :-p), whereas Git history is a directed acyclic graph. It might be non-trivial.