2

I checked out to a previous git commit, and I am trying to see the future revisions of the lines of one of the files of this commit.

Eg. If I have one file and two commits A and B (A being the initial commit, and B the current commit) :

Commit A:

1 ### file.py
2 
3 print("Hello world")

Commit B:

1 ### file.py
2 
3 # This is a comment
4 print("Hello Stack overflow")

When at commit B, everything works fine if I want to see the past history of the line with a print statement using :

git log -L 4,4:file.py

Now I want to do the same thing, but from commit A, and see the future changes of this line made in commit B. I tried doing :

git checkout commitB
git log --all -L 3,3:file.py

But I got the following error message :

fatal: More than one commit to dig from: HEAD and refs/heads/master?

My question is : Is there a way to see the future history of a line?

Charles G
  • 23
  • 2
  • 1
    In general, git history doesn't go _forwards_ - everything is based on following _parents_. I'm not even quite sure what the desired output would be in this case - how would you distinguish between multiple "future" branches that all changed the line in different ways? – IMSoP Apr 20 '21 at 13:48
  • You would probably have to mimic the function many IDEs with git integration has to allow you to blame a file, and then blame the previous version, etc. and then go back from the tip until the version you're interested in, tracking the blamed versions as you go backwards. git does not have anything built in that automates this. – Lasse V. Karlsen Apr 20 '21 at 13:51
  • Thanks for the comment ! I mean since it is possible to more or less see a log of future revisions of a commit using git log --all, wouldn't it be possible to do the same thing but with the -L flag? Or wouldn't it be possible to specify just one future branch and get the future revisions of the line only for this branch? – Charles G Apr 20 '21 at 13:55
  • @LasseV.Karlsen thanks for the input ! That's more or less what I am doing right now, but this assumes that you know what line in the latest commit corresponds to the line of interest in the earliest commit, which is not necessary my case. – Charles G Apr 20 '21 at 14:04
  • No, that is something you would have to track according to some algorithm. Even as humans that might be difficult to understand if the file has undergone radical changes. You might want to use a diff algorithm to compare version pair by version pair and see if you can track it that way. – Lasse V. Karlsen Apr 20 '21 at 14:07
  • @LasseV.Karlsen okay thanks ! – Charles G Apr 20 '21 at 14:08
  • 2
    Self-promotion, I have a DiffLib nuget package for .NET/C# that can be used to compare collections (such as lists of strings) and get something similar to what git would output. You might be able to use it to write that comparison automation. – Lasse V. Karlsen Apr 20 '21 at 14:09
  • @LasseV.Karlsen Nice I'll check that ! :) – Charles G Apr 20 '21 at 14:20

2 Answers2

0

git log isn't really the right tool but git diff can be.

Just need to make sure you have a revision set, for your example, I'd just do HEAD..<branch>

Here is an example:

$ gitldb     // this is an alias to the popular git dogball
* d75cada (master) Commit B
* e1e8f40 (HEAD) Commit A

dogball link

$ git diff HEAD..master -- file.py
diff --git a/file.py b/file.py
index 42da637..5b6c879 100644
--- a/file.py
+++ b/file.py
@@ -1,3 +1,4 @@
 ### file.py

-print("Hello world")
+# This is a comment
+print("Hello Stack overflow")

I personally find looking at the whole diff (by file if its very large) to be more useful than just the changes on a specific line. Even in this case, the specific line change just shows that it went from an actual line to a comment which isn't very useful.

g19fanatic
  • 10,567
  • 6
  • 33
  • 63
0

IMSoP's first comment contains a key question:

I'm not even quite sure what the desired output would be in this case - how would you distinguish between multiple "future" branches that all changed the line in different ways?

For instance, consider the following simple sequence of commits, in which the uppercase letters stand in for the commit hash IDs, and earlier commits are to the left with later commits to the right:

        D--E--F   <-- branch1
       /
A--B--C
       \
        G--H--I   <-- branch2

If you check out branch1, you get commit F. Running git log on file.py examines the copy of file.py in commit F, then the one in commit E, and so on, backwards. (I'm skipping over tons of detail here on purpose: we're focusing on mechanism first, though we'll come back to goal.) If you check out branch2, you get commit I, so a git log here will visit commits I, then H, then G, then C, B, and A in that order.

Obviously, if you check out any commit after C, you'll pick one "time line", as it were: either the time line of commits for branch1, or those for branch2. But if you check out C, there are two ways to go forward: via D, or via G.

In a maths-and-graph sense, what we have here is a partial order induced by the existence of a directed graph. Commit D precedes E which precedes F, which we denote as D ≺ E ≺ F.1 But partial orders leave some relationships unstated: in D vs G, there's no clear order, so D ⊀ G and yet also G ⊀ D. This is a fancy, math-y way to say: From C, we don't know which commit to move forwards to.

In Git, git log doesn't even try to solve this problem. Git makes you solve it, by checking out a later commit, or telling git log to start working backwards from some later commit. Problem solved—or at least swept under the rug, n'est-ce pas? Well, um, actually, non. Consider this graph fragment:

          I--J
         /    \
...--G--H      M--N   <-- main (HEAD)
         \    /
          K--L

Here, we've checked out commit N via branch main (that's what the HEAD in parentheses indicates), so git log with a file name will start at commit N and work backwards. But commit M is a merge commit, at which two branches—with no names any more; the branch names, if any existed,2 are long gone—were merged. When we make a merge commit, we are moving forwards—but to a time-traveler like Git, passing through a merge represents a divergence. So will git log go from commit M to commit J, or to commit L?

The git log command has multiple different answers for this. When running git log with no arguments, it will in fact visit both parent commits—in some unspecified order, and using a technique we won't cover here. But when running git log with a file name, with or without -L arguments, it usually defaults to visiting only one "branch" or "leg" of the merge (what to call it is up to you; I tend to call them legs myself). What git log does in this case is look to see which of the two parent commits J and L has a version of the file that matches the one in commit M. It then uses that parent to pick the leg to follow. If both match, it picks one leg more or less at random.

It would be possible for git log to do this in the other direction, when using --reverse from commit C in our first graph, for instance. But it doesn't, and in fact --reverse does not mean go forwards in git log: git log still goes backwards. It just prints out the commits in reverse, i.e., forwards, later.

Ultimately it comes down to this: each of Git's tools has certain limitations. Be aware of them, and when they pinch, consider another tool. In this case, you might want to look at git blame, which does have the ability to "go forwards" (though you must specify a particular revision range for this operation).


1Git internally implements instead the precedes-or-equals relationship, in which D ≼ D as well, but adding equality as an option doesn't affect what we're concerned with here.

2Branch names, in Git, exist so that we can find commits. But there are other ways to find commits, so we don't always require a branch name here. The other obvious way to find a commit is to work backwards (from some commit, probably found by a branch name), but any method that produces the right commit hash ID suffices. Since commit M, being a merge commit, contains the hash IDs of two different previous commits J and L, it works to find both previous commits, and we do not need branch names for either one.

torek
  • 448,244
  • 59
  • 642
  • 775